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