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