"Fossies" - the Fresh Open Source Software Archive

Member "kaffeine-2.0.18/src/dvb/dvbepg.cpp" (14 May 2019, 35101 Bytes) of package /linux/misc/kaffeine-2.0.18.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "dvbepg.cpp" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 2.0.16_vs_2.0.17.

    1 /*
    2  * dvbepg.cpp
    3  *
    4  * Copyright (C) 2009-2011 Christoph Pfister <christophpfister@gmail.com>
    5  *
    6  * This program is free software; you can redistribute it and/or modify
    7  * it under the terms of the GNU General Public License as published by
    8  * the Free Software Foundation; either version 2 of the License, or
    9  * (at your option) any later version.
   10  *
   11  * This program is distributed in the hope that it will be useful,
   12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
   13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   14  * GNU General Public License for more details.
   15  *
   16  * You should have received a copy of the GNU General Public License along
   17  * with this program; if not, write to the Free Software Foundation, Inc.,
   18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
   19  */
   20 
   21 #include "../log.h"
   22 
   23 #include <QDataStream>
   24 #include <QFile>
   25 #include <QLoggingCategory>
   26 #include <QStandardPaths>
   27 
   28 #include "../ensurenopendingoperation.h"
   29 #include "../iso-codes.h"
   30 #include "dvbdevice.h"
   31 #include "dvbepg.h"
   32 #include "dvbepg_p.h"
   33 #include "dvbmanager.h"
   34 #include "dvbsi.h"
   35 
   36 bool DvbEpgEntry::validate() const
   37 {
   38     if (channel.isValid() && begin.isValid() && (begin.timeSpec() == Qt::UTC) &&
   39         duration.isValid()) {
   40         return true;
   41     }
   42 
   43     return false;
   44 }
   45 
   46 bool DvbEpgEntryId::operator<(const DvbEpgEntryId &other) const
   47 {
   48     if (entry->channel != other.entry->channel) {
   49         return (entry->channel < other.entry->channel);
   50     }
   51 
   52     if (entry->begin != other.entry->begin) {
   53         return (entry->begin < other.entry->begin);
   54     }
   55     return false;
   56 }
   57 
   58 DvbEpgModel::DvbEpgModel(DvbManager *manager_, QObject *parent) : QObject(parent),
   59     manager(manager_), hasPendingOperation(false)
   60 {
   61     currentDateTimeUtc = QDateTime::currentDateTime().toUTC();
   62     startTimer(54000);
   63 
   64     DvbChannelModel *channelModel = manager->getChannelModel();
   65     connect(channelModel, SIGNAL(channelAboutToBeUpdated(DvbSharedChannel)),
   66         this, SLOT(channelAboutToBeUpdated(DvbSharedChannel)));
   67     connect(channelModel, SIGNAL(channelUpdated(DvbSharedChannel)),
   68         this, SLOT(channelUpdated(DvbSharedChannel)));
   69     connect(channelModel, SIGNAL(channelRemoved(DvbSharedChannel)),
   70         this, SLOT(channelRemoved(DvbSharedChannel)));
   71     connect(manager->getRecordingModel(), SIGNAL(recordingRemoved(DvbSharedRecording)),
   72         this, SLOT(recordingRemoved(DvbSharedRecording)));
   73 
   74     // TODO use SQL to store epg data
   75 
   76     QFile file(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/epgdata.dvb"));
   77 
   78     if (!file.open(QIODevice::ReadOnly)) {
   79         qCWarning(logEpg, "Cannot open %s", qPrintable(file.fileName()));
   80         return;
   81     }
   82 
   83     QDataStream stream(&file);
   84     stream.setVersion(QDataStream::Qt_4_4);
   85     DvbRecordingModel *recordingModel = manager->getRecordingModel();
   86     bool hasRecordingKey = true, hasParental = true, hasMultilang = true;
   87     int version;
   88     stream >> version;
   89 
   90     if (version == 0x1ce0eca7) {
   91         hasRecordingKey = false;
   92     } else if (version == 0x79cffd36) {
   93         hasParental = false;
   94     } else if (version == 0x140c37b5) {
   95         hasMultilang = false;
   96     } else if (version != 0x20171112) {
   97         qCWarning(logEpg, "Wrong DB version for: %s", qPrintable(file.fileName()));
   98         return;
   99     }
  100 
  101     while (!stream.atEnd()) {
  102         DvbEpgEntry entry;
  103         QString channelName;
  104         stream >> channelName;
  105         entry.channel = channelModel->findChannelByName(channelName);
  106         stream >> entry.begin;
  107         entry.begin = entry.begin.toUTC();
  108         stream >> entry.duration;
  109 
  110         if (hasMultilang) {
  111             int i, count;
  112 
  113             stream >> count;
  114 
  115             for (i = 0; i < count; i++) {
  116                 QString code;
  117 
  118                 DvbEpgLangEntry langEntry;
  119                 stream >> code;
  120                 stream >> langEntry.title;
  121                 stream >> langEntry.subheading;
  122                 stream >> langEntry.details;
  123 
  124                 entry.langEntry[code] = langEntry;
  125 
  126                 if (!langEntry.title.isEmpty() && !manager->languageCodes.contains(code))
  127                     manager->languageCodes[code] = true;
  128             }
  129 
  130 
  131         } else {
  132             DvbEpgLangEntry langEntry;
  133 
  134             stream >> langEntry.title;
  135             stream >> langEntry.subheading;
  136             stream >> langEntry.details;
  137 
  138             entry.langEntry[FIRST_LANG] = langEntry;
  139         }
  140 
  141         if (hasRecordingKey) {
  142             SqlKey recordingKey;
  143             stream >> recordingKey.sqlKey;
  144 
  145             if (recordingKey.isSqlKeyValid()) {
  146                 entry.recording = recordingModel->findRecordingByKey(recordingKey);
  147             }
  148         }
  149 
  150         if (hasParental) {
  151             unsigned type;
  152 
  153             stream >> type;
  154             stream >> entry.content;
  155             stream >> entry.parental;
  156 
  157             if (type <= DvbEpgEntry::EitLast)
  158                 entry.type = DvbEpgEntry::EitType(type);
  159             else
  160                 entry.type = DvbEpgEntry::EitActualTsSchedule;
  161         }
  162 
  163         if (stream.status() != QDataStream::Ok) {
  164             qCWarning(logEpg, "Corrupt data %s", qPrintable(file.fileName()));
  165             break;
  166         }
  167 
  168         addEntry(entry);
  169     }
  170 }
  171 
  172 DvbEpgModel::~DvbEpgModel()
  173 {
  174     if (hasPendingOperation) {
  175         qCWarning(logEpg, "Illegal recursive call");
  176     }
  177 
  178     if (!dvbEpgFilters.isEmpty() || !atscEpgFilters.isEmpty()) {
  179         qCWarning(logEpg, "filter list not empty");
  180     }
  181 
  182     QFile file(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/epgdata.dvb"));
  183 
  184     if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
  185         qCWarning(logEpg, "Cannot open %s", qPrintable(file.fileName()));
  186         return;
  187     }
  188 
  189     QDataStream stream(&file);
  190     stream.setVersion(QDataStream::Qt_4_4);
  191     int version = 0x20171112;
  192     stream << version;
  193 
  194     foreach (const DvbSharedEpgEntry &entry, entries) {
  195         SqlKey recordingKey;
  196 
  197         if (entry->recording.isValid()) {
  198             recordingKey = *entry->recording;
  199         }
  200 
  201         stream << entry->channel->name;
  202         stream << entry->begin;
  203         stream << entry->duration;
  204 
  205         stream << entry->langEntry.size();
  206 
  207         QHashIterator<QString, DvbEpgLangEntry> i(entry->langEntry);
  208 
  209         while (i.hasNext()) {
  210             i.next();
  211 
  212             stream << i.key();
  213 
  214             DvbEpgLangEntry langEntry = i.value();
  215 
  216             stream << langEntry.title;
  217             stream << langEntry.subheading;
  218             stream << langEntry.details;
  219         }
  220 
  221         stream << recordingKey.sqlKey;
  222         stream << int(entry->type);
  223         stream << entry->content;
  224         stream << entry->parental;
  225     }
  226 }
  227 
  228 QMap<DvbSharedRecording, DvbSharedEpgEntry> DvbEpgModel::getRecordings() const
  229 {
  230     return recordings;
  231 }
  232 
  233 void DvbEpgModel::setRecordings(const QMap<DvbSharedRecording, DvbSharedEpgEntry> map)
  234 {
  235     recordings = map;
  236 }
  237 
  238 QMap<DvbEpgEntryId, DvbSharedEpgEntry> DvbEpgModel::getEntries() const
  239 {
  240     return entries;
  241 }
  242 
  243 QHash<DvbSharedChannel, int> DvbEpgModel::getEpgChannels() const
  244 {
  245     return epgChannels;
  246 }
  247 
  248 QList<DvbSharedEpgEntry> DvbEpgModel::getCurrentNext(const DvbSharedChannel &channel) const
  249 {
  250     QList<DvbSharedEpgEntry> result;
  251     DvbEpgEntry fakeEntry(channel);
  252 
  253     for (ConstIterator it = entries.lowerBound(DvbEpgEntryId(&fakeEntry));
  254          it != entries.constEnd(); ++it) {
  255         const DvbSharedEpgEntry &entry = *it;
  256 
  257         if (entry->channel != channel) {
  258             break;
  259         }
  260 
  261         result.append(entry);
  262 
  263         if (result.size() == 2) {
  264             break;
  265         }
  266     }
  267 
  268     return result;
  269 }
  270 
  271 void DvbEpgModel::Debug(QString text, const DvbSharedEpgEntry &entry)
  272 {
  273     if (!QLoggingCategory::defaultCategory()->isEnabled(QtDebugMsg))
  274         return;
  275 
  276     QDateTime begin = entry->begin.toLocalTime();
  277     QTime end = entry->begin.addSecs(QTime(0, 0, 0).secsTo(entry->duration)).toLocalTime().time();
  278 
  279     qCDebug(logEpg, "event %s: type %d, from %s to %s: %s: %s: %s : %s",
  280         qPrintable(text), entry->type, qPrintable(QLocale().toString(begin, QLocale::ShortFormat)), qPrintable(QLocale().toString(end)),
  281         qPrintable(entry->title()), qPrintable(entry->subheading()), qPrintable(entry->details()), qPrintable(entry->content));
  282 }
  283 
  284 DvbSharedEpgEntry DvbEpgModel::addEntry(const DvbEpgEntry &entry)
  285 {
  286     if (!entry.validate()) {
  287         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");
  288         return DvbSharedEpgEntry();
  289     }
  290 
  291     if (hasPendingOperation) {
  292         qCWarning(logEpg, "Illegal recursive call");
  293         return DvbSharedEpgEntry();
  294     }
  295 
  296     EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation);
  297 
  298     // Check if the event was already recorded
  299     const QDateTime end = entry.begin.addSecs(QTime(0, 0, 0).secsTo(entry.duration));
  300 
  301     // Optimize duplicated register logic by using find, with is O(log n)
  302     Iterator it = entries.find(DvbEpgEntryId(&entry));
  303     while (it != entries.end()) {
  304         const DvbSharedEpgEntry &existingEntry = *it;
  305 
  306         // Don't do anything if the event already exists
  307         if (*existingEntry == entry)
  308             return DvbSharedEpgEntry();
  309 
  310         const QDateTime enEnd = existingEntry->begin.addSecs(QTime(0, 0, 0).secsTo(existingEntry->duration));
  311 
  312         // The logic here was simplified due to performance.
  313         // It won't check anymore if an event has its start time
  314         // switched, as that would require a O(n) loop, with is
  315         // too slow, specially on DVB-S/S2. So, we're letting the QMap
  316         // to use a key with just channel/begin time, identifying
  317         // obsolete entries only if the end time doesn't match.
  318 
  319         // A new event conflicts with an existing one
  320         if (end != enEnd) {
  321             Debug("removed", existingEntry);
  322             it = removeEntry(it);
  323             break;
  324         }
  325         // New event data for the same event
  326         if (existingEntry->details(FIRST_LANG).isEmpty() && !entry.details(FIRST_LANG).isEmpty()) {
  327             emit entryAboutToBeUpdated(existingEntry);
  328 
  329             QHashIterator<QString, DvbEpgLangEntry> i(entry.langEntry);
  330 
  331             while (i.hasNext()) {
  332                 i.next();
  333 
  334                 DvbEpgLangEntry langEntry = i.value();
  335 
  336                 const_cast<DvbEpgEntry *>(existingEntry.constData())->langEntry[i.key()].details = langEntry.details;
  337             }
  338             emit entryUpdated(existingEntry);
  339             Debug("updated", existingEntry);
  340         }
  341         return existingEntry;
  342     }
  343 
  344     if (entry.begin.addSecs(QTime(0, 0, 0).secsTo(entry.duration)) > currentDateTimeUtc) {
  345         DvbSharedEpgEntry existingEntry = entries.value(DvbEpgEntryId(&entry));
  346 
  347         if (existingEntry.isValid()) {
  348             if (existingEntry->details(FIRST_LANG).isEmpty() && !entry.details(FIRST_LANG).isEmpty()) {
  349                 // needed for atsc
  350                 emit entryAboutToBeUpdated(existingEntry);
  351 
  352                 QHashIterator<QString, DvbEpgLangEntry> i(entry.langEntry);
  353 
  354                 while (i.hasNext()) {
  355                     i.next();
  356 
  357                     DvbEpgLangEntry langEntry = i.value();
  358 
  359                     const_cast<DvbEpgEntry *>(existingEntry.constData())->langEntry[i.key()].details = langEntry.details;
  360                 }
  361                 emit entryUpdated(existingEntry);
  362                 Debug("updated2", existingEntry);
  363             }
  364 
  365             return existingEntry;
  366         }
  367 
  368         DvbSharedEpgEntry newEntry(new DvbEpgEntry(entry));
  369         entries.insert(DvbEpgEntryId(newEntry), newEntry);
  370 
  371         if (newEntry->recording.isValid()) {
  372             recordings.insert(newEntry->recording, newEntry);
  373         }
  374 
  375         if (++epgChannels[newEntry->channel] == 1) {
  376             emit epgChannelAdded(newEntry->channel);
  377         }
  378 
  379         emit entryAdded(newEntry);
  380         Debug("new", newEntry);
  381         return newEntry;
  382     }
  383 
  384     return DvbSharedEpgEntry();
  385 }
  386 
  387 void DvbEpgModel::scheduleProgram(const DvbSharedEpgEntry &entry, int extraSecondsBefore,
  388     int extraSecondsAfter, bool checkForRecursion, int priority)
  389 {
  390     if (!entry.isValid() || (entries.value(DvbEpgEntryId(entry)) != entry)) {
  391         qCWarning(logEpg, "Can't schedule program: invalid entry");
  392         return;
  393     }
  394 
  395     if (hasPendingOperation) {
  396         qCWarning(logEpg, "Illegal recursive call");
  397         return;
  398     }
  399 
  400     EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation);
  401     emit entryAboutToBeUpdated(entry);
  402     DvbSharedRecording oldRecording;
  403 
  404     if (!entry->recording.isValid()) {
  405         DvbRecording recording;
  406         recording.priority = priority;
  407         recording.name = entry->title(manager->currentEpgLanguage);
  408         recording.channel = entry->channel;
  409         recording.begin = entry->begin.addSecs(-extraSecondsBefore);
  410         recording.beginEPG = entry->begin;
  411         recording.duration =
  412             entry->duration.addSecs(extraSecondsBefore + extraSecondsAfter);
  413         recording.durationEPG =
  414             entry->duration;
  415         recording.subheading =
  416             entry->subheading(manager->currentEpgLanguage);
  417         recording.details =
  418             entry->details(manager->currentEpgLanguage);
  419         recording.disabled = false;
  420         const_cast<DvbEpgEntry *>(entry.constData())->recording =
  421             manager->getRecordingModel()->addRecording(recording, checkForRecursion);
  422         recordings.insert(entry->recording, entry);
  423     } else {
  424         oldRecording = entry->recording;
  425         recordings.remove(entry->recording);
  426         const_cast<DvbEpgEntry *>(entry.constData())->recording = DvbSharedRecording();
  427     }
  428 
  429     emit entryUpdated(entry);
  430 
  431     if (oldRecording.isValid()) {
  432         // recordingRemoved() will be called
  433         hasPendingOperation = false;
  434         manager->getRecordingModel()->removeRecording(oldRecording);
  435     }
  436 }
  437 
  438 void DvbEpgModel::startEventFilter(DvbDevice *device, const DvbSharedChannel &channel)
  439 {
  440     if (manager->disableEpg())
  441         return;
  442 
  443     switch (channel->transponder.getTransmissionType()) {
  444     case DvbTransponderBase::Invalid:
  445         break;
  446     case DvbTransponderBase::DvbC:
  447     case DvbTransponderBase::DvbS:
  448     case DvbTransponderBase::DvbS2:
  449     case DvbTransponderBase::DvbT:
  450     case DvbTransponderBase::DvbT2:
  451     case DvbTransponderBase::IsdbT:
  452         dvbEpgFilters.append(QExplicitlySharedDataPointer<DvbEpgFilter>(
  453             new DvbEpgFilter(manager, device, channel)));
  454         break;
  455     case DvbTransponderBase::Atsc:
  456         atscEpgFilters.append(QExplicitlySharedDataPointer<AtscEpgFilter>(
  457             new AtscEpgFilter(manager, device, channel)));
  458         break;
  459     }
  460 }
  461 
  462 void DvbEpgModel::stopEventFilter(DvbDevice *device, const DvbSharedChannel &channel)
  463 {
  464     switch (channel->transponder.getTransmissionType()) {
  465     case DvbTransponderBase::Invalid:
  466         break;
  467     case DvbTransponderBase::DvbC:
  468     case DvbTransponderBase::DvbS:
  469     case DvbTransponderBase::DvbS2:
  470     case DvbTransponderBase::DvbT:
  471     case DvbTransponderBase::DvbT2:
  472     case DvbTransponderBase::IsdbT:
  473         for (int i = 0; i < dvbEpgFilters.size(); ++i) {
  474             const DvbEpgFilter *epgFilter = dvbEpgFilters.at(i).constData();
  475 
  476             if ((epgFilter->device == device) &&
  477                 (epgFilter->source == channel->source) &&
  478                 (epgFilter->transponder.corresponds(channel->transponder))) {
  479                 dvbEpgFilters.removeAt(i);
  480                 break;
  481             }
  482         }
  483 
  484         break;
  485     case DvbTransponderBase::Atsc:
  486         for (int i = 0; i < atscEpgFilters.size(); ++i) {
  487             const AtscEpgFilter *epgFilter = atscEpgFilters.at(i).constData();
  488 
  489             if ((epgFilter->device == device) &&
  490                 (epgFilter->source == channel->source) &&
  491                 (epgFilter->transponder.corresponds(channel->transponder))) {
  492                 atscEpgFilters.removeAt(i);
  493                 break;
  494             }
  495         }
  496 
  497         break;
  498     }
  499 }
  500 
  501 void DvbEpgModel::channelAboutToBeUpdated(const DvbSharedChannel &channel)
  502 {
  503     updatingChannel = *channel;
  504 }
  505 
  506 void DvbEpgModel::channelUpdated(const DvbSharedChannel &channel)
  507 {
  508     if (hasPendingOperation) {
  509         qCWarning(logEpg, "Illegal recursive call");
  510         return;
  511     }
  512 
  513     EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation);
  514 
  515     if (DvbChannelId(channel) != DvbChannelId(&updatingChannel)) {
  516         DvbEpgEntry fakeEntry(channel);
  517         Iterator it = entries.lowerBound(DvbEpgEntryId(&fakeEntry));
  518 
  519         while ((ConstIterator(it) != entries.constEnd()) && ((*it)->channel == channel)) {
  520             it = removeEntry(it);
  521         }
  522     }
  523 }
  524 
  525 void DvbEpgModel::channelRemoved(const DvbSharedChannel &channel)
  526 {
  527     if (hasPendingOperation) {
  528         qCWarning(logEpg, "Illegal recursive call");
  529         return;
  530     }
  531 
  532     EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation);
  533     DvbEpgEntry fakeEntry(channel);
  534     Iterator it = entries.lowerBound(DvbEpgEntryId(&fakeEntry));
  535 
  536     while ((ConstIterator(it) != entries.constEnd()) && ((*it)->channel == channel)) {
  537         it = removeEntry(it);
  538     }
  539 }
  540 
  541 void DvbEpgModel::recordingRemoved(const DvbSharedRecording &recording)
  542 {
  543     if (hasPendingOperation) {
  544         qCWarning(logEpg, "Illegal recursive call");
  545         return;
  546     }
  547 
  548     EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation);
  549     DvbSharedEpgEntry entry = recordings.take(recording);
  550 
  551     if (entry.isValid()) {
  552         emit entryAboutToBeUpdated(entry);
  553         const_cast<DvbEpgEntry *>(entry.constData())->recording = DvbSharedRecording();
  554         emit entryUpdated(entry);
  555     }
  556 }
  557 
  558 void DvbEpgModel::timerEvent(QTimerEvent *event)
  559 {
  560     Q_UNUSED(event)
  561 
  562     if (hasPendingOperation) {
  563         qCWarning(logEpg, "Illegal recursive call");
  564         return;
  565     }
  566 
  567     EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation);
  568     currentDateTimeUtc = QDateTime::currentDateTime().toUTC();
  569     Iterator it = entries.begin();
  570 
  571     while (ConstIterator(it) != entries.constEnd()) {
  572         const DvbSharedEpgEntry &entry = *it;
  573 
  574         if (entry->begin.addSecs(QTime(0, 0, 0).secsTo(entry->duration)) > currentDateTimeUtc) {
  575             ++it;
  576         } else {
  577             it = removeEntry(it);
  578         }
  579     }
  580 }
  581 
  582 DvbEpgModel::Iterator DvbEpgModel::removeEntry(Iterator it)
  583 {
  584     const DvbSharedEpgEntry &entry = *it;
  585 
  586     if (entry->recording.isValid()) {
  587         recordings.remove(entry->recording);
  588     }
  589 
  590     if (--epgChannels[entry->channel] == 0) {
  591         epgChannels.remove(entry->channel);
  592         emit epgChannelRemoved(entry->channel);
  593     }
  594 
  595     emit entryRemoved(entry);
  596     return entries.erase(it);
  597 }
  598 
  599 DvbEpgFilter::DvbEpgFilter(DvbManager *manager_, DvbDevice *device_,
  600     const DvbSharedChannel &channel) : device(device_)
  601 {
  602     manager = manager_;
  603     source = channel->source;
  604     transponder = channel->transponder;
  605     device->addSectionFilter(0x12, this);
  606     channelModel = manager->getChannelModel();
  607     epgModel = manager->getEpgModel();
  608 }
  609 
  610 DvbEpgFilter::~DvbEpgFilter()
  611 {
  612     device->removeSectionFilter(0x12, this);
  613 }
  614 
  615 QTime DvbEpgFilter::bcdToTime(int bcd)
  616 {
  617     return QTime(((bcd >> 20) & 0x0f) * 10 + ((bcd >> 16) & 0x0f),
  618         ((bcd >> 12) & 0x0f) * 10 + ((bcd >> 8) & 0x0f),
  619         ((bcd >> 4) & 0x0f) * 10 + (bcd & 0x0f));
  620 }
  621 
  622 static const QByteArray contentStr[16][16] = {
  623     [0] = {},
  624     [1] = {
  625             /* Movie/Drama */
  626         {},
  627         {I18N_NOOP("Detective")},
  628         {I18N_NOOP("Adventure")},
  629         {I18N_NOOP("Science Fiction")},
  630         {I18N_NOOP("Comedy")},
  631         {I18N_NOOP("Soap")},
  632         {I18N_NOOP("Romance")},
  633         {I18N_NOOP("Classical")},
  634         {I18N_NOOP("Adult")},
  635         {I18N_NOOP("User defined")},
  636     },
  637     [2] = {
  638             /* News/Current affairs */
  639         {},
  640         {I18N_NOOP("Weather")},
  641         {I18N_NOOP("Magazine")},
  642         {I18N_NOOP("Documentary")},
  643         {I18N_NOOP("Discussion")},
  644         {I18N_NOOP("User Defined")},
  645     },
  646     [3] = {
  647             /* Show/Game show */
  648         {},
  649         {I18N_NOOP("Quiz")},
  650         {I18N_NOOP("Variety")},
  651         {I18N_NOOP("Talk")},
  652         {I18N_NOOP("User Defined")},
  653     },
  654     [4] = {
  655             /* Sports */
  656         {},
  657         {I18N_NOOP("Events")},
  658         {I18N_NOOP("Magazine")},
  659         {I18N_NOOP("Football")},
  660         {I18N_NOOP("Tennis")},
  661         {I18N_NOOP("Team")},
  662         {I18N_NOOP("Athletics")},
  663         {I18N_NOOP("Motor")},
  664         {I18N_NOOP("Water")},
  665         {I18N_NOOP("Winter")},
  666         {I18N_NOOP("Equestrian")},
  667         {I18N_NOOP("Martial")},
  668         {I18N_NOOP("User Defined")},
  669     },
  670     [5] = {
  671             /* Children's/Youth */
  672         {},
  673         {I18N_NOOP("Preschool")},
  674         {I18N_NOOP("06 to 14")},
  675         {I18N_NOOP("10 to 16")},
  676         {I18N_NOOP("Educational")},
  677         {I18N_NOOP("Cartoons")},
  678         {I18N_NOOP("User Defined")},
  679     },
  680     [6] = {
  681             /* Music/Ballet/Dance */
  682         {},
  683         {I18N_NOOP("Poprock")},
  684         {I18N_NOOP("Classical")},
  685         {I18N_NOOP("Folk")},
  686         {I18N_NOOP("Jazz")},
  687         {I18N_NOOP("Opera")},
  688         {I18N_NOOP("Ballet")},
  689         {I18N_NOOP("User Defined")},
  690     },
  691     [7] = {
  692             /* Arts/Culture */
  693         {},
  694         {I18N_NOOP("Performance")},
  695         {I18N_NOOP("Fine Arts")},
  696         {I18N_NOOP("Religion")},
  697         {I18N_NOOP("Traditional")},
  698         {I18N_NOOP("Literature")},
  699         {I18N_NOOP("Cinema")},
  700         {I18N_NOOP("Experimental")},
  701         {I18N_NOOP("Press")},
  702         {I18N_NOOP("New Media")},
  703         {I18N_NOOP("Magazine")},
  704         {I18N_NOOP("Fashion")},
  705         {I18N_NOOP("User Defined")},
  706     },
  707     [8] = {
  708             /* Social/Political/Economics */
  709         {},
  710         {I18N_NOOP("Magazine")},
  711         {I18N_NOOP("Advisory")},
  712         {I18N_NOOP("People")},
  713         {I18N_NOOP("User Defined")},
  714     },
  715     [9] = {
  716             /* Education/Science/Factual */
  717         {},
  718         {I18N_NOOP("Nature")},
  719         {I18N_NOOP("Technology")},
  720         {I18N_NOOP("Medicine")},
  721         {I18N_NOOP("Foreign")},
  722         {I18N_NOOP("Social")},
  723         {I18N_NOOP("Further")},
  724         {I18N_NOOP("Language")},
  725         {I18N_NOOP("User Defined")},
  726     },
  727     [10] = {
  728             /* Leisure/Hobbies */
  729         {},
  730         {I18N_NOOP("Travel")},
  731         {I18N_NOOP("Handicraft")},
  732         {I18N_NOOP("Motoring")},
  733         {I18N_NOOP("Fitness")},
  734         {I18N_NOOP("Cooking")},
  735         {I18N_NOOP("Shopping")},
  736         {I18N_NOOP("Gardening")},
  737         {I18N_NOOP("User Defined")},
  738     },
  739     [11] = {
  740             /* Special characteristics */
  741         {I18N_NOOP("Original Language")},
  742         {I18N_NOOP("Black and White ")},
  743         {I18N_NOOP("Unpublished")},
  744         {I18N_NOOP("Live")},
  745         {I18N_NOOP("Planostereoscopic")},
  746         {I18N_NOOP("User Defined")},
  747         {I18N_NOOP("User Defined 1")},
  748         {I18N_NOOP("User Defined 2")},
  749         {I18N_NOOP("User Defined 3")},
  750         {I18N_NOOP("User Defined 4")}
  751     }
  752 };
  753 
  754 static const QByteArray nibble1Str[16] = {
  755     [0]  = {I18N_NOOP("Undefined")},
  756     [1]  = {I18N_NOOP("Movie")},
  757     [2]  = {I18N_NOOP("News")},
  758     [3]  = {I18N_NOOP("Show")},
  759     [4]  = {I18N_NOOP("Sports")},
  760     [5]  = {I18N_NOOP("Children")},
  761     [6]  = {I18N_NOOP("Music")},
  762     [7]  = {I18N_NOOP("Culture")},
  763     [8]  = {I18N_NOOP("Social")},
  764     [9]  = {I18N_NOOP("Education")},
  765     [10] = {I18N_NOOP("Leisure")},
  766     [11] = {I18N_NOOP("Special")},
  767     [12] = {I18N_NOOP("Reserved")},
  768     [13] = {I18N_NOOP("Reserved")},
  769     [14] = {I18N_NOOP("Reserved")},
  770     [15] = {I18N_NOOP("User defined")},
  771 };
  772 
  773 static const QByteArray braNibble1Str[16] = {
  774     [0]  = {I18N_NOOP("News")},
  775     [1]  = {I18N_NOOP("Sports")},
  776     [2]  = {I18N_NOOP("Education")},
  777     [3]  = {I18N_NOOP("Soap opera")},
  778     [4]  = {I18N_NOOP("Mini-series")},
  779     [5]  = {I18N_NOOP("Series")},
  780     [6]  = {I18N_NOOP("Variety")},
  781     [7]  = {I18N_NOOP("Reality show")},
  782     [8]  = {I18N_NOOP("Information")},
  783     [9]  = {I18N_NOOP("Comical")},
  784     [10] = {I18N_NOOP("Children")},
  785     [11] = {I18N_NOOP("Erotic")},
  786     [12] = {I18N_NOOP("Movie")},
  787     [13] = {I18N_NOOP("Raffle, television sales, prizing")},
  788     [14] = {I18N_NOOP("Debate/interview")},
  789     [15] = {I18N_NOOP("Other")},
  790 };
  791 
  792 // Using the terms from the English version of NBR 15603-2:2007
  793 // The table omits nibble2="Other", as it is better to show nibble 1
  794 // definition instead.
  795 // when nibble2[x][0] == nibble1[x] and it has no other definition,
  796 // except for "Other", the field will be kept in blank, as the logic
  797 // will fall back to the definition at nibble 1.
  798 static QByteArray braNibble2Str[16][16] = {
  799     [0] = {
  800         {I18N_NOOP("News")},
  801         {I18N_NOOP("Report")},
  802         {I18N_NOOP("Documentary")},
  803         {I18N_NOOP("Biography")},
  804     },
  805     [1] = {},
  806     [2] = {
  807         {I18N_NOOP("Educative")},
  808     },
  809     [3] = {},
  810     [4] = {},
  811     [5] = {},
  812     [6] = {
  813         {I18N_NOOP("Auditorium")},
  814         {I18N_NOOP("Show")},
  815         {I18N_NOOP("Musical")},
  816         {I18N_NOOP("Making of")},
  817         {I18N_NOOP("Feminine")},
  818         {I18N_NOOP("Game show")},
  819     },
  820     [7] = {},
  821     [8] = {
  822         {I18N_NOOP("Cooking")},
  823         {I18N_NOOP("Fashion")},
  824         {I18N_NOOP("Country")},
  825         {I18N_NOOP("Health")},
  826         {I18N_NOOP("Travel")},
  827     },
  828     [9] = {},
  829     [10] = {},
  830     [11] = {},
  831     [12] = {},
  832     [13] = {
  833         {I18N_NOOP("Raffle")},
  834         {I18N_NOOP("Television sales")},
  835         {I18N_NOOP("Prizing")},
  836     },
  837     [14] = {
  838         {I18N_NOOP("Discussion")},
  839         {I18N_NOOP("Interview")},
  840     },
  841     [15] = {
  842         {I18N_NOOP("Adult cartoon")},
  843         {I18N_NOOP("Interactive")},
  844         {I18N_NOOP("Policy")},
  845         {I18N_NOOP("Religion")},
  846     },
  847 };
  848 
  849 QString DvbEpgFilter::getContent(DvbContentDescriptor &descriptor)
  850 {
  851     QString content;
  852 
  853     for (DvbEitContentEntry entry = descriptor.contents(); entry.isValid(); entry.advance()) {
  854         const int nibble1 = entry.contentNibbleLevel1();
  855         const int nibble2 = entry.contentNibbleLevel2();
  856         QByteArray s;
  857 
  858         // FIXME: should do it only for ISDB-Tb (Brazilian variation),
  859         // as the Japanese variation uses the same codes as DVB
  860         if (transponder.getTransmissionType() == DvbTransponderBase::IsdbT) {
  861             s = braNibble2Str[nibble1][nibble2];
  862             if (s == "")
  863                 s = braNibble1Str[nibble1];
  864             if (s != "")
  865                 content += i18n(s) + '\n';
  866         } else {
  867             s = contentStr[nibble1][nibble2];
  868             if (s == "")
  869                 s = nibble1Str[nibble1];
  870             if (s != "")
  871                 content += i18n(s) + '\n';
  872         }
  873     }
  874 
  875     if (content != "") {
  876         // xgettext:no-c-format
  877         return (i18n("Genre: %1", content));
  878     }
  879     return content;
  880 }
  881 
  882 /* As defined at ABNT NBR 15603-2 */
  883 static const QByteArray braRating[] = {
  884     [0] = {I18N_NOOP("reserved")},
  885     [1] = {I18N_NOOP("all audiences")},
  886     [2] = {I18N_NOOP("10 years")},
  887     [3] = {I18N_NOOP("12 years")},
  888     [4] = {I18N_NOOP("14 years")},
  889     [5] = {I18N_NOOP("16 years")},
  890     [6] = {I18N_NOOP("18 years")},
  891 };
  892 
  893 #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
  894 
  895 QString DvbEpgFilter::getParental(DvbParentalRatingDescriptor &descriptor)
  896 {
  897     QString parental;
  898 
  899     for (DvbParentalRatingEntry entry = descriptor.contents(); entry.isValid(); entry.advance()) {
  900         QString code;
  901         code.append(QChar(entry.languageCode1()));
  902         code.append(QChar(entry.languageCode2()));
  903         code.append(QChar(entry.languageCode3()));
  904 
  905         QString country;
  906         IsoCodes::getCountry(code, &country);
  907         if (country.isEmpty())
  908             country = code;
  909 
  910         // Rating from 0x10 to 0xff are broadcaster's specific
  911         if (entry.rating() == 0) {
  912             // xgettext:no-c-format
  913             parental += i18n("Country %1: not rated\n", country);
  914         } else if (entry.rating() < 0x10) {
  915             if (code == "BRA" && transponder.getTransmissionType() == DvbTransponderBase::IsdbT) {
  916                 unsigned int rating = entry.rating();
  917 
  918                 if (rating >= ARRAY_SIZE(braRating))
  919                     rating = 0; // Reserved
  920 
  921                 QString GenStr;
  922                 int genre = entry.rating() >> 4;
  923 
  924                 if (genre & 0x2)
  925                     GenStr = i18n("violence / ");
  926                 if (genre & 0x4)
  927                     GenStr = i18n("sex / ");
  928                 if (genre & 0x1)
  929                     GenStr = i18n("drugs / ");
  930                 if (genre) {
  931                     GenStr.truncate(GenStr.size() - 2);
  932                     GenStr = " (" + GenStr + ')';
  933                 }
  934 
  935                 QString ratingStr = i18n(braRating[entry.rating()]);
  936                 // xgettext:no-c-format
  937                 parental += i18n("Country %1: rating: %2%3\n", country, ratingStr, GenStr);
  938             } else {
  939                 // xgettext:no-c-format
  940                 parental += i18n("Country %1: rating: %2 years.\n", country, entry.rating() + 3);
  941             }
  942         }
  943     }
  944     return parental;
  945 }
  946 
  947 DvbEpgLangEntry *DvbEpgFilter::getLangEntry(DvbEpgEntry &epgEntry,
  948                         int code1, int code2, int code3,
  949                         bool add_code,
  950                         QString *code_)
  951 {
  952     DvbEpgLangEntry *langEntry;
  953     QString code;
  954 
  955     if (!code1 || code1 == 0x20)
  956         code = FIRST_LANG;
  957     else {
  958         code.append(QChar(code1));
  959         code.append(QChar(code2));
  960         code.append(QChar(code3));
  961         code = code.toUpper();
  962     }
  963     if (code_)
  964         code_ = new QString(code);
  965 
  966     if (!epgEntry.langEntry.contains(code)) {
  967         DvbEpgLangEntry e;
  968         epgEntry.langEntry.insert(code, e);
  969         if (add_code) {
  970             if (!manager->languageCodes.contains(code)) {
  971                 manager->languageCodes[code] = true;
  972                 emit epgModel->languageAdded(code);
  973             }
  974         }
  975     }
  976     langEntry = &epgEntry.langEntry[code];
  977 
  978     return langEntry;
  979 }
  980 
  981 
  982 void DvbEpgFilter::processSection(const char *data, int size)
  983 {
  984     unsigned char tableId = data[0];
  985 
  986     if ((tableId < 0x4e) || (tableId > 0x6f)) {
  987         return;
  988     }
  989 
  990     DvbEitSection eitSection(data, size);
  991 
  992     if (!eitSection.isValid()) {
  993         qCDebug(logEpg, "section is invalid");
  994         return;
  995     }
  996 
  997     DvbChannel fakeChannel;
  998     fakeChannel.source = source;
  999     fakeChannel.transponder = transponder;
 1000     fakeChannel.networkId = eitSection.originalNetworkId();
 1001     fakeChannel.transportStreamId = eitSection.transportStreamId();
 1002     fakeChannel.serviceId = eitSection.serviceId();
 1003     DvbSharedChannel channel = channelModel->findChannelById(fakeChannel);
 1004 
 1005     if (!channel.isValid()) {
 1006         fakeChannel.networkId = -1;
 1007         channel = channelModel->findChannelById(fakeChannel);
 1008     }
 1009 
 1010     if (!channel.isValid()) {
 1011         qCDebug(logEpg, "channel invalid");
 1012         return;
 1013     }
 1014 
 1015     if (eitSection.entries().getLength())
 1016         qCDebug(logEpg, "table 0x%02x, extension 0x%04x, session %d/%d, size %d", eitSection.tableId(), eitSection.tableIdExtension(), eitSection.sectionNumber(), eitSection.lastSectionNumber(), eitSection.entries().getLength());
 1017 
 1018     for (DvbEitSectionEntry entry = eitSection.entries(); entry.isValid(); entry.advance()) {
 1019         DvbEpgEntry epgEntry;
 1020         DvbEpgLangEntry *langEntry;
 1021 
 1022         if (tableId == 0x4e)
 1023             epgEntry.type = DvbEpgEntry::EitActualTsPresentFollowing;
 1024         else if (tableId == 0x4f)
 1025             epgEntry.type = DvbEpgEntry::EitOtherTsPresentFollowing;
 1026         else if (tableId < 0x60)
 1027             epgEntry.type = DvbEpgEntry::EitActualTsSchedule;
 1028         else
 1029             epgEntry.type = DvbEpgEntry::EitOtherTsSchedule;
 1030 
 1031         epgEntry.channel = channel;
 1032 
 1033         /*
 1034          * ISDB-T Brazil uses time in UTC-3,
 1035          * as defined by ABNT NBR 15603-2:2007.
 1036          */
 1037         if (channel->transponder.getTransmissionType() == DvbTransponderBase::IsdbT)
 1038             epgEntry.begin = QDateTime(QDate::fromJulianDay(entry.startDate() + 2400001),
 1039                            bcdToTime(entry.startTime()), Qt::OffsetFromUTC, -10800).toUTC();
 1040         else
 1041             epgEntry.begin = QDateTime(QDate::fromJulianDay(entry.startDate() + 2400001),
 1042                            bcdToTime(entry.startTime()), Qt::UTC);
 1043         epgEntry.duration = bcdToTime(entry.duration());
 1044 
 1045         for (DvbDescriptor descriptor = entry.descriptors(); descriptor.isValid();
 1046              descriptor.advance()) {
 1047             switch (descriptor.descriptorTag()) {
 1048             case 0x4d: {
 1049                 DvbShortEventDescriptor eventDescriptor(descriptor);
 1050 
 1051                 if (!eventDescriptor.isValid()) {
 1052                     break;
 1053                 }
 1054 
 1055                 langEntry = getLangEntry(epgEntry,
 1056                          eventDescriptor.languageCode1(),
 1057                          eventDescriptor.languageCode2(),
 1058                          eventDescriptor.languageCode3());
 1059 
 1060                 langEntry->title += eventDescriptor.eventName();
 1061                 langEntry->subheading += eventDescriptor.text();
 1062 
 1063                 break;
 1064                 }
 1065             case 0x4e: {
 1066                 DvbExtendedEventDescriptor eventDescriptor(descriptor);
 1067 
 1068                 if (!eventDescriptor.isValid()) {
 1069                     break;
 1070                 }
 1071 
 1072                 langEntry = getLangEntry(epgEntry,
 1073                          eventDescriptor.languageCode1(),
 1074                          eventDescriptor.languageCode2(),
 1075                          eventDescriptor.languageCode3());
 1076                 langEntry->details += eventDescriptor.text();
 1077                 break;
 1078                 }
 1079             case 0x54: {
 1080                 DvbContentDescriptor eventDescriptor(descriptor);
 1081 
 1082                 if (!eventDescriptor.isValid()) {
 1083                     break;
 1084                 }
 1085 
 1086                 epgEntry.content += getContent(eventDescriptor);
 1087                 break;
 1088                 }
 1089             case 0x55: {
 1090                 DvbParentalRatingDescriptor eventDescriptor(descriptor);
 1091 
 1092                 if (!eventDescriptor.isValid()) {
 1093                     break;
 1094                 }
 1095 
 1096                 epgEntry.parental += getParental(eventDescriptor);
 1097                 break;
 1098                 }
 1099             }
 1100         }
 1101 
 1102         epgModel->addEntry(epgEntry);
 1103     }
 1104 }
 1105 
 1106 void AtscEpgMgtFilter::processSection(const char *data, int size)
 1107 {
 1108     epgFilter->processMgtSection(data, size);
 1109 }
 1110 
 1111 void AtscEpgEitFilter::processSection(const char *data, int size)
 1112 {
 1113     epgFilter->processEitSection(data, size);
 1114 }
 1115 
 1116 void AtscEpgEttFilter::processSection(const char *data, int size)
 1117 {
 1118     epgFilter->processEttSection(data, size);
 1119 }
 1120 
 1121 AtscEpgFilter::AtscEpgFilter(DvbManager *manager, DvbDevice *device_,
 1122     const DvbSharedChannel &channel) : device(device_), mgtFilter(this), eitFilter(this),
 1123     ettFilter(this)
 1124 {
 1125     source = channel->source;
 1126     transponder = channel->transponder;
 1127     device->addSectionFilter(0x1ffb, &mgtFilter);
 1128     channelModel = manager->getChannelModel();
 1129     epgModel = manager->getEpgModel();
 1130 }
 1131 
 1132 AtscEpgFilter::~AtscEpgFilter()
 1133 {
 1134     foreach (int pid, eitPids) {
 1135         device->removeSectionFilter(pid, &eitFilter);
 1136     }
 1137 
 1138     foreach (int pid, ettPids) {
 1139         device->removeSectionFilter(pid, &ettFilter);
 1140     }
 1141 
 1142     device->removeSectionFilter(0x1ffb, &mgtFilter);
 1143 }
 1144 
 1145 void AtscEpgFilter::processMgtSection(const char *data, int size)
 1146 {
 1147     unsigned char tableId = data[0];
 1148 
 1149     if (tableId != 0xc7) {
 1150         return;
 1151     }
 1152 
 1153     AtscMgtSection mgtSection(data, size);
 1154 
 1155     if (!mgtSection.isValid()) {
 1156         return;
 1157     }
 1158 
 1159     int entryCount = mgtSection.entryCount();
 1160     QList<int> newEitPids;
 1161     QList<int> newEttPids;
 1162 
 1163     AtscMgtSectionEntry entry = mgtSection.entries();
 1164     for (int i = 0; i < entryCount; i++) {
 1165         if (!entry.isValid())
 1166             break;
 1167 
 1168         int tableType = entry.tableType();
 1169 
 1170         if ((tableType >= 0x0100) && (tableType <= 0x017f)) {
 1171             int pid = entry.pid();
 1172             int index = (qLowerBound(newEitPids, pid) - newEitPids.constBegin());
 1173 
 1174             if ((index >= newEitPids.size()) || (newEitPids.at(index) != pid)) {
 1175                 newEitPids.insert(index, pid);
 1176             }
 1177         }
 1178 
 1179         if ((tableType >= 0x0200) && (tableType <= 0x027f)) {
 1180             int pid = entry.pid();
 1181             int index = (qLowerBound(newEttPids, pid) - newEttPids.constBegin());
 1182 
 1183             if ((index >= newEttPids.size()) || (newEttPids.at(index) != pid)) {
 1184                 newEttPids.insert(index, pid);
 1185             }
 1186         }
 1187         if (i < entryCount - 1)
 1188             entry.advance();
 1189     }
 1190 
 1191     for (int i = 0; i < eitPids.size(); ++i) {
 1192         int pid = eitPids.at(i);
 1193         int index = (qBinaryFind(newEitPids, pid) - newEitPids.constBegin());
 1194 
 1195         if (index < newEitPids.size()) {
 1196             newEitPids.removeAt(index);
 1197         } else {
 1198             device->removeSectionFilter(pid, &eitFilter);
 1199             eitPids.removeAt(i);
 1200             --i;
 1201         }
 1202     }
 1203 
 1204     for (int i = 0; i < ettPids.size(); ++i) {
 1205         int pid = ettPids.at(i);
 1206         int index = (qBinaryFind(newEttPids, pid) - newEttPids.constBegin());
 1207 
 1208         if (index < newEttPids.size()) {
 1209             newEttPids.removeAt(index);
 1210         } else {
 1211             device->removeSectionFilter(pid, &ettFilter);
 1212             ettPids.removeAt(i);
 1213             --i;
 1214         }
 1215     }
 1216 
 1217     for (int i = 0; i < newEitPids.size(); ++i) {
 1218         int pid = newEitPids.at(i);
 1219         eitPids.append(pid);
 1220         device->addSectionFilter(pid, &eitFilter);
 1221     }
 1222 
 1223     for (int i = 0; i < newEttPids.size(); ++i) {
 1224         int pid = newEttPids.at(i);
 1225         ettPids.append(pid);
 1226         device->addSectionFilter(pid, &ettFilter);
 1227     }
 1228 }
 1229 
 1230 void AtscEpgFilter::processEitSection(const char *data, int size)
 1231 {
 1232     unsigned char tableId = data[0];
 1233 
 1234     if (tableId != 0xcb) {
 1235         return;
 1236     }
 1237 
 1238     AtscEitSection eitSection(data, size);
 1239 
 1240     if (!eitSection.isValid()) {
 1241         qCDebug(logEpg, "section is invalid");
 1242         return;
 1243     }
 1244 
 1245     DvbChannel fakeChannel;
 1246     fakeChannel.source = source;
 1247     fakeChannel.transponder = transponder;
 1248     fakeChannel.networkId = eitSection.sourceId();
 1249     DvbSharedChannel channel = channelModel->findChannelById(fakeChannel);
 1250 
 1251     if (!channel.isValid()) {
 1252         qCDebug(logEpg, "channel is invalid");
 1253         return;
 1254     }
 1255 
 1256     qCDebug(logEpg, "Processing EIT section with size %d", size);
 1257 
 1258     int entryCount = eitSection.entryCount();
 1259     // 1980-01-06T000000 minus 15 secs (= UTC - GPS in 2011)
 1260     QDateTime baseDateTime = QDateTime(QDate(1980, 1, 5), QTime(23, 59, 45), Qt::UTC);
 1261 
 1262     AtscEitSectionEntry eitEntry = eitSection.entries();
 1263     for (int i = 0; i < entryCount; i++) {
 1264         if (!eitEntry.isValid())
 1265             break;
 1266         DvbEpgEntry epgEntry;
 1267         epgEntry.channel = channel;
 1268         epgEntry.begin = baseDateTime.addSecs(eitEntry.startTime());
 1269         epgEntry.duration = QTime(0, 0, 0).addSecs(eitEntry.duration());
 1270 
 1271 
 1272         DvbEpgLangEntry *langEntry;
 1273 
 1274         /* Should be similar to DvbEpgFilter::getLangEntry */
 1275         if (!epgEntry.langEntry.contains(FIRST_LANG)) {
 1276             DvbEpgLangEntry e;
 1277             epgEntry.langEntry.insert(FIRST_LANG, e);
 1278         }
 1279         langEntry = &epgEntry.langEntry[FIRST_LANG];
 1280 
 1281         langEntry->title = eitEntry.title();
 1282 
 1283         quint32 id = ((quint32(fakeChannel.networkId) << 16) | quint32(eitEntry.eventId()));
 1284         DvbSharedEpgEntry entry = epgEntries.value(id);
 1285 
 1286         entry = epgModel->addEntry(epgEntry);
 1287         epgEntries.insert(id, entry);
 1288         if ( i < entryCount -1)
 1289             eitEntry.advance();
 1290     }
 1291 }
 1292 
 1293 void AtscEpgFilter::processEttSection(const char *data, int size)
 1294 {
 1295     unsigned char tableId = data[0];
 1296 
 1297     if (tableId != 0xcc) {
 1298         return;
 1299     }
 1300 
 1301     AtscEttSection ettSection(data, size);
 1302 
 1303     if (!ettSection.isValid() || (ettSection.messageType() != 0x02)) {
 1304         return;
 1305     }
 1306 
 1307     quint32 id = ((quint32(ettSection.sourceId()) << 16) | quint32(ettSection.eventId()));
 1308     DvbSharedEpgEntry entry = epgEntries.value(id);
 1309 
 1310     if (entry.isValid()) {
 1311         QString details = ettSection.text();
 1312 
 1313         if (entry->details() != details) {
 1314             DvbEpgEntry modifiedEntry = *entry;
 1315 
 1316             DvbEpgLangEntry *langEntry;
 1317 
 1318             if (modifiedEntry.langEntry.contains(FIRST_LANG))
 1319                 langEntry = &modifiedEntry.langEntry[FIRST_LANG];
 1320             else
 1321                 langEntry = new(DvbEpgLangEntry);
 1322 
 1323             langEntry->details = details;
 1324             entry = epgModel->addEntry(modifiedEntry);
 1325             epgEntries.insert(id, entry);
 1326         }
 1327     }
 1328 }