File indexing completed on 2024-05-12 04:52:14
0001 /* 0002 * dvbepg.cpp 0003 * 0004 * Copyright (C) 2009-2011 Christoph Pfister <christophpfister@gmail.com> 0005 * 0006 * This program is free software; you can redistribute it and/or modify 0007 * it under the terms of the GNU General Public License as published by 0008 * the Free Software Foundation; either version 2 of the License, or 0009 * (at your option) any later version. 0010 * 0011 * This program is distributed in the hope that it will be useful, 0012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0014 * GNU General Public License for more details. 0015 * 0016 * You should have received a copy of the GNU General Public License along 0017 * with this program; if not, write to the Free Software Foundation, Inc., 0018 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 0019 */ 0020 0021 #include "../log.h" 0022 0023 #include <QDataStream> 0024 #include <QFile> 0025 #include <QLoggingCategory> 0026 #include <QStandardPaths> 0027 0028 #include "../ensurenopendingoperation.h" 0029 #include "../iso-codes.h" 0030 #include "dvbdevice.h" 0031 #include "dvbepg.h" 0032 #include "dvbepg_p.h" 0033 #include "dvbmanager.h" 0034 #include "dvbsi.h" 0035 0036 bool DvbEpgEntry::validate() const 0037 { 0038 if (channel.isValid() && begin.isValid() && (begin.timeSpec() == Qt::UTC) && 0039 duration.isValid()) { 0040 return true; 0041 } 0042 0043 return false; 0044 } 0045 0046 bool DvbEpgEntryId::operator<(const DvbEpgEntryId &other) const 0047 { 0048 if (entry->channel != other.entry->channel) { 0049 return (entry->channel < other.entry->channel); 0050 } 0051 0052 if (entry->begin != other.entry->begin) { 0053 return (entry->begin < other.entry->begin); 0054 } 0055 return false; 0056 } 0057 0058 DvbEpgModel::DvbEpgModel(DvbManager *manager_, QObject *parent) : QObject(parent), 0059 manager(manager_), hasPendingOperation(false) 0060 { 0061 currentDateTimeUtc = QDateTime::currentDateTime().toUTC(); 0062 startTimer(54000); 0063 0064 DvbChannelModel *channelModel = manager->getChannelModel(); 0065 connect(channelModel, SIGNAL(channelAboutToBeUpdated(DvbSharedChannel)), 0066 this, SLOT(channelAboutToBeUpdated(DvbSharedChannel))); 0067 connect(channelModel, SIGNAL(channelUpdated(DvbSharedChannel)), 0068 this, SLOT(channelUpdated(DvbSharedChannel))); 0069 connect(channelModel, SIGNAL(channelRemoved(DvbSharedChannel)), 0070 this, SLOT(channelRemoved(DvbSharedChannel))); 0071 connect(manager->getRecordingModel(), SIGNAL(recordingRemoved(DvbSharedRecording)), 0072 this, SLOT(recordingRemoved(DvbSharedRecording))); 0073 0074 // TODO use SQL to store epg data 0075 0076 QFile file(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/epgdata.dvb")); 0077 0078 if (!file.open(QIODevice::ReadOnly)) { 0079 qCWarning(logEpg, "Cannot open %s", qPrintable(file.fileName())); 0080 return; 0081 } 0082 0083 QDataStream stream(&file); 0084 stream.setVersion(QDataStream::Qt_4_4); 0085 DvbRecordingModel *recordingModel = manager->getRecordingModel(); 0086 bool hasRecordingKey = true, hasParental = true, hasMultilang = true; 0087 int version; 0088 stream >> version; 0089 0090 if (version == 0x1ce0eca7) { 0091 hasRecordingKey = false; 0092 } else if (version == 0x79cffd36) { 0093 hasParental = false; 0094 } else if (version == 0x140c37b5) { 0095 hasMultilang = false; 0096 } else if (version != 0x20171112) { 0097 qCWarning(logEpg, "Wrong DB version for: %s", qPrintable(file.fileName())); 0098 return; 0099 } 0100 0101 while (!stream.atEnd()) { 0102 DvbEpgEntry entry; 0103 QString channelName; 0104 stream >> channelName; 0105 entry.channel = channelModel->findChannelByName(channelName); 0106 stream >> entry.begin; 0107 entry.begin = entry.begin.toUTC(); 0108 stream >> entry.duration; 0109 0110 if (hasMultilang) { 0111 int i, count; 0112 0113 stream >> count; 0114 0115 for (i = 0; i < count; i++) { 0116 QString code; 0117 0118 DvbEpgLangEntry langEntry; 0119 stream >> code; 0120 stream >> langEntry.title; 0121 stream >> langEntry.subheading; 0122 stream >> langEntry.details; 0123 0124 entry.langEntry[code] = langEntry; 0125 0126 if (!langEntry.title.isEmpty() && !manager->languageCodes.contains(code)) 0127 manager->languageCodes[code] = true; 0128 } 0129 0130 0131 } else { 0132 DvbEpgLangEntry langEntry; 0133 0134 stream >> langEntry.title; 0135 stream >> langEntry.subheading; 0136 stream >> langEntry.details; 0137 0138 entry.langEntry[FIRST_LANG] = langEntry; 0139 } 0140 0141 if (hasRecordingKey) { 0142 SqlKey recordingKey; 0143 stream >> recordingKey.sqlKey; 0144 0145 if (recordingKey.isSqlKeyValid()) { 0146 entry.recording = recordingModel->findRecordingByKey(recordingKey); 0147 } 0148 } 0149 0150 if (hasParental) { 0151 unsigned type; 0152 0153 stream >> type; 0154 stream >> entry.content; 0155 stream >> entry.parental; 0156 0157 if (type <= DvbEpgEntry::EitLast) 0158 entry.type = DvbEpgEntry::EitType(type); 0159 else 0160 entry.type = DvbEpgEntry::EitActualTsSchedule; 0161 } 0162 0163 if (stream.status() != QDataStream::Ok) { 0164 qCWarning(logEpg, "Corrupt data %s", qPrintable(file.fileName())); 0165 break; 0166 } 0167 0168 addEntry(entry); 0169 } 0170 } 0171 0172 DvbEpgModel::~DvbEpgModel() 0173 { 0174 if (hasPendingOperation) { 0175 qCWarning(logEpg, "Illegal recursive call"); 0176 } 0177 0178 if (!dvbEpgFilters.isEmpty() || !atscEpgFilters.isEmpty()) { 0179 qCWarning(logEpg, "filter list not empty"); 0180 } 0181 0182 QFile file(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/epgdata.dvb")); 0183 0184 if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { 0185 qCWarning(logEpg, "Cannot open %s", qPrintable(file.fileName())); 0186 return; 0187 } 0188 0189 QDataStream stream(&file); 0190 stream.setVersion(QDataStream::Qt_4_4); 0191 int version = 0x20171112; 0192 stream << version; 0193 0194 foreach (const DvbSharedEpgEntry &entry, entries) { 0195 SqlKey recordingKey; 0196 0197 if (entry->recording.isValid()) { 0198 recordingKey = *entry->recording; 0199 } 0200 0201 stream << entry->channel->name; 0202 stream << entry->begin; 0203 stream << entry->duration; 0204 0205 stream << entry->langEntry.size(); 0206 0207 QHashIterator<QString, DvbEpgLangEntry> i(entry->langEntry); 0208 0209 while (i.hasNext()) { 0210 i.next(); 0211 0212 stream << i.key(); 0213 0214 DvbEpgLangEntry langEntry = i.value(); 0215 0216 stream << langEntry.title; 0217 stream << langEntry.subheading; 0218 stream << langEntry.details; 0219 } 0220 0221 stream << recordingKey.sqlKey; 0222 stream << int(entry->type); 0223 stream << entry->content; 0224 stream << entry->parental; 0225 } 0226 } 0227 0228 QMap<DvbSharedRecording, DvbSharedEpgEntry> DvbEpgModel::getRecordings() const 0229 { 0230 return recordings; 0231 } 0232 0233 void DvbEpgModel::setRecordings(const QMap<DvbSharedRecording, DvbSharedEpgEntry> map) 0234 { 0235 recordings = map; 0236 } 0237 0238 QMap<DvbEpgEntryId, DvbSharedEpgEntry> DvbEpgModel::getEntries() const 0239 { 0240 return entries; 0241 } 0242 0243 QHash<DvbSharedChannel, int> DvbEpgModel::getEpgChannels() const 0244 { 0245 return epgChannels; 0246 } 0247 0248 QList<DvbSharedEpgEntry> DvbEpgModel::getCurrentNext(const DvbSharedChannel &channel) const 0249 { 0250 QList<DvbSharedEpgEntry> result; 0251 DvbEpgEntry fakeEntry(channel); 0252 0253 for (ConstIterator it = entries.lowerBound(DvbEpgEntryId(&fakeEntry)); 0254 it != entries.constEnd(); ++it) { 0255 const DvbSharedEpgEntry &entry = *it; 0256 0257 if (entry->channel != channel) { 0258 break; 0259 } 0260 0261 result.append(entry); 0262 0263 if (result.size() == 2) { 0264 break; 0265 } 0266 } 0267 0268 return result; 0269 } 0270 0271 void DvbEpgModel::Debug(QString text, const DvbSharedEpgEntry &entry) 0272 { 0273 if (!QLoggingCategory::defaultCategory()->isEnabled(QtDebugMsg)) 0274 return; 0275 0276 QDateTime begin = entry->begin.toLocalTime(); 0277 QTime end = entry->begin.addSecs(QTime(0, 0, 0).secsTo(entry->duration)).toLocalTime().time(); 0278 0279 qCDebug(logEpg, "event %s: type %d, from %s to %s: %s: %s: %s : %s", 0280 qPrintable(text), entry->type, qPrintable(QLocale().toString(begin, QLocale::ShortFormat)), qPrintable(QLocale().toString(end)), 0281 qPrintable(entry->title()), qPrintable(entry->subheading()), qPrintable(entry->details()), qPrintable(entry->content)); 0282 } 0283 0284 DvbSharedEpgEntry DvbEpgModel::addEntry(const DvbEpgEntry &entry) 0285 { 0286 if (!entry.validate()) { 0287 qCWarning(logEpg, "Invalid entry: channel is %s, begin is %s, duration is %s", entry.channel.isValid() ? "valid" : "invalid", entry.begin.isValid() ? "valid" : "invalid", entry.duration.isValid() ? "valid" : "invalid"); 0288 return DvbSharedEpgEntry(); 0289 } 0290 0291 if (hasPendingOperation) { 0292 qCWarning(logEpg, "Illegal recursive call"); 0293 return DvbSharedEpgEntry(); 0294 } 0295 0296 EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation); 0297 0298 // Check if the event was already recorded 0299 const QDateTime end = entry.begin.addSecs(QTime(0, 0, 0).secsTo(entry.duration)); 0300 0301 // Optimize duplicated register logic by using find, with is O(log n) 0302 Iterator it = entries.find(DvbEpgEntryId(&entry)); 0303 while (it != entries.end()) { 0304 const DvbSharedEpgEntry &existingEntry = *it; 0305 0306 // Don't do anything if the event already exists 0307 if (*existingEntry == entry) 0308 return DvbSharedEpgEntry(); 0309 0310 const QDateTime enEnd = existingEntry->begin.addSecs(QTime(0, 0, 0).secsTo(existingEntry->duration)); 0311 0312 // The logic here was simplified due to performance. 0313 // It won't check anymore if an event has its start time 0314 // switched, as that would require a O(n) loop, with is 0315 // too slow, specially on DVB-S/S2. So, we're letting the QMap 0316 // to use a key with just channel/begin time, identifying 0317 // obsolete entries only if the end time doesn't match. 0318 0319 // A new event conflicts with an existing one 0320 if (end != enEnd) { 0321 Debug("removed", existingEntry); 0322 it = removeEntry(it); 0323 break; 0324 } 0325 // New event data for the same event 0326 if (existingEntry->details(FIRST_LANG).isEmpty() && !entry.details(FIRST_LANG).isEmpty()) { 0327 emit entryAboutToBeUpdated(existingEntry); 0328 0329 QHashIterator<QString, DvbEpgLangEntry> i(entry.langEntry); 0330 0331 while (i.hasNext()) { 0332 i.next(); 0333 0334 DvbEpgLangEntry langEntry = i.value(); 0335 0336 const_cast<DvbEpgEntry *>(existingEntry.constData())->langEntry[i.key()].details = langEntry.details; 0337 } 0338 emit entryUpdated(existingEntry); 0339 Debug("updated", existingEntry); 0340 } 0341 return existingEntry; 0342 } 0343 0344 if (entry.begin.addSecs(QTime(0, 0, 0).secsTo(entry.duration)) > currentDateTimeUtc) { 0345 DvbSharedEpgEntry existingEntry = entries.value(DvbEpgEntryId(&entry)); 0346 0347 if (existingEntry.isValid()) { 0348 if (existingEntry->details(FIRST_LANG).isEmpty() && !entry.details(FIRST_LANG).isEmpty()) { 0349 // needed for atsc 0350 emit entryAboutToBeUpdated(existingEntry); 0351 0352 QHashIterator<QString, DvbEpgLangEntry> i(entry.langEntry); 0353 0354 while (i.hasNext()) { 0355 i.next(); 0356 0357 DvbEpgLangEntry langEntry = i.value(); 0358 0359 const_cast<DvbEpgEntry *>(existingEntry.constData())->langEntry[i.key()].details = langEntry.details; 0360 } 0361 emit entryUpdated(existingEntry); 0362 Debug("updated2", existingEntry); 0363 } 0364 0365 return existingEntry; 0366 } 0367 0368 DvbSharedEpgEntry newEntry(new DvbEpgEntry(entry)); 0369 entries.insert(DvbEpgEntryId(newEntry), newEntry); 0370 0371 if (newEntry->recording.isValid()) { 0372 recordings.insert(newEntry->recording, newEntry); 0373 } 0374 0375 if (++epgChannels[newEntry->channel] == 1) { 0376 emit epgChannelAdded(newEntry->channel); 0377 } 0378 0379 emit entryAdded(newEntry); 0380 Debug("new", newEntry); 0381 return newEntry; 0382 } 0383 0384 return DvbSharedEpgEntry(); 0385 } 0386 0387 void DvbEpgModel::scheduleProgram(const DvbSharedEpgEntry &entry, int extraSecondsBefore, 0388 int extraSecondsAfter, bool checkForRecursion, int priority) 0389 { 0390 if (!entry.isValid() || (entries.value(DvbEpgEntryId(entry)) != entry)) { 0391 qCWarning(logEpg, "Can't schedule program: invalid entry"); 0392 return; 0393 } 0394 0395 if (hasPendingOperation) { 0396 qCWarning(logEpg, "Illegal recursive call"); 0397 return; 0398 } 0399 0400 EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation); 0401 emit entryAboutToBeUpdated(entry); 0402 DvbSharedRecording oldRecording; 0403 0404 if (!entry->recording.isValid()) { 0405 DvbRecording recording; 0406 recording.priority = priority; 0407 recording.name = entry->title(manager->currentEpgLanguage); 0408 recording.channel = entry->channel; 0409 recording.begin = entry->begin.addSecs(-extraSecondsBefore); 0410 recording.beginEPG = entry->begin; 0411 recording.duration = 0412 entry->duration.addSecs(extraSecondsBefore + extraSecondsAfter); 0413 recording.durationEPG = 0414 entry->duration; 0415 recording.subheading = 0416 entry->subheading(manager->currentEpgLanguage); 0417 recording.details = 0418 entry->details(manager->currentEpgLanguage); 0419 recording.disabled = false; 0420 const_cast<DvbEpgEntry *>(entry.constData())->recording = 0421 manager->getRecordingModel()->addRecording(recording, checkForRecursion); 0422 recordings.insert(entry->recording, entry); 0423 } else { 0424 oldRecording = entry->recording; 0425 recordings.remove(entry->recording); 0426 const_cast<DvbEpgEntry *>(entry.constData())->recording = DvbSharedRecording(); 0427 } 0428 0429 emit entryUpdated(entry); 0430 0431 if (oldRecording.isValid()) { 0432 // recordingRemoved() will be called 0433 hasPendingOperation = false; 0434 manager->getRecordingModel()->removeRecording(oldRecording); 0435 } 0436 } 0437 0438 void DvbEpgModel::startEventFilter(DvbDevice *device, const DvbSharedChannel &channel) 0439 { 0440 if (manager->disableEpg()) 0441 return; 0442 0443 switch (channel->transponder.getTransmissionType()) { 0444 case DvbTransponderBase::Invalid: 0445 break; 0446 case DvbTransponderBase::DvbC: 0447 case DvbTransponderBase::DvbS: 0448 case DvbTransponderBase::DvbS2: 0449 case DvbTransponderBase::DvbT: 0450 case DvbTransponderBase::DvbT2: 0451 case DvbTransponderBase::IsdbT: 0452 dvbEpgFilters.append(QExplicitlySharedDataPointer<DvbEpgFilter>( 0453 new DvbEpgFilter(manager, device, channel))); 0454 break; 0455 case DvbTransponderBase::Atsc: 0456 atscEpgFilters.append(QExplicitlySharedDataPointer<AtscEpgFilter>( 0457 new AtscEpgFilter(manager, device, channel))); 0458 break; 0459 } 0460 } 0461 0462 void DvbEpgModel::stopEventFilter(DvbDevice *device, const DvbSharedChannel &channel) 0463 { 0464 switch (channel->transponder.getTransmissionType()) { 0465 case DvbTransponderBase::Invalid: 0466 break; 0467 case DvbTransponderBase::DvbC: 0468 case DvbTransponderBase::DvbS: 0469 case DvbTransponderBase::DvbS2: 0470 case DvbTransponderBase::DvbT: 0471 case DvbTransponderBase::DvbT2: 0472 case DvbTransponderBase::IsdbT: 0473 for (int i = 0; i < dvbEpgFilters.size(); ++i) { 0474 const DvbEpgFilter *epgFilter = dvbEpgFilters.at(i).constData(); 0475 0476 if ((epgFilter->device == device) && 0477 (epgFilter->source == channel->source) && 0478 (epgFilter->transponder.corresponds(channel->transponder))) { 0479 dvbEpgFilters.removeAt(i); 0480 break; 0481 } 0482 } 0483 0484 break; 0485 case DvbTransponderBase::Atsc: 0486 for (int i = 0; i < atscEpgFilters.size(); ++i) { 0487 const AtscEpgFilter *epgFilter = atscEpgFilters.at(i).constData(); 0488 0489 if ((epgFilter->device == device) && 0490 (epgFilter->source == channel->source) && 0491 (epgFilter->transponder.corresponds(channel->transponder))) { 0492 atscEpgFilters.removeAt(i); 0493 break; 0494 } 0495 } 0496 0497 break; 0498 } 0499 } 0500 0501 void DvbEpgModel::channelAboutToBeUpdated(const DvbSharedChannel &channel) 0502 { 0503 updatingChannel = *channel; 0504 } 0505 0506 void DvbEpgModel::channelUpdated(const DvbSharedChannel &channel) 0507 { 0508 if (hasPendingOperation) { 0509 qCWarning(logEpg, "Illegal recursive call"); 0510 return; 0511 } 0512 0513 EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation); 0514 0515 if (DvbChannelId(channel) != DvbChannelId(&updatingChannel)) { 0516 DvbEpgEntry fakeEntry(channel); 0517 Iterator it = entries.lowerBound(DvbEpgEntryId(&fakeEntry)); 0518 0519 while ((ConstIterator(it) != entries.constEnd()) && ((*it)->channel == channel)) { 0520 it = removeEntry(it); 0521 } 0522 } 0523 } 0524 0525 void DvbEpgModel::channelRemoved(const DvbSharedChannel &channel) 0526 { 0527 if (hasPendingOperation) { 0528 qCWarning(logEpg, "Illegal recursive call"); 0529 return; 0530 } 0531 0532 EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation); 0533 DvbEpgEntry fakeEntry(channel); 0534 Iterator it = entries.lowerBound(DvbEpgEntryId(&fakeEntry)); 0535 0536 while ((ConstIterator(it) != entries.constEnd()) && ((*it)->channel == channel)) { 0537 it = removeEntry(it); 0538 } 0539 } 0540 0541 void DvbEpgModel::recordingRemoved(const DvbSharedRecording &recording) 0542 { 0543 if (hasPendingOperation) { 0544 qCWarning(logEpg, "Illegal recursive call"); 0545 return; 0546 } 0547 0548 EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation); 0549 DvbSharedEpgEntry entry = recordings.take(recording); 0550 0551 if (entry.isValid()) { 0552 emit entryAboutToBeUpdated(entry); 0553 const_cast<DvbEpgEntry *>(entry.constData())->recording = DvbSharedRecording(); 0554 emit entryUpdated(entry); 0555 } 0556 } 0557 0558 void DvbEpgModel::timerEvent(QTimerEvent *event) 0559 { 0560 Q_UNUSED(event) 0561 0562 if (hasPendingOperation) { 0563 qCWarning(logEpg, "Illegal recursive call"); 0564 return; 0565 } 0566 0567 EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation); 0568 currentDateTimeUtc = QDateTime::currentDateTime().toUTC(); 0569 Iterator it = entries.begin(); 0570 0571 while (ConstIterator(it) != entries.constEnd()) { 0572 const DvbSharedEpgEntry &entry = *it; 0573 0574 if (entry->begin.addSecs(QTime(0, 0, 0).secsTo(entry->duration)) > currentDateTimeUtc) { 0575 ++it; 0576 } else { 0577 it = removeEntry(it); 0578 } 0579 } 0580 } 0581 0582 DvbEpgModel::Iterator DvbEpgModel::removeEntry(Iterator it) 0583 { 0584 const DvbSharedEpgEntry &entry = *it; 0585 0586 if (entry->recording.isValid()) { 0587 recordings.remove(entry->recording); 0588 } 0589 0590 if (--epgChannels[entry->channel] == 0) { 0591 epgChannels.remove(entry->channel); 0592 emit epgChannelRemoved(entry->channel); 0593 } 0594 0595 emit entryRemoved(entry); 0596 return entries.erase(it); 0597 } 0598 0599 DvbEpgFilter::DvbEpgFilter(DvbManager *manager_, DvbDevice *device_, 0600 const DvbSharedChannel &channel) : device(device_) 0601 { 0602 manager = manager_; 0603 source = channel->source; 0604 transponder = channel->transponder; 0605 device->addSectionFilter(0x12, this); 0606 channelModel = manager->getChannelModel(); 0607 epgModel = manager->getEpgModel(); 0608 } 0609 0610 DvbEpgFilter::~DvbEpgFilter() 0611 { 0612 device->removeSectionFilter(0x12, this); 0613 } 0614 0615 QTime DvbEpgFilter::bcdToTime(int bcd) 0616 { 0617 int h = ((bcd >> 20) & 0x0f) * 10 + ((bcd >> 16) & 0x0f); 0618 int m = ((bcd >> 12) & 0x0f) * 10 + ((bcd >> 8) & 0x0f); 0619 int s = ((bcd >> 4) & 0x0f) * 10 + (bcd & 0x0f); 0620 int t = s + m * 60 + h * 3600; 0621 static bool first = true; 0622 0623 // Just in case seconds or minutes would be greater than 60 0624 s = t % 60; 0625 m = (t / 60) % 60; 0626 h = t / 3600; 0627 0628 // Maximum value supported by QTime() 0629 if (h > 23) { 0630 if (first) { 0631 qCWarning(logEpg, "Warning: some EIT event last longer than 24 hours. Truncating them."); 0632 first = false; 0633 } 0634 h = 23; 0635 m = 59; 0636 s = 59; 0637 } 0638 0639 return QTime(h, m, s); 0640 } 0641 0642 static const QByteArray contentStr[16][16] = { 0643 [0] = {}, 0644 [1] = { 0645 /* Movie/Drama */ 0646 {}, 0647 {I18N_NOOP("Detective")}, 0648 {I18N_NOOP("Adventure")}, 0649 {I18N_NOOP("Science Fiction")}, 0650 {I18N_NOOP("Comedy")}, 0651 {I18N_NOOP("Soap")}, 0652 {I18N_NOOP("Romance")}, 0653 {I18N_NOOP("Classical")}, 0654 {I18N_NOOP("Adult")}, 0655 {I18N_NOOP("User defined")}, 0656 }, 0657 [2] = { 0658 /* News/Current affairs */ 0659 {}, 0660 {I18N_NOOP("Weather")}, 0661 {I18N_NOOP("Magazine")}, 0662 {I18N_NOOP("Documentary")}, 0663 {I18N_NOOP("Discussion")}, 0664 {I18N_NOOP("User Defined")}, 0665 }, 0666 [3] = { 0667 /* Show/Game show */ 0668 {}, 0669 {I18N_NOOP("Quiz")}, 0670 {I18N_NOOP("Variety")}, 0671 {I18N_NOOP("Talk")}, 0672 {I18N_NOOP("User Defined")}, 0673 }, 0674 [4] = { 0675 /* Sports */ 0676 {}, 0677 {I18N_NOOP("Events")}, 0678 {I18N_NOOP("Magazine")}, 0679 {I18N_NOOP("Football")}, 0680 {I18N_NOOP("Tennis")}, 0681 {I18N_NOOP("Team")}, 0682 {I18N_NOOP("Athletics")}, 0683 {I18N_NOOP("Motor")}, 0684 {I18N_NOOP("Water")}, 0685 {I18N_NOOP("Winter")}, 0686 {I18N_NOOP("Equestrian")}, 0687 {I18N_NOOP("Martial")}, 0688 {I18N_NOOP("User Defined")}, 0689 }, 0690 [5] = { 0691 /* Children's/Youth */ 0692 {}, 0693 {I18N_NOOP("Preschool")}, 0694 {I18N_NOOP("06 to 14")}, 0695 {I18N_NOOP("10 to 16")}, 0696 {I18N_NOOP("Educational")}, 0697 {I18N_NOOP("Cartoons")}, 0698 {I18N_NOOP("User Defined")}, 0699 }, 0700 [6] = { 0701 /* Music/Ballet/Dance */ 0702 {}, 0703 {I18N_NOOP("Poprock")}, 0704 {I18N_NOOP("Classical")}, 0705 {I18N_NOOP("Folk")}, 0706 {I18N_NOOP("Jazz")}, 0707 {I18N_NOOP("Opera")}, 0708 {I18N_NOOP("Ballet")}, 0709 {I18N_NOOP("User Defined")}, 0710 }, 0711 [7] = { 0712 /* Arts/Culture */ 0713 {}, 0714 {I18N_NOOP("Performance")}, 0715 {I18N_NOOP("Fine Arts")}, 0716 {I18N_NOOP("Religion")}, 0717 {I18N_NOOP("Traditional")}, 0718 {I18N_NOOP("Literature")}, 0719 {I18N_NOOP("Cinema")}, 0720 {I18N_NOOP("Experimental")}, 0721 {I18N_NOOP("Press")}, 0722 {I18N_NOOP("New Media")}, 0723 {I18N_NOOP("Magazine")}, 0724 {I18N_NOOP("Fashion")}, 0725 {I18N_NOOP("User Defined")}, 0726 }, 0727 [8] = { 0728 /* Social/Political/Economics */ 0729 {}, 0730 {I18N_NOOP("Magazine")}, 0731 {I18N_NOOP("Advisory")}, 0732 {I18N_NOOP("People")}, 0733 {I18N_NOOP("User Defined")}, 0734 }, 0735 [9] = { 0736 /* Education/Science/Factual */ 0737 {}, 0738 {I18N_NOOP("Nature")}, 0739 {I18N_NOOP("Technology")}, 0740 {I18N_NOOP("Medicine")}, 0741 {I18N_NOOP("Foreign")}, 0742 {I18N_NOOP("Social")}, 0743 {I18N_NOOP("Further")}, 0744 {I18N_NOOP("Language")}, 0745 {I18N_NOOP("User Defined")}, 0746 }, 0747 [10] = { 0748 /* Leisure/Hobbies */ 0749 {}, 0750 {I18N_NOOP("Travel")}, 0751 {I18N_NOOP("Handicraft")}, 0752 {I18N_NOOP("Motoring")}, 0753 {I18N_NOOP("Fitness")}, 0754 {I18N_NOOP("Cooking")}, 0755 {I18N_NOOP("Shopping")}, 0756 {I18N_NOOP("Gardening")}, 0757 {I18N_NOOP("User Defined")}, 0758 }, 0759 [11] = { 0760 /* Special characteristics */ 0761 {I18N_NOOP("Original Language")}, 0762 {I18N_NOOP("Black and White ")}, 0763 {I18N_NOOP("Unpublished")}, 0764 {I18N_NOOP("Live")}, 0765 {I18N_NOOP("Planostereoscopic")}, 0766 {I18N_NOOP("User Defined")}, 0767 {I18N_NOOP("User Defined 1")}, 0768 {I18N_NOOP("User Defined 2")}, 0769 {I18N_NOOP("User Defined 3")}, 0770 {I18N_NOOP("User Defined 4")} 0771 } 0772 }; 0773 0774 static const QByteArray nibble1Str[16] = { 0775 [0] = {I18N_NOOP("Undefined")}, 0776 [1] = {I18N_NOOP("Movie")}, 0777 [2] = {I18N_NOOP("News")}, 0778 [3] = {I18N_NOOP("Show")}, 0779 [4] = {I18N_NOOP("Sports")}, 0780 [5] = {I18N_NOOP("Children")}, 0781 [6] = {I18N_NOOP("Music")}, 0782 [7] = {I18N_NOOP("Culture")}, 0783 [8] = {I18N_NOOP("Social")}, 0784 [9] = {I18N_NOOP("Education")}, 0785 [10] = {I18N_NOOP("Leisure")}, 0786 [11] = {I18N_NOOP("Special")}, 0787 [12] = {I18N_NOOP("Reserved")}, 0788 [13] = {I18N_NOOP("Reserved")}, 0789 [14] = {I18N_NOOP("Reserved")}, 0790 [15] = {I18N_NOOP("User defined")}, 0791 }; 0792 0793 static const QByteArray braNibble1Str[16] = { 0794 [0] = {I18N_NOOP("News")}, 0795 [1] = {I18N_NOOP("Sports")}, 0796 [2] = {I18N_NOOP("Education")}, 0797 [3] = {I18N_NOOP("Soap opera")}, 0798 [4] = {I18N_NOOP("Mini-series")}, 0799 [5] = {I18N_NOOP("Series")}, 0800 [6] = {I18N_NOOP("Variety")}, 0801 [7] = {I18N_NOOP("Reality show")}, 0802 [8] = {I18N_NOOP("Information")}, 0803 [9] = {I18N_NOOP("Comical")}, 0804 [10] = {I18N_NOOP("Children")}, 0805 [11] = {I18N_NOOP("Erotic")}, 0806 [12] = {I18N_NOOP("Movie")}, 0807 [13] = {I18N_NOOP("Raffle, television sales, prizing")}, 0808 [14] = {I18N_NOOP("Debate/interview")}, 0809 [15] = {I18N_NOOP("Other")}, 0810 }; 0811 0812 // Using the terms from the English version of NBR 15603-2:2007 0813 // The table omits nibble2="Other", as it is better to show nibble 1 0814 // definition instead. 0815 // when nibble2[x][0] == nibble1[x] and it has no other definition, 0816 // except for "Other", the field will be kept in blank, as the logic 0817 // will fall back to the definition at nibble 1. 0818 static QByteArray braNibble2Str[16][16] = { 0819 [0] = { 0820 {I18N_NOOP("News")}, 0821 {I18N_NOOP("Report")}, 0822 {I18N_NOOP("Documentary")}, 0823 {I18N_NOOP("Biography")}, 0824 }, 0825 [1] = {}, 0826 [2] = { 0827 {I18N_NOOP("Educative")}, 0828 }, 0829 [3] = {}, 0830 [4] = {}, 0831 [5] = {}, 0832 [6] = { 0833 {I18N_NOOP("Auditorium")}, 0834 {I18N_NOOP("Show")}, 0835 {I18N_NOOP("Musical")}, 0836 {I18N_NOOP("Making of")}, 0837 {I18N_NOOP("Feminine")}, 0838 {I18N_NOOP("Game show")}, 0839 }, 0840 [7] = {}, 0841 [8] = { 0842 {I18N_NOOP("Cooking")}, 0843 {I18N_NOOP("Fashion")}, 0844 {I18N_NOOP("Country")}, 0845 {I18N_NOOP("Health")}, 0846 {I18N_NOOP("Travel")}, 0847 }, 0848 [9] = {}, 0849 [10] = {}, 0850 [11] = {}, 0851 [12] = {}, 0852 [13] = { 0853 {I18N_NOOP("Raffle")}, 0854 {I18N_NOOP("Television sales")}, 0855 {I18N_NOOP("Prizing")}, 0856 }, 0857 [14] = { 0858 {I18N_NOOP("Discussion")}, 0859 {I18N_NOOP("Interview")}, 0860 }, 0861 [15] = { 0862 {I18N_NOOP("Adult cartoon")}, 0863 {I18N_NOOP("Interactive")}, 0864 {I18N_NOOP("Policy")}, 0865 {I18N_NOOP("Religion")}, 0866 }, 0867 }; 0868 0869 QString DvbEpgFilter::getContent(DvbContentDescriptor &descriptor) 0870 { 0871 QString content; 0872 0873 for (DvbEitContentEntry entry = descriptor.contents(); entry.isValid(); entry.advance()) { 0874 const int nibble1 = entry.contentNibbleLevel1(); 0875 const int nibble2 = entry.contentNibbleLevel2(); 0876 QByteArray s; 0877 0878 // FIXME: should do it only for ISDB-Tb (Brazilian variation), 0879 // as the Japanese variation uses the same codes as DVB 0880 if (transponder.getTransmissionType() == DvbTransponderBase::IsdbT) { 0881 s = braNibble2Str[nibble1][nibble2]; 0882 if (s.isEmpty()) 0883 s = braNibble1Str[nibble1]; 0884 if (!s.isEmpty()) 0885 content += i18n(s) + '\n'; 0886 } else { 0887 s = contentStr[nibble1][nibble2]; 0888 if (s.isEmpty()) 0889 s = nibble1Str[nibble1]; 0890 if (!s.isEmpty()) 0891 content += i18n(s) + '\n'; 0892 } 0893 } 0894 0895 if (!content.isEmpty()) { 0896 // xgettext:no-c-format 0897 return (i18n("Genre: %1", content)); 0898 } 0899 return content; 0900 } 0901 0902 /* As defined at ABNT NBR 15603-2 */ 0903 static const QByteArray braRating[] = { 0904 [0] = {I18N_NOOP("reserved")}, 0905 [1] = {I18N_NOOP("all audiences")}, 0906 [2] = {I18N_NOOP("10 years")}, 0907 [3] = {I18N_NOOP("12 years")}, 0908 [4] = {I18N_NOOP("14 years")}, 0909 [5] = {I18N_NOOP("16 years")}, 0910 [6] = {I18N_NOOP("18 years")}, 0911 }; 0912 0913 #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) 0914 0915 QString DvbEpgFilter::getParental(DvbParentalRatingDescriptor &descriptor) 0916 { 0917 QString parental; 0918 0919 for (DvbParentalRatingEntry entry = descriptor.contents(); entry.isValid(); entry.advance()) { 0920 QString code; 0921 code.append(QChar(entry.languageCode1())); 0922 code.append(QChar(entry.languageCode2())); 0923 code.append(QChar(entry.languageCode3())); 0924 0925 QString country; 0926 IsoCodes::getCountry(code, &country); 0927 if (country.isEmpty()) 0928 country = code; 0929 0930 // Rating from 0x10 to 0xff are broadcaster's specific 0931 if (entry.rating() == 0) { 0932 // xgettext:no-c-format 0933 parental += i18n("Country %1: not rated\n", country); 0934 } else if (entry.rating() < 0x10) { 0935 if (code == "BRA" && transponder.getTransmissionType() == DvbTransponderBase::IsdbT) { 0936 unsigned int rating = entry.rating(); 0937 0938 if (rating >= ARRAY_SIZE(braRating)) 0939 rating = 0; // Reserved 0940 0941 QString GenStr; 0942 int genre = entry.rating() >> 4; 0943 0944 if (genre & 0x2) 0945 GenStr = i18n("violence / "); 0946 if (genre & 0x4) 0947 GenStr = i18n("sex / "); 0948 if (genre & 0x1) 0949 GenStr = i18n("drugs / "); 0950 if (genre) { 0951 GenStr.truncate(GenStr.size() - 2); 0952 GenStr = " (" + GenStr + ')'; 0953 } 0954 0955 QString ratingStr = i18n(braRating[entry.rating()]); 0956 // xgettext:no-c-format 0957 parental += i18n("Country %1: rating: %2%3\n", country, ratingStr, GenStr); 0958 } else { 0959 // xgettext:no-c-format 0960 parental += i18n("Country %1: rating: %2 years.\n", country, entry.rating() + 3); 0961 } 0962 } 0963 } 0964 return parental; 0965 } 0966 0967 DvbEpgLangEntry *DvbEpgFilter::getLangEntry(DvbEpgEntry &epgEntry, 0968 int code1, int code2, int code3, 0969 bool add_code, 0970 QString *code_) 0971 { 0972 DvbEpgLangEntry *langEntry; 0973 QString code; 0974 0975 if (!code1 || code1 == 0x20) 0976 code = FIRST_LANG; 0977 else { 0978 code.append(QChar(code1)); 0979 code.append(QChar(code2)); 0980 code.append(QChar(code3)); 0981 code = code.toUpper(); 0982 } 0983 if (code_) 0984 code_ = new QString(code); 0985 0986 if (!epgEntry.langEntry.contains(code)) { 0987 DvbEpgLangEntry e; 0988 epgEntry.langEntry.insert(code, e); 0989 if (add_code) { 0990 if (!manager->languageCodes.contains(code)) { 0991 manager->languageCodes[code] = true; 0992 emit epgModel->languageAdded(code); 0993 } 0994 } 0995 } 0996 langEntry = &epgEntry.langEntry[code]; 0997 0998 return langEntry; 0999 } 1000 1001 1002 void DvbEpgFilter::processSection(const char *data, int size) 1003 { 1004 unsigned char tableId = data[0]; 1005 1006 if ((tableId < 0x4e) || (tableId > 0x6f)) { 1007 return; 1008 } 1009 1010 DvbEitSection eitSection(data, size); 1011 1012 if (!eitSection.isValid()) { 1013 qCDebug(logEpg, "section is invalid"); 1014 return; 1015 } 1016 1017 DvbChannel fakeChannel; 1018 fakeChannel.source = source; 1019 fakeChannel.transponder = transponder; 1020 fakeChannel.networkId = eitSection.originalNetworkId(); 1021 fakeChannel.transportStreamId = eitSection.transportStreamId(); 1022 fakeChannel.serviceId = eitSection.serviceId(); 1023 DvbSharedChannel channel = channelModel->findChannelById(fakeChannel); 1024 1025 if (!channel.isValid()) { 1026 fakeChannel.networkId = -1; 1027 channel = channelModel->findChannelById(fakeChannel); 1028 } 1029 1030 if (!channel.isValid()) { 1031 qCDebug(logEpg, "channel invalid"); 1032 return; 1033 } 1034 1035 if (eitSection.entries().getLength()) 1036 qCDebug(logEpg, "table 0x%02x, extension 0x%04x, session %d/%d, size %d", eitSection.tableId(), eitSection.tableIdExtension(), eitSection.sectionNumber(), eitSection.lastSectionNumber(), eitSection.entries().getLength()); 1037 1038 for (DvbEitSectionEntry entry = eitSection.entries(); entry.isValid(); entry.advance()) { 1039 DvbEpgEntry epgEntry; 1040 DvbEpgLangEntry *langEntry; 1041 1042 if (tableId == 0x4e) 1043 epgEntry.type = DvbEpgEntry::EitActualTsPresentFollowing; 1044 else if (tableId == 0x4f) 1045 epgEntry.type = DvbEpgEntry::EitOtherTsPresentFollowing; 1046 else if (tableId < 0x60) 1047 epgEntry.type = DvbEpgEntry::EitActualTsSchedule; 1048 else 1049 epgEntry.type = DvbEpgEntry::EitOtherTsSchedule; 1050 1051 epgEntry.channel = channel; 1052 1053 /* 1054 * ISDB-T Brazil uses time in UTC-3, 1055 * as defined by ABNT NBR 15603-2:2007. 1056 */ 1057 if (channel->transponder.getTransmissionType() == DvbTransponderBase::IsdbT) 1058 epgEntry.begin = QDateTime(QDate::fromJulianDay(entry.startDate() + 2400001), 1059 bcdToTime(entry.startTime()), Qt::OffsetFromUTC, -10800).toUTC(); 1060 else 1061 epgEntry.begin = QDateTime(QDate::fromJulianDay(entry.startDate() + 2400001), 1062 bcdToTime(entry.startTime()), Qt::UTC); 1063 epgEntry.duration = bcdToTime(entry.duration()); 1064 1065 for (DvbDescriptor descriptor = entry.descriptors(); descriptor.isValid(); 1066 descriptor.advance()) { 1067 switch (descriptor.descriptorTag()) { 1068 case 0x4d: { 1069 DvbShortEventDescriptor eventDescriptor(descriptor); 1070 1071 if (!eventDescriptor.isValid()) { 1072 break; 1073 } 1074 1075 langEntry = getLangEntry(epgEntry, 1076 eventDescriptor.languageCode1(), 1077 eventDescriptor.languageCode2(), 1078 eventDescriptor.languageCode3()); 1079 1080 langEntry->title += eventDescriptor.eventName(); 1081 langEntry->subheading += eventDescriptor.text(); 1082 1083 break; 1084 } 1085 case 0x4e: { 1086 DvbExtendedEventDescriptor eventDescriptor(descriptor); 1087 1088 if (!eventDescriptor.isValid()) { 1089 break; 1090 } 1091 1092 langEntry = getLangEntry(epgEntry, 1093 eventDescriptor.languageCode1(), 1094 eventDescriptor.languageCode2(), 1095 eventDescriptor.languageCode3()); 1096 langEntry->details += eventDescriptor.text(); 1097 break; 1098 } 1099 case 0x54: { 1100 DvbContentDescriptor eventDescriptor(descriptor); 1101 1102 if (!eventDescriptor.isValid()) { 1103 break; 1104 } 1105 1106 epgEntry.content += getContent(eventDescriptor); 1107 break; 1108 } 1109 case 0x55: { 1110 DvbParentalRatingDescriptor eventDescriptor(descriptor); 1111 1112 if (!eventDescriptor.isValid()) { 1113 break; 1114 } 1115 1116 epgEntry.parental += getParental(eventDescriptor); 1117 break; 1118 } 1119 } 1120 } 1121 1122 epgModel->addEntry(epgEntry); 1123 } 1124 } 1125 1126 void AtscEpgMgtFilter::processSection(const char *data, int size) 1127 { 1128 epgFilter->processMgtSection(data, size); 1129 } 1130 1131 void AtscEpgEitFilter::processSection(const char *data, int size) 1132 { 1133 epgFilter->processEitSection(data, size); 1134 } 1135 1136 void AtscEpgEttFilter::processSection(const char *data, int size) 1137 { 1138 epgFilter->processEttSection(data, size); 1139 } 1140 1141 AtscEpgFilter::AtscEpgFilter(DvbManager *manager, DvbDevice *device_, 1142 const DvbSharedChannel &channel) : device(device_), mgtFilter(this), eitFilter(this), 1143 ettFilter(this) 1144 { 1145 source = channel->source; 1146 transponder = channel->transponder; 1147 device->addSectionFilter(0x1ffb, &mgtFilter); 1148 channelModel = manager->getChannelModel(); 1149 epgModel = manager->getEpgModel(); 1150 } 1151 1152 AtscEpgFilter::~AtscEpgFilter() 1153 { 1154 foreach (int pid, eitPids) { 1155 device->removeSectionFilter(pid, &eitFilter); 1156 } 1157 1158 foreach (int pid, ettPids) { 1159 device->removeSectionFilter(pid, &ettFilter); 1160 } 1161 1162 device->removeSectionFilter(0x1ffb, &mgtFilter); 1163 } 1164 1165 void AtscEpgFilter::processMgtSection(const char *data, int size) 1166 { 1167 unsigned char tableId = data[0]; 1168 1169 if (tableId != 0xc7) { 1170 return; 1171 } 1172 1173 AtscMgtSection mgtSection(data, size); 1174 1175 if (!mgtSection.isValid()) { 1176 return; 1177 } 1178 1179 int entryCount = mgtSection.entryCount(); 1180 QList<int> newEitPids; 1181 QList<int> newEttPids; 1182 1183 AtscMgtSectionEntry entry = mgtSection.entries(); 1184 for (int i = 0; i < entryCount; i++) { 1185 if (!entry.isValid()) 1186 break; 1187 1188 int tableType = entry.tableType(); 1189 1190 if ((tableType >= 0x0100) && (tableType <= 0x017f)) { 1191 int pid = entry.pid(); 1192 int index = (std::lower_bound(newEitPids.constBegin(), newEitPids.constEnd(), pid) - newEitPids.constBegin()); 1193 1194 if ((index >= newEitPids.size()) || (newEitPids.at(index) != pid)) { 1195 newEitPids.insert(index, pid); 1196 } 1197 } 1198 1199 if ((tableType >= 0x0200) && (tableType <= 0x027f)) { 1200 int pid = entry.pid(); 1201 int index = (std::lower_bound(newEttPids.constBegin(), newEttPids.constEnd(), pid) - newEttPids.constBegin()); 1202 1203 if ((index >= newEttPids.size()) || (newEttPids.at(index) != pid)) { 1204 newEttPids.insert(index, pid); 1205 } 1206 } 1207 if (i < entryCount - 1) 1208 entry.advance(); 1209 } 1210 1211 for (int i = 0; i < eitPids.size(); ++i) { 1212 int pid = eitPids.at(i); 1213 int index = (std::lower_bound(newEitPids.constBegin(), newEitPids.constEnd(), pid) - newEitPids.constBegin()); 1214 1215 if (index < newEitPids.size()) { 1216 newEitPids.removeAt(index); 1217 } else { 1218 device->removeSectionFilter(pid, &eitFilter); 1219 eitPids.removeAt(i); 1220 --i; 1221 } 1222 } 1223 1224 for (int i = 0; i < ettPids.size(); ++i) { 1225 int pid = ettPids.at(i); 1226 int index = (std::lower_bound(newEttPids.constBegin(), newEttPids.constEnd(), pid) - newEttPids.constBegin()); 1227 1228 if (index < newEttPids.size()) { 1229 newEttPids.removeAt(index); 1230 } else { 1231 device->removeSectionFilter(pid, &ettFilter); 1232 ettPids.removeAt(i); 1233 --i; 1234 } 1235 } 1236 1237 for (int i = 0; i < newEitPids.size(); ++i) { 1238 int pid = newEitPids.at(i); 1239 eitPids.append(pid); 1240 device->addSectionFilter(pid, &eitFilter); 1241 } 1242 1243 for (int i = 0; i < newEttPids.size(); ++i) { 1244 int pid = newEttPids.at(i); 1245 ettPids.append(pid); 1246 device->addSectionFilter(pid, &ettFilter); 1247 } 1248 } 1249 1250 void AtscEpgFilter::processEitSection(const char *data, int size) 1251 { 1252 unsigned char tableId = data[0]; 1253 1254 if (tableId != 0xcb) { 1255 return; 1256 } 1257 1258 AtscEitSection eitSection(data, size); 1259 1260 if (!eitSection.isValid()) { 1261 qCDebug(logEpg, "section is invalid"); 1262 return; 1263 } 1264 1265 DvbChannel fakeChannel; 1266 fakeChannel.source = source; 1267 fakeChannel.transponder = transponder; 1268 fakeChannel.networkId = eitSection.sourceId(); 1269 DvbSharedChannel channel = channelModel->findChannelById(fakeChannel); 1270 1271 if (!channel.isValid()) { 1272 qCDebug(logEpg, "channel is invalid"); 1273 return; 1274 } 1275 1276 qCDebug(logEpg, "Processing EIT section with size %d", size); 1277 1278 int entryCount = eitSection.entryCount(); 1279 // 1980-01-06T000000 minus 15 secs (= UTC - GPS in 2011) 1280 QDateTime baseDateTime = QDateTime(QDate(1980, 1, 5), QTime(23, 59, 45), Qt::UTC); 1281 1282 AtscEitSectionEntry eitEntry = eitSection.entries(); 1283 for (int i = 0; i < entryCount; i++) { 1284 if (!eitEntry.isValid()) 1285 break; 1286 DvbEpgEntry epgEntry; 1287 epgEntry.channel = channel; 1288 epgEntry.begin = baseDateTime.addSecs(eitEntry.startTime()); 1289 epgEntry.duration = QTime(0, 0, 0).addSecs(eitEntry.duration()); 1290 1291 1292 DvbEpgLangEntry *langEntry; 1293 1294 /* Should be similar to DvbEpgFilter::getLangEntry */ 1295 if (!epgEntry.langEntry.contains(FIRST_LANG)) { 1296 DvbEpgLangEntry e; 1297 epgEntry.langEntry.insert(FIRST_LANG, e); 1298 } 1299 langEntry = &epgEntry.langEntry[FIRST_LANG]; 1300 1301 langEntry->title = eitEntry.title(); 1302 1303 quint32 id = ((quint32(fakeChannel.networkId) << 16) | quint32(eitEntry.eventId())); 1304 DvbSharedEpgEntry entry = epgEntries.value(id); 1305 1306 entry = epgModel->addEntry(epgEntry); 1307 epgEntries.insert(id, entry); 1308 if ( i < entryCount -1) 1309 eitEntry.advance(); 1310 } 1311 } 1312 1313 void AtscEpgFilter::processEttSection(const char *data, int size) 1314 { 1315 unsigned char tableId = data[0]; 1316 1317 if (tableId != 0xcc) { 1318 return; 1319 } 1320 1321 AtscEttSection ettSection(data, size); 1322 1323 if (!ettSection.isValid() || (ettSection.messageType() != 0x02)) { 1324 return; 1325 } 1326 1327 quint32 id = ((quint32(ettSection.sourceId()) << 16) | quint32(ettSection.eventId())); 1328 DvbSharedEpgEntry entry = epgEntries.value(id); 1329 1330 if (entry.isValid()) { 1331 QString details = ettSection.text(); 1332 1333 if (entry->details() != details) { 1334 DvbEpgEntry modifiedEntry = *entry; 1335 1336 DvbEpgLangEntry *langEntry; 1337 1338 if (modifiedEntry.langEntry.contains(FIRST_LANG)) 1339 langEntry = &modifiedEntry.langEntry[FIRST_LANG]; 1340 else 1341 langEntry = new(DvbEpgLangEntry); 1342 1343 langEntry->details = details; 1344 entry = epgModel->addEntry(modifiedEntry); 1345 epgEntries.insert(id, entry); 1346 } 1347 } 1348 } 1349 1350 #include "moc_dvbepg.cpp"