|
MythTV
0.26-pre
|
00001 #include <unistd.h> 00002 00003 #include <QTextCodec> 00004 00005 #include "dvbdescriptors.h" 00006 #include "iso6937tables.h" 00007 #include "freesat_huffman.h" 00008 #include "mythlogging.h" 00009 00010 00011 static QString decode_iso6937(const unsigned char *buf, uint length) 00012 { 00013 // ISO/IEC 6937 to unicode (UCS2) convertor... 00014 // This is a composed encoding - accent first then plain character 00015 QString result = ""; 00016 ushort ch = 0x20; 00017 for (uint i = 0; (i < length) && buf[i]; i++) 00018 { 00019 if (ch == 0xFFFF) 00020 { 00021 // Process second byte of two byte character 00022 ch = iso6937table_secondary[buf[i-1]][buf[i]]; 00023 if (ch == 0xFFFF) 00024 { 00025 // If no valid code found in secondary table, 00026 // reprocess this second byte as first byte. 00027 ch = iso6937table_base[buf[i]]; 00028 if (ch == 0xFFFF) 00029 continue; // process second byte 00030 } 00031 } 00032 else 00033 { 00034 // Process first character of two possible characters. 00035 // double byte characters have a sentinel (0xffff) in this table. 00036 ch = iso6937table_base[buf[i]]; 00037 if (ch == 0xFFFF) 00038 continue; // process second byte 00039 00040 } 00041 result += QChar(ch); 00042 } 00043 return result; 00044 } 00045 00046 static QString decode_text(const unsigned char *buf, uint length); 00047 00048 // Decode a text string according to ETSI EN 300 468 Annex A 00049 QString dvb_decode_text(const unsigned char *src, uint raw_length, 00050 const unsigned char *encoding_override, 00051 uint encoding_override_length) 00052 { 00053 if (!raw_length) 00054 return ""; 00055 00056 if (src[0] == 0x1f) 00057 return freesat_huffman_to_string(src, raw_length); 00058 00059 if (((0x10 < src[0]) && (src[0] < 0x15)) || 00060 ((0x15 < src[0]) && (src[0] < 0x20))) 00061 { 00062 // TODO: Handle multi-byte encodings 00063 LOG(VB_SIPARSER, LOG_ERR, 00064 "dvb_decode_text: Multi-byte coded text is not yet supported."); 00065 return ""; 00066 } 00067 00068 // if a override encoding is specified and the default ISO 6937 encoding 00069 // would be used copy the override encoding in front of the text 00070 unsigned char *dst = 00071 new unsigned char[raw_length + encoding_override_length]; 00072 00073 uint length = 0; 00074 if (encoding_override && src[0] >= 0x20) { 00075 memcpy(dst, encoding_override, encoding_override_length); 00076 length = encoding_override_length; 00077 } 00078 00079 // Strip formatting characters 00080 for (uint i = 0; i < raw_length; i++) 00081 { 00082 if ((src[i] < 0x80) || (src[i] > 0x9F)) 00083 dst[length++] = src[i]; 00084 // replace CR/LF with a space 00085 else if (src[i] == 0x8A) 00086 dst[length++] = 0x20; 00087 } 00088 00089 // Exit on empty string, sans formatting. 00090 00091 QString sStr = (!length) ? "" : decode_text(dst, length); 00092 00093 delete [] dst; 00094 00095 return sStr; 00096 } 00097 00098 static QString decode_text(const unsigned char *buf, uint length) 00099 { 00100 // Only some of the QTextCodec calls are reentrant. 00101 // If you use this please verify that you are using a reentrant call. 00102 static const QTextCodec *iso8859_codecs[16] = 00103 { 00104 QTextCodec::codecForName("Latin1"), 00105 QTextCodec::codecForName("ISO8859-1"), // Western 00106 QTextCodec::codecForName("ISO8859-2"), // Central European 00107 QTextCodec::codecForName("ISO8859-3"), // Central European 00108 QTextCodec::codecForName("ISO8859-4"), // Baltic 00109 QTextCodec::codecForName("ISO8859-5"), // Cyrillic 00110 QTextCodec::codecForName("ISO8859-6"), // Arabic 00111 QTextCodec::codecForName("ISO8859-7"), // Greek 00112 QTextCodec::codecForName("ISO8859-8"), // Hebrew, visually ordered 00113 QTextCodec::codecForName("ISO8859-9"), // Turkish 00114 QTextCodec::codecForName("ISO8859-10"), 00115 QTextCodec::codecForName("ISO8859-11"), 00116 QTextCodec::codecForName("ISO8859-12"), 00117 QTextCodec::codecForName("ISO8859-13"), 00118 QTextCodec::codecForName("ISO8859-14"), 00119 QTextCodec::codecForName("ISO8859-15"), // Western 00120 }; 00121 00122 // Decode using the correct text codec 00123 if (buf[0] >= 0x20) 00124 { 00125 return decode_iso6937(buf, length); 00126 } 00127 else if ((buf[0] >= 0x01) && (buf[0] <= 0x0B)) 00128 { 00129 return iso8859_codecs[4 + buf[0]]->toUnicode((char*)(buf + 1), length - 1); 00130 } 00131 else if (buf[0] == 0x10) 00132 { 00133 // If the first byte of the text field has a value "0x10" 00134 // then the following two bytes carry a 16-bit value (uimsbf) N 00135 // to indicate that the remaining data of the text field is 00136 // coded using the character code table specified by 00137 // ISO Standard 8859, parts 1 to 9 00138 00139 uint code = buf[1] << 8 | buf[2]; 00140 if (code <= 15) 00141 return iso8859_codecs[code]->toUnicode((char*)(buf + 3), length - 3); 00142 else 00143 return QString::fromLocal8Bit((char*)(buf + 3), length - 3); 00144 } 00145 else if (buf[0] == 0x15) // Already Unicode 00146 { 00147 return QString::fromUtf8((char*)(buf + 1), length - 1); 00148 } 00149 else 00150 { 00151 // Unknown/invalid encoding - assume local8Bit 00152 return QString::fromLocal8Bit((char*)(buf + 1), length - 1); 00153 } 00154 } 00155 00156 00157 QString dvb_decode_short_name(const unsigned char *src, uint raw_length) 00158 { 00159 if (raw_length > 50) 00160 { 00161 LOG(VB_SIPARSER, LOG_WARNING, 00162 QString("dvb_decode_short_name: name is %1 chars " 00163 "long. Unlikely to be a short name.") 00164 .arg(raw_length)); 00165 return ""; 00166 } 00167 00168 if (((0x10 < src[0]) && (src[0] < 0x15)) || 00169 ((0x15 < src[0]) && (src[0] < 0x20))) 00170 { 00171 // TODO: Handle multi-byte encodings 00172 LOG(VB_SIPARSER, LOG_ERR, "dvb_decode_short_name: " 00173 "Multi-byte coded text is not yet supported."); 00174 return ""; 00175 } 00176 00177 unsigned char *dst = new unsigned char[raw_length]; 00178 uint length = 0; 00179 00180 // check for emphasis control codes 00181 for (uint i = 0; i < raw_length; i++) 00182 if (src[i] == 0x86) 00183 while ((++i < raw_length) && (src[i] != 0x87)) 00184 { 00185 if ((src[i] < 0x80) || (src[i] > 0x9F)) 00186 dst[length++] = src[i]; 00187 // replace CR/LF with a space 00188 else if (src[i] == 0x8A) 00189 dst[length++] = 0x20; 00190 } 00191 00192 QString sStr = (!length) ? dvb_decode_text(src, raw_length) 00193 : decode_text(dst, length); 00194 00195 delete [] dst; 00196 00197 return sStr; 00198 } 00199 00200 QMutex ContentDescriptor::categoryLock; 00201 QMap<uint,QString> ContentDescriptor::categoryDesc; 00202 volatile bool ContentDescriptor::categoryDescExists = false; 00203 00204 QString myth_category_type_to_string(uint category_type) 00205 { 00206 static const char *cattype[] = 00207 { "", "movie", "series", "sports", "tvshow", }; 00208 00209 if ((category_type > kCategoryNone) && (category_type < kCategoryLast)) 00210 return QString(cattype[category_type]); 00211 00212 return ""; 00213 } 00214 00215 MythCategoryType string_to_myth_category_type(const QString &category_type) 00216 { 00217 static const char *cattype[] = 00218 { "", "movie", "series", "sports", "tvshow", }; 00219 00220 for (uint i = 1; i < 5; i++) 00221 if (category_type == cattype[i]) 00222 return (MythCategoryType) i; 00223 return kCategoryNone; 00224 } 00225 00226 MythCategoryType ContentDescriptor::GetMythCategory(uint i) const 00227 { 00228 if (0x1 == Nibble1(i)) 00229 return kCategoryMovie; 00230 if (0x4 == Nibble1(i)) 00231 return kCategorySports; 00232 return kCategoryTVShow; 00233 } 00234 00235 const char *linkage_types[] = 00236 { 00237 "Reserved(0x00)", 00238 "Information Service", 00239 "EPG Service", 00240 "CA Replacement Service", 00241 "TS Containing Complete Network/Bouquet SI", 00242 "Service Replacement Service", 00243 "Data Broadcast Service", 00244 "RCS Map", 00245 "Mobile Hand-Over", 00246 "System Software Update Service", 00247 "TS Containing SSU, BAT or NIT", 00248 "IP/MAC Notification Service", 00249 "TS Containing INT, BAT or NIT", 00250 "Event Linkage", 00251 }; 00252 00253 00254 QString LinkageDescriptor::LinkageTypeString(void) const 00255 { 00256 if (LinkageType() < (sizeof(linkage_types) / sizeof(const char*))) 00257 return QString(linkage_types[LinkageType()]); 00258 if ((LinkageType() <= 0x7f) || (LinkageType() == 0x7f)) 00259 return QString("Reserved(0x%1)").arg(LinkageType(),2,16,QChar('0')); 00260 return QString("User Defined(0x%1)").arg(LinkageType(),2,16,QChar('0')); 00261 } 00262 00263 QString LinkageDescriptor::MobileHandOverTypeString(void) const 00264 { 00265 if (kHandOverIdentical == MobileHandOverType()) 00266 return "Hand-Over to an Identical Service"; 00267 if (kHandOverLocalVariation == MobileHandOverType()) 00268 return "Hand-Over to a Local Variation"; 00269 if (kHandOverAssociatedService == MobileHandOverType()) 00270 return "Hand-over to an Associated Service"; 00271 return "Reserved"; 00272 } 00273 00274 QString ContentDescriptor::GetDescription(uint i) const 00275 { 00276 if (!categoryDescExists) 00277 Init(); 00278 00279 QMutexLocker locker(&categoryLock); 00280 00281 // Try to get detailed description 00282 QMap<uint,QString>::const_iterator it = categoryDesc.find(Nibble(i)); 00283 if (it != categoryDesc.end()) 00284 return *it; 00285 00286 // Fall back to category description 00287 it = categoryDesc.find(Nibble1(i)<<4); 00288 if (it != categoryDesc.end()) 00289 return *it; 00290 00291 // Found nothing? Just return empty string. 00292 return ""; 00293 } 00294 00295 QString ContentDescriptor::toString() const 00296 { 00297 QString tmp("ContentDescriptor: "); 00298 for (uint i = 0; i < Count(); i++) 00299 tmp += GetMythCategory(i) + " : " + GetDescription(i) + ", "; 00300 return tmp; 00301 } 00302 00303 void ContentDescriptor::Init(void) 00304 { 00305 QMutexLocker locker(&categoryLock); 00306 00307 if (categoryDescExists) 00308 return; 00309 00310 categoryDesc[0x10] = QObject::tr("Movie"); 00311 categoryDesc[0x11] = QObject::tr("Movie") + " - " + 00312 QObject::tr("Detective/Thriller"); 00313 categoryDesc[0x12] = QObject::tr("Movie")+ " - " + 00314 QObject::tr("Adventure/Western/War"); 00315 categoryDesc[0x13] = QObject::tr("Movie")+ " - " + 00316 QObject::tr("Science Fiction/Fantasy/Horror"); 00317 categoryDesc[0x14] = QObject::tr("Movie")+ " - " + 00318 QObject::tr("Comedy"); 00319 categoryDesc[0x15] = QObject::tr("Movie")+ " - " + 00320 QObject::tr("Soap/melodrama/folkloric"); 00321 categoryDesc[0x16] = QObject::tr("Movie")+ " - " + 00322 QObject::tr("Romance"); 00323 categoryDesc[0x17] = QObject::tr("Movie")+ " - " + 00324 QObject::tr("Serious/Classical/Religious/Historical Movie/Drama"); 00325 categoryDesc[0x18] = QObject::tr("Movie")+ " - " + 00326 QObject::tr("Adult", "Adult Movie"); 00327 00328 categoryDesc[0x20] = QObject::tr("News"); 00329 categoryDesc[0x21] = QObject::tr("News/weather report"); 00330 categoryDesc[0x22] = QObject::tr("News magazine"); 00331 categoryDesc[0x23] = QObject::tr("Documentary"); 00332 categoryDesc[0x24] = QObject::tr("Intelligent Programs"); 00333 00334 categoryDesc[0x30] = QObject::tr("Entertainment"); 00335 categoryDesc[0x31] = QObject::tr("Game Show"); 00336 categoryDesc[0x32] = QObject::tr("Variety Show"); 00337 categoryDesc[0x33] = QObject::tr("Talk Show"); 00338 00339 categoryDesc[0x40] = QObject::tr("Sports"); 00340 categoryDesc[0x41] = 00341 QObject::tr("Special Events (World Cup, World Series, etc)"); 00342 categoryDesc[0x42] = QObject::tr("Sports Magazines"); 00343 categoryDesc[0x43] = QObject::tr("Football (Soccer)"); 00344 categoryDesc[0x44] = QObject::tr("Tennis/Squash"); 00345 categoryDesc[0x45] = 00346 QObject::tr("Misc. Team Sports"); // not football/soccer 00347 categoryDesc[0x46] = QObject::tr("Athletics"); 00348 categoryDesc[0x47] = QObject::tr("Motor Sport"); 00349 categoryDesc[0x48] = QObject::tr("Water Sport"); 00350 categoryDesc[0x49] = QObject::tr("Winter Sports"); 00351 categoryDesc[0x4A] = QObject::tr("Equestrian"); 00352 categoryDesc[0x4B] = QObject::tr("Martial Sports"); 00353 00354 categoryDesc[0x50] = QObject::tr("Kids"); 00355 categoryDesc[0x51] = QObject::tr("Pre-School Children's Programs"); 00356 categoryDesc[0x52] = QObject::tr("Entertainment Programs for 6 to 14"); 00357 categoryDesc[0x53] = QObject::tr("Entertainment Programs for 10 to 16"); 00358 categoryDesc[0x54] = QObject::tr("Informational/Educational"); 00359 categoryDesc[0x55] = QObject::tr("Cartoons/Puppets"); 00360 00361 categoryDesc[0x60] = QObject::tr("Music/Ballet/Dance"); 00362 categoryDesc[0x61] = QObject::tr("Rock/Pop"); 00363 categoryDesc[0x62] = QObject::tr("Classical Music"); 00364 categoryDesc[0x63] = QObject::tr("Folk Music"); 00365 categoryDesc[0x64] = QObject::tr("Jazz"); 00366 categoryDesc[0x65] = QObject::tr("Musical/Opera"); 00367 categoryDesc[0x66] = QObject::tr("Ballet"); 00368 00369 categoryDesc[0x70] = QObject::tr("Arts/Culture"); 00370 categoryDesc[0x71] = QObject::tr("Performing Arts"); 00371 categoryDesc[0x72] = QObject::tr("Fine Arts"); 00372 categoryDesc[0x73] = QObject::tr("Religion"); 00373 categoryDesc[0x74] = QObject::tr("Popular Culture/Traditional Arts"); 00374 categoryDesc[0x75] = QObject::tr("Literature"); 00375 categoryDesc[0x76] = QObject::tr("Film/Cinema"); 00376 categoryDesc[0x77] = QObject::tr("Experimental Film/Video"); 00377 categoryDesc[0x78] = QObject::tr("Broadcasting/Press"); 00378 categoryDesc[0x79] = QObject::tr("New Media"); 00379 categoryDesc[0x7A] = QObject::tr("Arts/Culture Magazines"); 00380 categoryDesc[0x7B] = QObject::tr("Fashion"); 00381 00382 categoryDesc[0x80] = QObject::tr("Social/Policical/Economics"); 00383 categoryDesc[0x81] = QObject::tr("Magazines/Reports/Documentary"); 00384 categoryDesc[0x82] = QObject::tr("Economics/Social Advisory"); 00385 categoryDesc[0x83] = QObject::tr("Remarkable People"); 00386 00387 categoryDesc[0x90] = QObject::tr("Education/Science/Factual"); 00388 categoryDesc[0x91] = QObject::tr("Nature/animals/Environment"); 00389 categoryDesc[0x92] = QObject::tr("Technology/Natural Sciences"); 00390 categoryDesc[0x93] = QObject::tr("Medicine/Physiology/Psychology"); 00391 categoryDesc[0x94] = QObject::tr("Foreign Countries/Expeditions"); 00392 categoryDesc[0x95] = QObject::tr("Social/Spiritual Sciences"); 00393 categoryDesc[0x96] = QObject::tr("Further Education"); 00394 categoryDesc[0x97] = QObject::tr("Languages"); 00395 00396 categoryDesc[0xA0] = QObject::tr("Leisure/Hobbies"); 00397 categoryDesc[0xA1] = QObject::tr("Tourism/Travel"); 00398 categoryDesc[0xA2] = QObject::tr("Handicraft"); 00399 categoryDesc[0xA3] = QObject::tr("Motoring"); 00400 categoryDesc[0xA4] = QObject::tr("Fitness & Health"); 00401 categoryDesc[0xA5] = QObject::tr("Cooking"); 00402 categoryDesc[0xA6] = QObject::tr("Advertizement/Shopping"); 00403 categoryDesc[0xA7] = QObject::tr("Gardening"); 00404 // Special 00405 categoryDesc[0xB0] = QObject::tr("Original Language"); 00406 categoryDesc[0xB1] = QObject::tr("Black & White"); 00407 categoryDesc[0xB2] = QObject::tr("\"Unpublished\" Programs"); 00408 categoryDesc[0xB3] = QObject::tr("Live Broadcast"); 00409 // UK Freeview custom id 00410 categoryDesc[0xF0] = QObject::tr("Drama"); 00411 00412 categoryDescExists = true; 00413 } 00414 00415 QString FrequencyListDescriptor::toString() const 00416 { 00417 QString str = "FrequencyListDescriptor: frequencies: "; 00418 00419 for (uint i = 0; i < FrequencyCount(); i++) 00420 str.append(QString(" %1").arg(FrequencyHz(i))); 00421 00422 return str; 00423 } 00424 00425 QString ServiceDescriptorMapping::toString() const 00426 { 00427 QString str = ""; 00428 00429 if (IsDTV()) 00430 str.append(" (TV)"); 00431 else if (IsDigitalAudio()) 00432 str.append(" (Radio)"); 00433 else if (IsHDTV()) 00434 str.append(" (HDTV)"); 00435 else if (IsTeletext()) 00436 str.append(" (Teletext)"); 00437 else 00438 str.append(QString(" (Unknown %1)").arg(ServiceType(),2,16)); 00439 00440 return str; 00441 } 00442 00443 QString CableDeliverySystemDescriptor::toString() const 00444 { 00445 QString str = QString("CableDeliverySystemDescriptor: "); 00446 00447 str.append(QString("Frequency: %1\n").arg(FrequencyHz())); 00448 str.append(QString(" Mod=%1, SymbR=%2, FECInner=%3, FECOuter=%4") 00449 .arg(ModulationString()) 00450 .arg(SymbolRateHz()) 00451 .arg(FECInnerString()) 00452 .arg(FECOuterString())); 00453 00454 return str; 00455 } 00456 00457 QString SatelliteDeliverySystemDescriptor::toString() const 00458 { 00459 QString str = QString("SatelliteDeliverySystemDescriptor: "); 00460 00461 str.append(QString("Frequency: %1\n").arg(FrequencyHz())); 00462 str.append(QString(" Mod=%1, SymbR=%2, FECInner=%3, Orbit=%4, Pol=%5") 00463 .arg(ModulationString()) 00464 .arg(SymbolRateHz()) 00465 .arg(FECInnerString()) 00466 .arg(OrbitalPositionString()) 00467 .arg(PolarizationString())); 00468 00469 return str; 00470 } 00471 00472 QString TerrestrialDeliverySystemDescriptor::toString() const 00473 { 00474 QString str = QString("TerrestrialDeliverySystemDescriptor: "); 00475 00476 str.append(QString("Frequency: %1\n").arg(FrequencyHz())); 00477 str.append(QString(" BW=%1k, C=%2, HP=%3, LP=%4, GI=%5, TransMode=%6k") 00478 .arg(BandwidthString()) 00479 .arg(ConstellationString()) 00480 .arg(CodeRateHPString()) 00481 .arg(CodeRateLPString()) 00482 .arg(GuardIntervalString()) 00483 .arg(TransmissionModeString())); 00484 00485 return str; 00486 } 00487 00488 QString UKChannelListDescriptor::toString() const 00489 { 00490 QString ret = "UKChannelListDescriptor sid->chan_num: "; 00491 for (uint i = 0; i < ChannelCount(); i++) 00492 { 00493 ret += QString("%1->%2").arg(ServiceID(i)).arg(ChannelNumber(i)); 00494 ret += (i+1<ChannelCount()) ? ", " : ""; 00495 } 00496 return ret; 00497 } 00498 00499 QString CAIdentifierDescriptor::toString(void) const 00500 { 00501 QString ret = QString("CAIdentifierDescriptor "); 00502 for (uint i = 0; i < CASystemCount(); ++i) 00503 { 00504 ret += QString("ca_system_id(0x%1) ") 00505 .arg(CASystemId(i), 0, 16); 00506 } 00507 return ret; 00508 } 00509 00510 QString DataBroadcastDescriptor::toString(void) const 00511 { 00512 QString ret = QString("DataBroadcastDescriptor: " 00513 "data_broadcast_id(%1) " 00514 "component_tag(%1) ") 00515 .arg(DataBroadcastId(), 0, 10) 00516 .arg(DataComponentTag(), 0, 10); 00517 00518 ret += QString("selector(0x "); 00519 for (uint i = 0; i < SelectorLength(); i++) 00520 ret += QString("%1 ").arg(Selector()[i], 0, 16); 00521 ret += ") "; 00522 00523 ret += QString("ISO_639_language_code(%1) ") 00524 .arg(LanguageString()); 00525 00526 ret += QString("text(%1) ") + QString(Text()); 00527 00528 return ret; 00529 } 00530 00531 QString LocalTimeOffsetDescriptor::toString(void) const 00532 { 00533 QString ret = QString("LocalTimeOffsetDescriptor "); 00534 uint count = Count(); 00535 for (uint i = 0; i < count; ++i) 00536 { 00537 ret += QString("country_code(%1) country_region_id(0x%2) " 00538 "local_time_offset_with_polarity(%3) " 00539 "time_of_change(TODO)") 00540 .arg(CountryCodeString(i)) 00541 .arg(CountryRegionId(i), 0, 16) 00542 .arg(LocalTimeOffsetWithPolarity(i)); 00543 // TODO add time of change 00544 } 00545 return ret; 00546 } 00547 00548 QString NVODReferenceDescriptor::toString(void) const 00549 { 00550 QString ret = QString("NVODReferenceDescriptor "); 00551 for (uint i = 0; i < Count(); ++i) 00552 { 00553 ret += QString("transport_stream_id(0x%1) original_network_id(0x%2) " 00554 "service_id(0x%3) ") 00555 .arg(TransportStreamId(i), 0, 16) 00556 .arg(OriginalNetworkId(i), 0, 16) 00557 .arg(ServiceId(i), 0, 16); 00558 } 00559 return ret; 00560 } 00561 00562 QString PartialTransportStreamDescriptor::toString(void) const 00563 { 00564 return QString("PartialTransportStreamDescriptor peak_rate(%1) " 00565 "min_overall_smooth_rate(%2) max_overall_smooth_buf(3)") 00566 .arg(PeakRate()).arg(SmoothRate()).arg(SmoothBuf()); 00567 } 00568 00569 QString AC3Descriptor::toString(void) const 00570 { 00571 QString ret = QString("AC3DescriptorDescriptor "); 00572 if (HasComponentType()) 00573 ret += QString("component_type(%1) ") 00574 .arg(ComponentType(), 0, 10); 00575 if (HasBSID()) 00576 ret += QString("bsid(0x%1) ").arg(BSID(),0,16); 00577 if (HasMainID()) 00578 ret += QString("mainid(0x%1) ").arg(MainID(),0,16); 00579 if (HasASVC()) 00580 ret += QString("asvc(%1) ").arg(ASVC()); 00581 return ret; 00582 } 00583 00584
1.7.6.1