00001
00002
00003
00004
00005 #include <algorithm>
00006 using namespace std;
00007
00008 #include <QStringList>
00009
00010 #include "format.h"
00011 #include "cc608decoder.h"
00012 #include "mythcontext.h"
00013 #include "mythlogging.h"
00014 #include "vbilut.h"
00015
00016 #define DEBUG_XDS 0
00017
00018 static void init_xds_program_type(QString xds_program_type[96]);
00019
00020 CC608Decoder::CC608Decoder(CC608Input *ccr)
00021 : reader(ccr), ignore_time_code(false),
00022 rbuf(new unsigned char[sizeof(ccsubtitle)+255]),
00023 vps_l(0),
00024 wss_flags(0), wss_valid(false),
00025 xds_cur_service(-1),
00026 xds_crc_passed(0), xds_crc_failed(0),
00027 xds_lock(QMutex::Recursive),
00028 xds_net_call(QString::null), xds_net_name(QString::null),
00029 xds_tsid(0)
00030 {
00031 for (uint i = 0; i < 2; i++)
00032 {
00033 badvbi[i] = 0;
00034 lasttc[i] = 0;
00035 lastcode[i] = -1;
00036 lastcodetc[i] = 0;
00037 ccmode[i] = -1;
00038 xds[i] = 0;
00039 txtmode[i*2+0] = 0;
00040 txtmode[i*2+1] = 0;
00041 last_format_tc[i] = 0;
00042 last_format_data[i] = 0;
00043 }
00044
00045
00046 memset(lastrow, 0, sizeof(lastrow));
00047 memset(newrow, 0, sizeof(newrow));
00048 memset(newcol, 0, sizeof(newcol));
00049 memset(newattr, 0, sizeof(newattr));
00050 memset(timecode, 0, sizeof(timecode));
00051 memset(row, 0, sizeof(row));
00052 memset(col, 0, sizeof(col));
00053 memset(rowcount, 0, sizeof(rowcount));
00054 memset(style, 0, sizeof(style));
00055 memset(linecont, 0, sizeof(linecont));
00056 memset(resumetext, 0, sizeof(resumetext));
00057 memset(lastclr, 0, sizeof(lastclr));
00058
00059 for (uint i = 0; i < 8; i++)
00060 ccbuf[i] = "";
00061
00062
00063 for (uint i = 0; i < 128; i++)
00064 stdchar[i] = QChar(i);
00065 stdchar[42] = 'á';
00066 stdchar[92] = 'é';
00067 stdchar[94] = 'í';
00068 stdchar[95] = 'ó';
00069 stdchar[96] = 'ú';
00070 stdchar[123] = 'ç';
00071 stdchar[124] = '÷';
00072 stdchar[125] = 'Ñ';
00073 stdchar[126] = 'ñ';
00074 stdchar[127] = 0x2588;
00075
00076
00077 memset(vps_pr_label, 0, sizeof(vps_pr_label));
00078 memset(vps_label, 0, sizeof(vps_label));
00079
00080
00081 memset(xds_rating, 0, sizeof(uint) * 2 * 4);
00082 for (uint i = 0; i < 2; i++)
00083 {
00084 xds_rating_systems[i] = 0;
00085 xds_program_name[i] = QString::null;
00086 }
00087
00088 init_xds_program_type(xds_program_type_string);
00089 }
00090
00091 CC608Decoder::~CC608Decoder(void)
00092 {
00093 if (rbuf)
00094 delete [] rbuf;
00095 }
00096
00097 void CC608Decoder::FormatCC(int tc, int code1, int code2)
00098 {
00099 FormatCCField(tc, 0, code1);
00100 FormatCCField(tc, 1, code2);
00101 }
00102
00103 void CC608Decoder::GetServices(uint seconds, bool seen[4]) const
00104 {
00105 time_t now = time(NULL);
00106 time_t then = now - seconds;
00107 for (uint i = 0; i < 4; i++)
00108 seen[i] = (last_seen[i] >= then);
00109 }
00110
00111 static const int rowdata[] =
00112 {
00113 11, -1, 1, 2, 3, 4, 12, 13,
00114 14, 15, 5, 6, 7, 8, 9, 10
00115 };
00116
00117 static const QChar specialchar[] =
00118 {
00119 '®', '°', '½', '¿', 0x2122 , '¢', '£', 0x266A ,
00120 'à', ' ', 'è', 'â', 'ê', 'î', 'ô', 'û'
00121 };
00122
00123 static const QChar extendedchar2[] =
00124 {
00125 'Á', 'É', 'Ó', 'Ú', 'Ü', 'ü', '`', '¡',
00126 '*', '\'', 0x2014 , '©',
00127 0x2120 , '·', 0x201C, 0x201D ,
00128 'À', 'Â', 'Ç', 'È', 'Ê', 'Ë', 'ë', 'Î',
00129 'Ï', 'ï', 'Ô', 'Ù', 'ù', 'Û', '«', '»'
00130 };
00131
00132 static const QChar extendedchar3[] =
00133 {
00134 'Ã', 'ã', 'Í', 'Ì', 'ì', 'Ò', 'ò', 'Õ',
00135 'õ', '{', '}', '\\', '^', '_', '¦', '~',
00136 'Ä', 'ä', 'Ö', 'ö', 'ß', '¥', '¤', '|',
00137 'Å', 'å', 'Ø', 'ø', 0x250C, 0x2510, 0x2514, 0x2518
00138 };
00139
00140 void CC608Decoder::FormatCCField(int tc, int field, int data)
00141 {
00142 int b1, b2, len, x;
00143 int mode;
00144
00145 if (data == -1)
00146 {
00147
00148 if (ccmode[field] != -1)
00149 {
00150 for (mode = field*4; mode < (field*4 + 4); mode++)
00151 ResetCC(mode);
00152 xds[field] = 0;
00153 badvbi[field] = 0;
00154 ccmode[field] = -1;
00155 txtmode[field*2] = 0;
00156 txtmode[field*2 + 1] = 0;
00157 }
00158 return;
00159 }
00160
00161 if ((last_format_data[field&1] == data) &&
00162 (last_format_tc[field&1] == tc))
00163 {
00164 LOG(VB_VBI, LOG_DEBUG, "Format CC -- Duplicate");
00165 return;
00166 }
00167
00168 last_format_tc[field&1] = tc;
00169 last_format_data[field&1] = data;
00170
00171 b1 = data & 0x7f;
00172 b2 = (data >> 8) & 0x7f;
00173 #if 1
00174 LOG(VB_VBI, LOG_DEBUG, QString("Format CC @%1/%2 = %3 %4")
00175 .arg(tc).arg(field)
00176 .arg((data&0xff), 2, 16)
00177 .arg((data&0xff00)>>8, 2, 16));
00178 #endif
00179 if (ccmode[field] >= 0)
00180 {
00181 mode = field << 2 |
00182 (txtmode[field*2 + ccmode[field]] << 1) |
00183 ccmode[field];
00184 len = ccbuf[mode].length();
00185 }
00186 else
00187 {
00188 mode = -1;
00189 len = 0;
00190 }
00191
00192 if (FalseDup(tc, field, data))
00193 {
00194 if (ignore_time_code)
00195 return;
00196 else
00197 goto skip;
00198 }
00199
00200 if (XDSDecode(field, b1, b2))
00201 return;
00202
00203 if (b1 & 0x60)
00204
00205
00206 {
00207 if (mode >= 0)
00208 {
00209 lastcodetc[field] += 33;
00210 timecode[mode] = tc;
00211
00212
00213
00214 if (newrow[mode])
00215 len = NewRowCC(mode, len);
00216
00217 ccbuf[mode] += CharCC(b1);
00218 len++;
00219 col[mode]++;
00220 if (b2 & 0x60)
00221 {
00222 ccbuf[mode] += CharCC(b2);
00223 len++;
00224 col[mode]++;
00225 }
00226 }
00227 }
00228
00229 else if ((b1 & 0x10) && (b2 > 0x1F))
00230
00231
00232 {
00233 lastcodetc[field] += 67;
00234
00235 int newccmode = (b1 >> 3) & 1;
00236 int newtxtmode = txtmode[field*2 + newccmode];
00237 if ((b1 & 0x06) == 0x04)
00238 {
00239 switch (b2)
00240 {
00241 case 0x29:
00242 case 0x2C:
00243 case 0x20:
00244 case 0x2F:
00245 case 0x25:
00246 case 0x26:
00247 case 0x27:
00248
00249 newtxtmode = 0;
00250 break;
00251 case 0x2A:
00252 case 0x2B:
00253
00254 newtxtmode = 1;
00255 break;
00256 }
00257 }
00258 ccmode[field] = newccmode;
00259 txtmode[field*2 + newccmode] = newtxtmode;
00260 mode = (field << 2) | (newtxtmode << 1) | ccmode[field];
00261
00262 timecode[mode] = tc;
00263 len = ccbuf[mode].length();
00264
00265 if (b2 & 0x40)
00266 {
00267 if (newtxtmode)
00268
00269 goto skip;
00270
00271 newrow[mode] = rowdata[((b1 << 1) & 14) | ((b2 >> 5) & 1)];
00272 if (newrow[mode] == -1)
00273
00274 newrow[mode] = lastrow[mode] + 1;
00275
00276 if (b2 & 0x10)
00277 {
00278 newcol[mode] = (b2 & 0x0E) << 1;
00279
00280
00281 newattr[mode] = (b2 & 0x1) + 0x20;
00282 LOG(VB_VBI, LOG_INFO,
00283 QString("cc608 preamble indent, b2=%1")
00284 .arg(b2, 2, 16));
00285 }
00286 else
00287 {
00288 newcol[mode] = 0;
00289 newattr[mode] = (b2 & 0xf) + 0x10;
00290
00291
00292 LOG(VB_VBI, LOG_INFO,
00293 QString("cc608 preamble color change, b2=%1")
00294 .arg(b2, 2, 16));
00295 }
00296
00297
00298
00299 }
00300 else
00301 {
00302 switch (b1 & 0x07)
00303 {
00304 case 0x00:
00305 #if 0
00306 LOG(VB_VBI, LOG_DEBUG,
00307 QString("<ATTRIBUTE %1 %2>").arg(b1).arg(b2);
00308 #endif
00309 break;
00310 case 0x01:
00311 if (newrow[mode])
00312 len = NewRowCC(mode, len);
00313
00314 switch (b2 & 0x70)
00315 {
00316 case 0x20:
00317 LOG(VB_VBI, LOG_INFO,
00318 QString("cc608 mid-row color change, b2=%1")
00319 .arg(b2, 2, 16));
00320
00321
00322 ccbuf[mode] += QChar(0x7000 + (b2 & 0xf));
00323 len = ccbuf[mode].length();
00324 col[mode]++;
00325 break;
00326 case 0x30:
00327 ccbuf[mode] += specialchar[b2 & 0x0f];
00328 len++;
00329 col[mode]++;
00330 break;
00331 }
00332 break;
00333 case 0x02:
00334
00335
00336 if (!len)
00337 break;
00338
00339 if (b2 & 0x30)
00340 {
00341 ccbuf[mode].remove(len - 1, 1);
00342 ccbuf[mode] += extendedchar2[b2 - 0x20];
00343 len = ccbuf[mode].length();
00344 break;
00345 }
00346 break;
00347 case 0x03:
00348
00349
00350 if (!len)
00351 break;
00352
00353 if (b2 & 0x30)
00354 {
00355 ccbuf[mode].remove(len - 1, 1);
00356 ccbuf[mode] += extendedchar3[b2 - 0x20];
00357 len = ccbuf[mode].length();
00358 break;
00359 }
00360 break;
00361 case 0x04:
00362 case 0x05:
00363 #if 0
00364 LOG(VB_VBI, LOG_DEBUG,
00365 QString("ccmode %1 cmd %2").arg(ccmode)
00366 .arg(b2, 2, 16, '0'));
00367 #endif
00368 switch (b2)
00369 {
00370 case 0x21:
00371
00372 if (newrow[mode])
00373 len = NewRowCC(mode, len);
00374
00375 if (len == 0 ||
00376 ccbuf[mode].left(1) == "\b")
00377 {
00378 ccbuf[mode] += (char)'\b';
00379 len++;
00380 col[mode]--;
00381 }
00382 else
00383 {
00384 ccbuf[mode].remove(len - 1, 1);
00385 len = ccbuf[mode].length();
00386 col[mode]--;
00387 }
00388 break;
00389 case 0x25:
00390 case 0x26:
00391 case 0x27:
00392 if (style[mode] == CC_STYLE_PAINT && len)
00393 {
00394
00395 BufferCC(mode, len, 0);
00396 ccbuf[mode] = "";
00397 row[mode] = 0;
00398 col[mode] = 0;
00399 }
00400 else if (style[mode] == CC_STYLE_POPUP)
00401 ResetCC(mode);
00402
00403 rowcount[mode] = b2 - 0x25 + 2;
00404 style[mode] = CC_STYLE_ROLLUP;
00405 break;
00406 case 0x2D:
00407 if (style[mode] != CC_STYLE_ROLLUP)
00408 break;
00409
00410 if (newrow[mode])
00411 row[mode] = newrow[mode];
00412
00413
00414
00415 if (len || (row[mode] != 0 && !linecont[mode] &&
00416 (!newtxtmode || row[mode] >= 16)))
00417 {
00418 BufferCC(mode, len, 0);
00419 }
00420
00421 if (newtxtmode)
00422 {
00423 if (row[mode] < 16)
00424 newrow[mode] = row[mode] + 1;
00425 else
00426
00427 newrow[mode] = 16;
00428 }
00429
00430 ccbuf[mode] = "";
00431 col[mode] = 0;
00432 linecont[mode] = 0;
00433 break;
00434
00435 case 0x29:
00436
00437 if (style[mode] == CC_STYLE_ROLLUP && len)
00438 {
00439
00440 BufferCC(mode, len, 0);
00441 ccbuf[mode] = "";
00442 row[mode] = 0;
00443 col[mode] = 0;
00444 }
00445 else if (style[mode] == CC_STYLE_POPUP)
00446 ResetCC(mode);
00447
00448 style[mode] = CC_STYLE_PAINT;
00449 rowcount[mode] = 0;
00450 linecont[mode] = 0;
00451 break;
00452
00453 case 0x2B:
00454 resumetext[mode] = 1;
00455 if (row[mode] == 0)
00456 {
00457 newrow[mode] = 1;
00458 newcol[mode] = 0;
00459 newattr[mode] = 0;
00460 }
00461 style[mode] = CC_STYLE_ROLLUP;
00462 break;
00463 case 0x2C:
00464 if (ignore_time_code ||
00465 (tc - lastclr[mode]) > 5000 ||
00466 lastclr[mode] == 0)
00467
00468
00469 BufferCC(mode, 0, 1);
00470 if (style[mode] != CC_STYLE_POPUP)
00471 {
00472 row[mode] = 0;
00473 col[mode] = 0;
00474 }
00475 linecont[mode] = 0;
00476 break;
00477
00478 case 0x20:
00479 if (style[mode] != CC_STYLE_POPUP)
00480 {
00481 if (len)
00482
00483 BufferCC(mode, len, 0);
00484 ccbuf[mode] = "";
00485 row[mode] = 0;
00486 col[mode] = 0;
00487 }
00488 style[mode] = CC_STYLE_POPUP;
00489 rowcount[mode] = 0;
00490 linecont[mode] = 0;
00491 break;
00492 case 0x2F:
00493 if (style[mode] != CC_STYLE_POPUP)
00494 {
00495 if (len)
00496
00497 BufferCC(mode, len, 0);
00498 }
00499 else if (ignore_time_code ||
00500 (tc - lastclr[mode]) > 5000 ||
00501 lastclr[mode] == 0)
00502
00503 BufferCC(mode, len, 1);
00504 else if (len)
00505
00506 BufferCC(mode, len, 0);
00507 ccbuf[mode] = "";
00508 row[mode] = 0;
00509 col[mode] = 0;
00510 style[mode] = CC_STYLE_POPUP;
00511 rowcount[mode] = 0;
00512 linecont[mode] = 0;
00513 break;
00514
00515 case 0x2A:
00516
00517 BufferCC(mode, 0, 1);
00518 ResetCC(mode);
00519
00520 newrow[mode] = 1;
00521 newcol[mode] = 0;
00522 newattr[mode] = 0;
00523 style[mode] = CC_STYLE_ROLLUP;
00524 break;
00525
00526 case 0x2E:
00527 ResetCC(mode);
00528 break;
00529 }
00530 break;
00531 case 0x07:
00532 if (newrow[mode])
00533 {
00534 newcol[mode] += (b2 & 0x03);
00535 len = NewRowCC(mode, len);
00536 }
00537 else
00538
00539 for (x = 0; x < (b2 & 0x03); x++)
00540 {
00541 ccbuf[mode] += ' ';
00542 len++;
00543 col[mode]++;
00544 }
00545 break;
00546 }
00547 }
00548 }
00549
00550 skip:
00551 for (mode = field*4; mode < (field*4 + 4); mode++)
00552 {
00553 len = ccbuf[mode].length();
00554 if ((ignore_time_code || ((tc - timecode[mode]) > 100)) &&
00555 (style[mode] != CC_STYLE_POPUP) && len)
00556 {
00557
00558
00559 timecode[mode] = tc;
00560 BufferCC(mode, len, 0);
00561 ccbuf[mode] = "";
00562 row[mode] = lastrow[mode];
00563 linecont[mode] = 1;
00564 }
00565 }
00566
00567 if (data != lastcode[field])
00568 {
00569 lastcode[field] = data;
00570 lastcodetc[field] = tc;
00571 }
00572 lasttc[field] = tc;
00573 }
00574
00575 int CC608Decoder::FalseDup(int tc, int field, int data)
00576 {
00577 int b1, b2;
00578
00579 b1 = data & 0x7f;
00580 b2 = (data >> 8) & 0x7f;
00581
00582 if (ignore_time_code)
00583 {
00584
00585
00586
00587 if ((data == lastcode[field]) &&
00588 ((b1 & 0x70) == 0x10))
00589 {
00590 lastcode[field] = -1;
00591 return 1;
00592 }
00593 else
00594 {
00595 return 0;
00596 }
00597 }
00598
00599
00600
00601
00602
00603 int dup_text_fudge, dup_ctrl_fudge;
00604 if (badvbi[field] < 100 && b1 != 0 && b2 != 0)
00605 {
00606 int d = tc - lasttc[field];
00607 if (d < 25 || d > 42)
00608 badvbi[field]++;
00609 else if (badvbi[field] > 0)
00610 badvbi[field]--;
00611 }
00612 if (badvbi[field] < 4)
00613 {
00614
00615 dup_text_fudge = -2;
00616
00617 dup_ctrl_fudge = 33 - 4;
00618 }
00619 else
00620 {
00621 dup_text_fudge = 4;
00622 dup_ctrl_fudge = 33 - 4;
00623 }
00624
00625 if (data == lastcode[field])
00626 {
00627 if ((b1 & 0x70) == 0x10)
00628 {
00629 if (tc > (lastcodetc[field] + 67 + dup_ctrl_fudge))
00630 return 0;
00631 }
00632 else if (b1)
00633 {
00634
00635 if (tc > (lastcodetc[field] + 33 + dup_text_fudge))
00636 return 0;
00637 }
00638
00639 return 1;
00640 }
00641
00642 return 0;
00643 }
00644
00645 void CC608Decoder::ResetCC(int mode)
00646 {
00647
00648
00649
00650
00651 row[mode] = 0;
00652 col[mode] = 0;
00653 rowcount[mode] = 0;
00654
00655 linecont[mode] = 0;
00656 resumetext[mode] = 0;
00657 lastclr[mode] = 0;
00658 ccbuf[mode] = "";
00659 }
00660
00661 QString CC608Decoder::ToASCII(const QString &cc608str, bool suppress_unknown)
00662 {
00663 QString ret = "";
00664
00665 for (int i = 0; i < cc608str.length(); i++)
00666 {
00667 QChar cp = cc608str[i];
00668 int cpu = cp.unicode();
00669 switch (cpu)
00670 {
00671 case 0x2120 : ret += "(SM)"; break;
00672 case 0x2122 : ret += "(TM)"; break;
00673 case 0x2014 : ret += "(--)"; break;
00674 case 0x201C : ret += "``"; break;
00675 case 0x201D : ret += "''"; break;
00676 case 0x250C : ret += "|-"; break;
00677 case 0x2510 : ret += "-|"; break;
00678 case 0x2514 : ret += "|_"; break;
00679 case 0x2518 : ret += "_|"; break;
00680 case 0x2588 : ret += "[]"; break;
00681 case 0x266A : ret += "o/~"; break;
00682 case '\b' : ret += "\\b"; break;
00683 default :
00684 if (cpu >= 0x7000 && cpu < 0x7000 + 0x30)
00685 {
00686 if (!suppress_unknown)
00687 ret += QString("[%1]").arg(cpu - 0x7000, 2, 16);
00688 }
00689 else if (cpu >= 0x20 && cpu <= 0x80)
00690 ret += QString(cp.toLatin1());
00691 if (!suppress_unknown)
00692 ret += QString("[%1]").arg(cpu - 0x7000, 2, 16);
00693 }
00694 }
00695
00696 return ret;
00697 }
00698
00699 void CC608Decoder::BufferCC(int mode, int len, int clr)
00700 {
00701 QByteArray tmpbuf;
00702 if (len)
00703 {
00704
00705 tmpbuf = ccbuf[mode].toUtf8();
00706 len = min(tmpbuf.length(), 255);
00707 }
00708
00709 unsigned char f;
00710 unsigned char *bp = rbuf;
00711 *(bp++) = row[mode];
00712 *(bp++) = rowcount[mode];
00713 *(bp++) = style[mode];
00714
00715 f = resumetext[mode];
00716 f |= mode << 4;
00717 if (linecont[mode])
00718 f |= CC_LINE_CONT;
00719 *(bp++) = f;
00720 *(bp++) = clr;
00721 *(bp++) = len;
00722 if (len)
00723 {
00724 memcpy(bp,
00725 tmpbuf.constData(),
00726 len);
00727 len += sizeof(ccsubtitle);
00728 }
00729 else
00730 len = sizeof(ccsubtitle);
00731
00732 if (len && VERBOSE_LEVEL_CHECK(VB_VBI, LOG_INFO))
00733 {
00734 LOG(VB_VBI, LOG_INFO, QString("### %1 %2 %3 %4 %5 %6 %7 - '%8'")
00735 .arg(timecode[mode], 10)
00736 .arg(row[mode], 2).arg(rowcount[mode])
00737 .arg(style[mode]).arg(f, 2, 16)
00738 .arg(clr).arg(len, 3)
00739 .arg(ToASCII(QString::fromUtf8(tmpbuf.constData(), len), false)));
00740 }
00741
00742 reader->AddTextData(rbuf, len, timecode[mode], 'C');
00743 int ccmode = rbuf[3] & CC_MODE_MASK;
00744 int stream = -1;
00745 switch (ccmode)
00746 {
00747 case CC_CC1: stream = 0; break;
00748 case CC_CC2: stream = 1; break;
00749 case CC_CC3: stream = 2; break;
00750 case CC_CC4: stream = 3; break;
00751 }
00752 if (stream >= 0)
00753 last_seen[stream] = time(NULL);
00754
00755 resumetext[mode] = 0;
00756 if (clr && !len)
00757 lastclr[mode] = timecode[mode];
00758 else if (len)
00759 lastclr[mode] = 0;
00760 }
00761
00762 int CC608Decoder::NewRowCC(int mode, int len)
00763 {
00764 if (style[mode] == CC_STYLE_ROLLUP)
00765 {
00766
00767 row[mode] = newrow[mode];
00768 if (len)
00769 {
00770 BufferCC(mode, len, 0);
00771 ccbuf[mode] = "";
00772 len = 0;
00773 }
00774 col[mode] = 0;
00775 linecont[mode] = 0;
00776 }
00777 else
00778 {
00779
00780
00781 if (row[mode] == 0)
00782 {
00783 if (len == 0)
00784 row[mode] = newrow[mode];
00785 else
00786 {
00787
00788
00789 ccbuf[mode] += (char)'\n';
00790 len++;
00791 if (row[mode] == 0)
00792 row[mode] = newrow[mode] - 1;
00793 else
00794 row[mode]--;
00795 }
00796 }
00797 else if (newrow[mode] > lastrow[mode])
00798 {
00799
00800 for (int i = 0; i < (newrow[mode] - lastrow[mode]); i++)
00801 {
00802 ccbuf[mode] += (char)'\n';
00803 len++;
00804 }
00805 col[mode] = 0;
00806 }
00807 else if (newrow[mode] == lastrow[mode])
00808 {
00809
00810 if (newcol[mode] >= col[mode])
00811
00812 newcol[mode] -= col[mode];
00813 else
00814 {
00815
00816
00817
00818
00819
00820 ccbuf[mode] += (char)'\n';
00821 len++;
00822 col[mode] = 0;
00823 }
00824 }
00825 else
00826 {
00827
00828
00829 BufferCC(mode, len, 0);
00830 ccbuf[mode] = "";
00831 row[mode] = newrow[mode];
00832 col[mode] = 0;
00833 linecont[mode] = 0;
00834 len = 0;
00835 }
00836 }
00837
00838 lastrow[mode] = newrow[mode];
00839 newrow[mode] = 0;
00840
00841 int limit = (newattr[mode] ? newcol[mode] - 1 : newcol[mode]);
00842 for (int x = 0; x < limit; x++)
00843 {
00844 ccbuf[mode] += ' ';
00845 len++;
00846 col[mode]++;
00847 }
00848
00849 if (newattr[mode])
00850 {
00851 ccbuf[mode] += QChar(newattr[mode] + 0x7000);
00852 len++;
00853 col[mode]++;
00854 }
00855
00856 newcol[mode] = 0;
00857 newattr[mode] = 0;
00858
00859 return len;
00860 }
00861
00862
00863 static bool IsPrintable(char c)
00864 {
00865 return !(((c) & 0x7F) < 0x20 || ((c) & 0x7F) > 0x7E);
00866 }
00867
00868 static char Printable(char c)
00869 {
00870 return IsPrintable(c) ? ((c) & 0x7F) : '.';
00871 }
00872
00873 #if 0
00874 static int OddParity(unsigned char c)
00875 {
00876 c ^= (c >> 4); c ^= (c >> 2); c ^= (c >> 1);
00877 return c & 1;
00878 }
00879 #endif
00880
00881
00882
00883
00884
00885 static void DumpPIL(int pil)
00886 {
00887 int day = (pil >> 15);
00888 int mon = (pil >> 11) & 0xF;
00889 int hour = (pil >> 6 ) & 0x1F;
00890 int min = (pil ) & 0x3F;
00891
00892 #define _PIL_(day, mon, hour, min) \
00893 (((day) << 15) + ((mon) << 11) + ((hour) << 6) + ((min) << 0))
00894
00895 if (pil == _PIL_(0, 15, 31, 63))
00896 LOG(VB_VBI, LOG_INFO, " PDC: Timer-control (no PDC)");
00897 else if (pil == _PIL_(0, 15, 30, 63))
00898 LOG(VB_VBI, LOG_INFO, " PDC: Recording inhibit/terminate");
00899 else if (pil == _PIL_(0, 15, 29, 63))
00900 LOG(VB_VBI, LOG_INFO, " PDC: Interruption");
00901 else if (pil == _PIL_(0, 15, 28, 63))
00902 LOG(VB_VBI, LOG_INFO, " PDC: Continue");
00903 else if (pil == _PIL_(31, 15, 31, 63))
00904 LOG(VB_VBI, LOG_INFO, " PDC: No time");
00905 else
00906 LOG(VB_VBI, LOG_INFO, QString(" PDC: %1, 200X-%2-%3 %4:%5")
00907 .arg(pil).arg(mon).arg(day).arg(hour).arg(min));
00908 #undef _PIL_
00909 }
00910
00911 void CC608Decoder::DecodeVPS(const unsigned char *buf)
00912 {
00913 int cni, pcs, pty, pil;
00914
00915 int c = vbi_bit_reverse[buf[1]];
00916
00917 if ((int8_t) c < 0)
00918 {
00919 vps_label[vps_l] = 0;
00920 memcpy(vps_pr_label, vps_label, sizeof(vps_pr_label));
00921 vps_l = 0;
00922 }
00923 c &= 0x7F;
00924 vps_label[vps_l] = Printable(c);
00925 vps_l = (vps_l + 1) % 16;
00926
00927 LOG(VB_VBI, LOG_INFO, QString("VPS: 3-10: %1 %2 %3 %4 %5 %6 %7 %8 (\"%9\")")
00928 .arg(buf[0]).arg(buf[1]).arg(buf[2]).arg(buf[3]).arg(buf[4])
00929 .arg(buf[5]).arg(buf[6]).arg(buf[7]).arg(vps_pr_label));
00930
00931 pcs = buf[2] >> 6;
00932 cni = + ((buf[10] & 3) << 10)
00933 + ((buf[11] & 0xC0) << 2)
00934 + ((buf[8] & 0xC0) << 0)
00935 + (buf[11] & 0x3F);
00936 pil = ((buf[8] & 0x3F) << 14) + (buf[9] << 6) + (buf[10] >> 2);
00937 pty = buf[12];
00938
00939 LOG(VB_VBI, LOG_INFO, QString("CNI: %1 PCS: %2 PTY: %3 ")
00940 .arg(cni).arg(pcs).arg(pty));
00941
00942 DumpPIL(pil);
00943
00944
00945 }
00946
00947
00948
00949
00950
00951 void CC608Decoder::DecodeWSS(const unsigned char *buf)
00952 {
00953 static const int wss_bits[8] = { 0, 0, 0, 1, 0, 1, 1, 1 };
00954 uint wss = 0;
00955
00956 for (uint i = 0; i < 16; i++)
00957 {
00958 uint b1 = wss_bits[buf[i] & 7];
00959 uint b2 = wss_bits[(buf[i] >> 3) & 7];
00960
00961 if (b1 == b2)
00962 return;
00963 wss |= b2 << i;
00964 }
00965 unsigned char parity = wss & 0xf;
00966 parity ^= parity >> 2;
00967 parity ^= parity >> 1;
00968
00969 LOG(VB_VBI, LOG_INFO,
00970 QString("WSS: %1; %2 mode; %3 color coding;\n\t\t\t"
00971 " %4 helper; reserved b7=%5; %6\n\t\t\t"
00972 " open subtitles: %7; %scopyright %8; copying %9")
00973 .arg(formats[wss & 7])
00974 .arg((wss & 0x0010) ? "film" : "camera")
00975 .arg((wss & 0x0020) ? "MA/CP" : "standard")
00976 .arg((wss & 0x0040) ? "modulated" : "no")
00977 .arg(!!(wss & 0x0080))
00978 .arg((wss & 0x0100) ? "have TTX subtitles; " : "")
00979 .arg(subtitles[(wss >> 9) & 3])
00980 .arg((wss & 0x0800) ? "surround sound; " : "")
00981 .arg((wss & 0x1000) ? "asserted" : "unknown")
00982 .arg((wss & 0x2000) ? "restricted" : "not restricted"));
00983
00984 if (parity & 1)
00985 {
00986 wss_flags = wss;
00987 wss_valid = true;
00988 }
00989 }
00990
00991 QString CC608Decoder::XDSDecodeString(const vector<unsigned char> &buf,
00992 uint start, uint end) const
00993 {
00994 #if DEBUG_XDS
00995 for (uint i = start; (i < buf.size()) && (i < end); i++)
00996 {
00997 LOG(VB_VBI, LOG_INFO, QString("%1: 0x%2 -> 0x%3 %4")
00998 .arg(i,2)
00999 .arg(buf[i],2,16,QChar('0'))
01000 .arg(CharCC(buf[i]).unicode(),2,16,QChar('0'))
01001 .arg(CharCC(buf[i])));
01002 }
01003 #endif // DEBUG_XDS
01004
01005 QString tmp = "";
01006 for (uint i = start; (i < buf.size()) && (i < end); i++)
01007 {
01008 if (buf[i] > 0x0)
01009 tmp += CharCC(buf[i]);
01010 }
01011
01012 #if DEBUG_XDS
01013 LOG(VB_VBI, LOG_INFO, QString("XDSDecodeString: '%1'").arg(tmp));
01014 #endif // DEBUG_XDS
01015
01016 return tmp.trimmed();
01017 }
01018
01019 static bool is_better(const QString &newStr, const QString &oldStr)
01020 {
01021 if (!newStr.isEmpty() && newStr != oldStr &&
01022 (newStr != oldStr.left(newStr.length())))
01023 {
01024 if (oldStr.isEmpty())
01025 return true;
01026
01027
01028 for (int i = 0; i < newStr.length(); i++)
01029 if (newStr[i].toAscii() < 0x20)
01030 return false;
01031
01032 return true;
01033 }
01034 return false;
01035 }
01036
01037 uint CC608Decoder::GetRatingSystems(bool future) const
01038 {
01039 QMutexLocker locker(&xds_lock);
01040 return xds_rating_systems[(future) ? 1 : 0];
01041 }
01042
01043 uint CC608Decoder::GetRating(uint i, bool future) const
01044 {
01045 QMutexLocker locker(&xds_lock);
01046 return xds_rating[(future) ? 1 : 0][i & 0x3] & 0x7;
01047 }
01048
01049 QString CC608Decoder::GetRatingString(uint i, bool future) const
01050 {
01051 QMutexLocker locker(&xds_lock);
01052
01053 QString prefix[4] = { "MPAA-", "TV-", "CE-", "CF-" };
01054 QString mainStr[4][8] =
01055 {
01056 { "NR", "G", "PG", "PG-13", "R", "NC-17", "X", "NR" },
01057 { "NR", "Y", "Y7", "G", "PG", "14", "MA", "NR" },
01058 { "E", "C", "C8+", "G", "PG", "14+", "18+", "NR" },
01059 { "E", "G", "8+", "13+", "16+", "18+", "NR", "NR" },
01060 };
01061
01062 QString main = prefix[i] + mainStr[i][GetRating(i, future)];
01063
01064 if (kRatingTPG == i)
01065 {
01066 uint cf = (future) ? 1 : 0;
01067 if (!(xds_rating[cf][i]&0xF0))
01068 {
01069 main.detach();
01070 return main;
01071 }
01072
01073 main += " ";
01074
01075 if (xds_rating[cf][i] & 0x80)
01076 main += "D";
01077 if (xds_rating[cf][i] & 0x40)
01078 main += "V";
01079 if (xds_rating[cf][i] & 0x20)
01080 main += "S";
01081 if (xds_rating[cf][i] & 0x10)
01082 main += "L";
01083 }
01084
01085 main.detach();
01086 return main;
01087 }
01088
01089 QString CC608Decoder::GetProgramName(bool future) const
01090 {
01091 QMutexLocker locker(&xds_lock);
01092 QString ret = xds_program_name[(future) ? 1 : 0];
01093 ret.detach();
01094 return ret;
01095 }
01096
01097 QString CC608Decoder::GetProgramType(bool future) const
01098 {
01099 QMutexLocker locker(&xds_lock);
01100 const vector<uint> &program_type = xds_program_type[(future) ? 1 : 0];
01101 QString tmp = "";
01102
01103 for (uint i = 0; i < program_type.size(); i++)
01104 {
01105 if (i != 0)
01106 tmp += ", ";
01107 tmp += xds_program_type_string[program_type[i]];
01108 }
01109
01110 tmp.detach();
01111 return tmp;
01112 }
01113
01114 QString CC608Decoder::GetXDS(const QString &key) const
01115 {
01116 QMutexLocker locker(&xds_lock);
01117
01118 if (key == "ratings")
01119 return QString::number(GetRatingSystems(false));
01120 else if (key.left(11) == "has_rating_")
01121 return ((1<<key.right(1).toUInt()) & GetRatingSystems(false))?"1":"0";
01122 else if (key.left(7) == "rating_")
01123 return GetRatingString(key.right(1).toUInt(), false);
01124
01125 else if (key == "future_ratings")
01126 return QString::number(GetRatingSystems(true));
01127 else if (key.left(11) == "has_future_rating_")
01128 return ((1<<key.right(1).toUInt()) & GetRatingSystems(true))?"1":"0";
01129 else if (key.left(14) == "future_rating_")
01130 return GetRatingString(key.right(1).toUInt(), true);
01131
01132 else if (key == "programname")
01133 return GetProgramName(false);
01134 else if (key == "future_programname")
01135 return GetProgramName(true);
01136
01137 else if (key == "programtype")
01138 return GetProgramType(false);
01139 else if (key == "future_programtype")
01140 return GetProgramType(true);
01141
01142 else if (key == "callsign")
01143 {
01144 QString ret = xds_net_call;
01145 ret.detach();
01146 return ret;
01147 }
01148 else if (key == "channame")
01149 {
01150 QString ret = xds_net_name;
01151 ret.detach();
01152 return ret;
01153 }
01154 else if (key == "tsid")
01155 return QString::number(xds_tsid);
01156
01157 return QString::null;
01158 }
01159
01160 static int b1_to_service[16] =
01161 { -1,
01162 0, 0,
01163 1, 1,
01164 2, 2,
01165 3, 3,
01166 4, 4,
01167 5, 5,
01168 6, 6,
01169 -1,
01170 };
01171
01172 bool CC608Decoder::XDSDecode(int field, int b1, int b2)
01173 {
01174 if (field == 0)
01175 return false;
01176
01177 #if DEBUG_XDS
01178 LOG(VB_VBI, LOG_INFO,
01179 QString("XDSDecode: 0x%1 0x%2 '%3%4' xds[%5]=%6 in XDS %7")
01180 .arg(b1,2,16,QChar('0')).arg(b2,2,16,QChar('0'))
01181 .arg((CharCC(b1).unicode()>0x20) ? CharCC(b1) : QChar(' '))
01182 .arg((CharCC(b2).unicode()>0x20) ? CharCC(b2) : QChar(' '))
01183 .arg(field).arg(xds[field])
01184 .arg(xds_cur_service));
01185 #else
01186 (void) field;
01187 #endif // DEBUG_XDS
01188
01189 if (xds_cur_service < 0)
01190 {
01191 if (b1 > 0x0f)
01192 return false;
01193
01194 xds_cur_service = b1_to_service[b1];
01195
01196 if (xds_cur_service < 0)
01197 return false;
01198
01199 if (b1 & 1)
01200 {
01201 xds_buf[xds_cur_service].clear();
01202 #if DEBUG_XDS
01203 LOG(VB_VBI, LOG_INFO, QString("XDSDecode: Starting XDS %1").arg(xds_cur_service));
01204 #endif // DEBUG_XDS
01205 }
01206 }
01207 else if ((0x0 < b1) && (b1 < 0x0f))
01208 {
01209 xds_cur_service = b1_to_service[b1];
01210 #if DEBUG_XDS
01211 LOG(VB_VBI, LOG_INFO, QString("XDSDecode: Resuming XDS %1").arg(xds_cur_service));
01212 #endif // DEBUG_XDS
01213 }
01214
01215 if (xds_cur_service < 0)
01216 return false;
01217
01218 xds_buf[xds_cur_service].push_back(b1);
01219 xds_buf[xds_cur_service].push_back(b2);
01220
01221 if (b1 == 0x0f)
01222 {
01223 #if DEBUG_XDS
01224 LOG(VB_VBI, LOG_INFO, QString("XDSDecode: Ending XDS %1").arg(xds_cur_service));
01225 #endif // DEBUG_XDS
01226 if (XDSPacketCRC(xds_buf[xds_cur_service]))
01227 XDSPacketParse(xds_buf[xds_cur_service]);
01228 xds_buf[xds_cur_service].clear();
01229 xds_cur_service = -1;
01230 }
01231 else if ((0x10 <= b1) && (b1 <= 0x1f))
01232 {
01233 #if DEBUG_XDS
01234 LOG(VB_VBI, LOG_INFO, QString("XDSDecode: Suspending XDS %1 on 0x%2")
01235 .arg(xds_cur_service).arg(b1,2,16,QChar('0')));
01236 #endif // DEBUG_XDS
01237 xds_cur_service = -1;
01238 }
01239
01240 return true;
01241 }
01242
01243 void CC608Decoder::XDSPacketParse(const vector<unsigned char> &xds_buf)
01244 {
01245 QMutexLocker locker(&xds_lock);
01246
01247 bool handled = false;
01248 int xds_class = xds_buf[0];
01249
01250 if (!xds_class)
01251 return;
01252
01253 if ((xds_class == 0x01) || (xds_class == 0x03))
01254 handled = XDSPacketParseProgram(xds_buf, (xds_class == 0x03));
01255 else if (xds_class == 0x05)
01256 handled = XDSPacketParseChannel(xds_buf);
01257 else if (xds_class == 0x07)
01258 ;
01259 else if (xds_class == 0x09)
01260 ;
01261 else if (xds_class == 0x0b)
01262 ;
01263 else if (xds_class == 0x0d)
01264 handled = true;
01265 #if DEBUG_XDS
01266 if (!handled)
01267 {
01268 LOG(VB_VBI, LOG_INFO, QString("XDS: ") +
01269 QString("Unhandled packet (0x%1 0x%2) sz(%3) '%4'")
01270 .arg(xds_buf[0],0,16).arg(xds_buf[1],0,16)
01271 .arg(xds_buf.size())
01272 .arg(XDSDecodeString(xds_buf, 2, xds_buf.size() - 2)));
01273 }
01274 #endif // DEBUG_XDS
01275 }
01276
01277 bool CC608Decoder::XDSPacketCRC(const vector<unsigned char> &xds_buf)
01278 {
01279
01280 int sum = 0;
01281 for (uint i = 0; i < xds_buf.size() - 1; i++)
01282 sum += xds_buf[i];
01283
01284 if ((((~sum) & 0x7f) + 1) != xds_buf[xds_buf.size() - 1])
01285 {
01286 xds_crc_failed++;
01287
01288 LOG(VB_VBI, LOG_ERR, QString("XDS: failed CRC %1 of %2")
01289 .arg(xds_crc_failed).arg(xds_crc_failed + xds_crc_passed));
01290
01291 return false;
01292 }
01293
01294 xds_crc_passed++;
01295 return true;
01296 }
01297
01298 bool CC608Decoder::XDSPacketParseProgram(
01299 const vector<unsigned char> &xds_buf, bool future)
01300 {
01301 bool handled = true;
01302 int b2 = xds_buf[1];
01303 int cf = (future) ? 1 : 0;
01304 QString loc = (future) ? "XDS: Future " : "XDS: Current ";
01305
01306 if ((b2 == 0x01) && (xds_buf.size() >= 6))
01307 {
01308 uint min = xds_buf[2] & 0x3f;
01309 uint hour = xds_buf[3] & 0x0f;
01310 uint day = xds_buf[4] & 0x1f;
01311 uint month = xds_buf[5] & 0x0f;
01312 month = (month < 1 || month > 12) ? 0 : month;
01313
01314 LOG(VB_VBI, LOG_INFO, loc +
01315 QString("Start Time %1/%2 %3:%4%5")
01316 .arg(month).arg(day).arg(hour).arg(min / 10).arg(min % 10));
01317 }
01318 else if ((b2 == 0x02) && (xds_buf.size() >= 4))
01319 {
01320 uint length_min = xds_buf[2] & 0x3f;
01321 uint length_hour = xds_buf[3] & 0x3f;
01322 uint length_elapsed_min = 0;
01323 uint length_elapsed_hour = 0;
01324 uint length_elapsed_secs = 0;
01325 if (xds_buf.size() > 6)
01326 {
01327 length_elapsed_min = xds_buf[4] & 0x3f;
01328 length_elapsed_hour = xds_buf[5] & 0x3f;
01329 }
01330 if (xds_buf.size() > 8 && xds_buf[7] == 0x40)
01331 length_elapsed_secs = xds_buf[6] & 0x3f;
01332
01333 QString msg = QString("Program Length %1:%2%3 "
01334 "Time in Show %4:%5%6.%7%8")
01335 .arg(length_hour).arg(length_min / 10).arg(length_min % 10)
01336 .arg(length_elapsed_hour)
01337 .arg(length_elapsed_min / 10).arg(length_elapsed_min % 10)
01338 .arg(length_elapsed_secs / 10).arg(length_elapsed_secs % 10);
01339
01340 LOG(VB_VBI, LOG_INFO, loc + msg);
01341 }
01342 else if ((b2 == 0x03) && (xds_buf.size() >= 6))
01343 {
01344 QString tmp = XDSDecodeString(xds_buf, 2, xds_buf.size() - 2);
01345 if (is_better(tmp, xds_program_name[cf]))
01346 {
01347 xds_program_name[cf] = tmp;
01348 LOG(VB_VBI, LOG_INFO, loc + QString("Program Name: '%1'")
01349 .arg(GetProgramName(future)));
01350 }
01351 }
01352 else if ((b2 == 0x04) && (xds_buf.size() >= 6))
01353 {
01354 vector<uint> program_type;
01355 for (uint i = 2; i < xds_buf.size() - 2; i++)
01356 {
01357 int cur = xds_buf[i] - 0x20;
01358 if (cur >= 0 && cur < 96)
01359 program_type.push_back(cur);
01360 }
01361
01362 bool unchanged = xds_program_type[cf].size() == program_type.size();
01363 for (uint i = 0; (i < program_type.size()) && unchanged; i++)
01364 unchanged = xds_program_type[cf][i] == program_type[i];
01365
01366 if (!unchanged)
01367 {
01368 xds_program_type[cf] = program_type;
01369 LOG(VB_VBI, LOG_INFO, loc + QString("Program Type '%1'")
01370 .arg(GetProgramType(future)));
01371 }
01372 }
01373 else if ((b2 == 0x05) && (xds_buf.size() >= 4))
01374 {
01375 uint movie_rating = xds_buf[2] & 0x7;
01376 uint rating_system = (xds_buf[2] >> 3) & 0x7;
01377 uint tv_rating = xds_buf[3] & 0x7;
01378 uint VSL = xds_buf[3] & (0x7 << 3);
01379 uint sel = VSL | rating_system;
01380 if (sel == 3)
01381 {
01382 if (!(kHasCanEnglish & xds_rating_systems[cf]) ||
01383 (tv_rating != GetRating(kRatingCanEnglish, future)))
01384 {
01385 xds_rating_systems[cf] |= kHasCanEnglish;
01386 xds_rating[cf][kRatingCanEnglish] = tv_rating;
01387 LOG(VB_VBI, LOG_INFO, loc + QString("VChip %1")
01388 .arg(GetRatingString(kRatingCanEnglish, future)));
01389 }
01390 }
01391 else if (sel == 7)
01392 {
01393 if (!(kHasCanFrench & xds_rating_systems[cf]) ||
01394 (tv_rating != GetRating(kRatingCanFrench, future)))
01395 {
01396 xds_rating_systems[cf] |= kHasCanFrench;
01397 xds_rating[cf][kRatingCanFrench] = tv_rating;
01398 LOG(VB_VBI, LOG_INFO, loc + QString("VChip %1")
01399 .arg(GetRatingString(kRatingCanFrench, future)));
01400 }
01401 }
01402 else if (sel == 0x13 || sel == 0x1f)
01403 ;
01404 else if ((rating_system & 0x3) == 1)
01405 {
01406 if (!(kHasTPG & xds_rating_systems[cf]) ||
01407 (tv_rating != GetRating(kRatingTPG, future)))
01408 {
01409 uint f = ((xds_buf[0]<<3) & 0x80) | ((xds_buf[1]<<1) & 0x70);
01410 xds_rating_systems[cf] |= kHasTPG;
01411 xds_rating[cf][kRatingTPG] = tv_rating | f;
01412 LOG(VB_VBI, LOG_INFO, loc + QString("VChip %1")
01413 .arg(GetRatingString(kRatingTPG, future)));
01414 }
01415 }
01416 else if (rating_system == 0)
01417 {
01418 if (!(kHasMPAA & xds_rating_systems[cf]) ||
01419 (movie_rating != GetRating(kRatingMPAA, future)))
01420 {
01421 xds_rating_systems[cf] |= kHasMPAA;
01422 xds_rating[cf][kRatingMPAA] = movie_rating;
01423 LOG(VB_VBI, LOG_INFO, loc + QString("VChip %1")
01424 .arg(GetRatingString(kRatingMPAA, future)));
01425 }
01426 }
01427 else
01428 {
01429 LOG(VB_VBI, LOG_ERR, loc +
01430 QString("VChip Unhandled -- rs(%1) rating(%2:%3)")
01431 .arg(rating_system).arg(tv_rating).arg(movie_rating));
01432 }
01433 }
01434 #if 0
01435 else if (b2 == 0x07)
01436 ;
01437 else if (b2 == 0x08)
01438 ;
01439 else if (b2 == 0x09)
01440 ;
01441 else if (b2 == 0x0c)
01442 ;
01443 else if (b2 == 0x10 || b2 == 0x13 || b2 == 0x15 || b2 == 0x16 ||
01444 b2 == 0x91 || b2 == 0x92 || b2 == 0x94 || b2 == 0x97)
01445 ;
01446 else if (b2 == 0x86)
01447 ;
01448 else if (b2 == 0x89)
01449 ;
01450 else if (b2 == 0x8c)
01451 ;
01452 #endif
01453 else
01454 handled = false;
01455
01456 return handled;
01457 }
01458
01459 bool CC608Decoder::XDSPacketParseChannel(const vector<unsigned char> &xds_buf)
01460 {
01461 bool handled = true;
01462
01463 int b2 = xds_buf[1];
01464 if ((b2 == 0x01) && (xds_buf.size() >= 6))
01465 {
01466 QString tmp = XDSDecodeString(xds_buf, 2, xds_buf.size() - 2);
01467 if (is_better(tmp, xds_net_name))
01468 {
01469 LOG(VB_VBI, LOG_INFO, QString("XDS: Network Name '%1'").arg(tmp));
01470 xds_net_name = tmp;
01471 }
01472 }
01473 else if ((b2 == 0x02) && (xds_buf.size() >= 6))
01474 {
01475 QString tmp = XDSDecodeString(xds_buf, 2, xds_buf.size() - 2);
01476 if (is_better(tmp, xds_net_call) && (tmp.indexOf(" ") < 0))
01477 {
01478 LOG(VB_VBI, LOG_INFO, QString("XDS: Network Call '%1'").arg(tmp));
01479 xds_net_call = tmp;
01480 }
01481 }
01482 else if ((b2 == 0x04) && (xds_buf.size() >= 6))
01483 {
01484 uint tsid = (xds_buf[2] << 24 | xds_buf[3] << 16 |
01485 xds_buf[4] << 8 | xds_buf[5]);
01486 if (tsid != xds_tsid)
01487 {
01488 LOG(VB_VBI, LOG_INFO, QString("XDS: TSID 0x%1").arg(tsid,0,16));
01489 xds_tsid = tsid;
01490 }
01491 }
01492 else
01493 handled = false;
01494
01495 return handled;
01496 }
01497
01498 static void init_xds_program_type(QString xds_program_type[96])
01499 {
01500 xds_program_type[0] = QObject::tr("Education");
01501 xds_program_type[1] = QObject::tr("Entertainment");
01502 xds_program_type[2] = QObject::tr("Movie");
01503 xds_program_type[3] = QObject::tr("News");
01504 xds_program_type[4] = QObject::tr("Religious");
01505 xds_program_type[5] = QObject::tr("Sports");
01506 xds_program_type[6] = QObject::tr("Other");
01507 xds_program_type[7] = QObject::tr("Action");
01508 xds_program_type[8] = QObject::tr("Advertisement");
01509 xds_program_type[9] = QObject::tr("Animated");
01510 xds_program_type[10] = QObject::tr("Anthology");
01511 xds_program_type[11] = QObject::tr("Automobile");
01512 xds_program_type[12] = QObject::tr("Awards");
01513 xds_program_type[13] = QObject::tr("Baseball");
01514 xds_program_type[14] = QObject::tr("Basketball");
01515 xds_program_type[15] = QObject::tr("Bulletin");
01516 xds_program_type[16] = QObject::tr("Business");
01517 xds_program_type[17] = QObject::tr("Classical");
01518 xds_program_type[18] = QObject::tr("College");
01519 xds_program_type[19] = QObject::tr("Combat");
01520 xds_program_type[20] = QObject::tr("Comedy");
01521 xds_program_type[21] = QObject::tr("Commentary");
01522 xds_program_type[22] = QObject::tr("Concert");
01523 xds_program_type[23] = QObject::tr("Consumer");
01524 xds_program_type[24] = QObject::tr("Contemporary");
01525 xds_program_type[25] = QObject::tr("Crime");
01526 xds_program_type[26] = QObject::tr("Dance");
01527 xds_program_type[27] = QObject::tr("Documentary");
01528 xds_program_type[28] = QObject::tr("Drama");
01529 xds_program_type[29] = QObject::tr("Elementary");
01530 xds_program_type[30] = QObject::tr("Erotica");
01531 xds_program_type[31] = QObject::tr("Exercise");
01532 xds_program_type[32] = QObject::tr("Fantasy");
01533 xds_program_type[33] = QObject::tr("Farm");
01534 xds_program_type[34] = QObject::tr("Fashion");
01535 xds_program_type[35] = QObject::tr("Fiction");
01536 xds_program_type[36] = QObject::tr("Food");
01537 xds_program_type[37] = QObject::tr("Football");
01538 xds_program_type[38] = QObject::tr("Foreign");
01539 xds_program_type[39] = QObject::tr("Fund Raiser");
01540 xds_program_type[40] = QObject::tr("Game/Quiz");
01541 xds_program_type[41] = QObject::tr("Garden");
01542 xds_program_type[42] = QObject::tr("Golf");
01543 xds_program_type[43] = QObject::tr("Government");
01544 xds_program_type[44] = QObject::tr("Health");
01545 xds_program_type[45] = QObject::tr("High School");
01546 xds_program_type[46] = QObject::tr("History");
01547 xds_program_type[47] = QObject::tr("Hobby");
01548 xds_program_type[48] = QObject::tr("Hockey");
01549 xds_program_type[49] = QObject::tr("Home");
01550 xds_program_type[50] = QObject::tr("Horror");
01551 xds_program_type[51] = QObject::tr("Information");
01552 xds_program_type[52] = QObject::tr("Instruction");
01553 xds_program_type[53] = QObject::tr("International");
01554 xds_program_type[54] = QObject::tr("Interview");
01555 xds_program_type[55] = QObject::tr("Language");
01556 xds_program_type[56] = QObject::tr("Legal");
01557 xds_program_type[57] = QObject::tr("Live");
01558 xds_program_type[58] = QObject::tr("Local");
01559 xds_program_type[59] = QObject::tr("Math");
01560 xds_program_type[60] = QObject::tr("Medical");
01561 xds_program_type[61] = QObject::tr("Meeting");
01562 xds_program_type[62] = QObject::tr("Military");
01563 xds_program_type[63] = QObject::tr("Miniseries");
01564 xds_program_type[64] = QObject::tr("Music");
01565 xds_program_type[65] = QObject::tr("Mystery");
01566 xds_program_type[66] = QObject::tr("National");
01567 xds_program_type[67] = QObject::tr("Nature");
01568 xds_program_type[68] = QObject::tr("Police");
01569 xds_program_type[69] = QObject::tr("Politics");
01570 xds_program_type[70] = QObject::tr("Premiere");
01571 xds_program_type[71] = QObject::tr("Prerecorded");
01572 xds_program_type[72] = QObject::tr("Product");
01573 xds_program_type[73] = QObject::tr("Professional");
01574 xds_program_type[74] = QObject::tr("Public");
01575 xds_program_type[75] = QObject::tr("Racing");
01576 xds_program_type[76] = QObject::tr("Reading");
01577 xds_program_type[77] = QObject::tr("Repair");
01578 xds_program_type[78] = QObject::tr("Repeat");
01579 xds_program_type[79] = QObject::tr("Review");
01580 xds_program_type[80] = QObject::tr("Romance");
01581 xds_program_type[81] = QObject::tr("Science");
01582 xds_program_type[82] = QObject::tr("Series");
01583 xds_program_type[83] = QObject::tr("Service");
01584 xds_program_type[84] = QObject::tr("Shopping");
01585 xds_program_type[85] = QObject::tr("Soap Opera");
01586 xds_program_type[86] = QObject::tr("Special");
01587 xds_program_type[87] = QObject::tr("Suspense");
01588 xds_program_type[88] = QObject::tr("Talk");
01589 xds_program_type[89] = QObject::tr("Technical");
01590 xds_program_type[90] = QObject::tr("Tennis");
01591 xds_program_type[91] = QObject::tr("Travel");
01592 xds_program_type[92] = QObject::tr("Variety");
01593 xds_program_type[93] = QObject::tr("Video");
01594 xds_program_type[94] = QObject::tr("Weather");
01595 xds_program_type[95] = QObject::tr("Western");
01596 }