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"