diff --git a/src/dvb/dvbepg.cpp b/src/dvb/dvbepg.cpp index b501218..ee41479 100644 --- a/src/dvb/dvbepg.cpp +++ b/src/dvb/dvbepg.cpp @@ -1,1233 +1,1313 @@ /* * dvbepg.cpp * * Copyright (C) 2009-2011 Christoph Pfister * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "../log.h" #include #include #include #include #include "../ensurenopendingoperation.h" #include "dvbdevice.h" #include "dvbepg.h" #include "dvbepg_p.h" #include "dvbmanager.h" #include "dvbsi.h" bool DvbEpgEntry::validate() const { if (channel.isValid() && begin.isValid() && (begin.timeSpec() == Qt::UTC) && duration.isValid()) { return true; } return false; } bool DvbEpgEntryId::operator<(const DvbEpgEntryId &other) const { if (entry->channel != other.entry->channel) { return (entry->channel < other.entry->channel); } if (entry->begin != other.entry->begin) { return (entry->begin < other.entry->begin); } return false; } DvbEpgModel::DvbEpgModel(DvbManager *manager_, QObject *parent) : QObject(parent), manager(manager_), hasPendingOperation(false) { currentDateTimeUtc = QDateTime::currentDateTime().toUTC(); startTimer(54000); DvbChannelModel *channelModel = manager->getChannelModel(); connect(channelModel, SIGNAL(channelAboutToBeUpdated(DvbSharedChannel)), this, SLOT(channelAboutToBeUpdated(DvbSharedChannel))); connect(channelModel, SIGNAL(channelUpdated(DvbSharedChannel)), this, SLOT(channelUpdated(DvbSharedChannel))); connect(channelModel, SIGNAL(channelRemoved(DvbSharedChannel)), this, SLOT(channelRemoved(DvbSharedChannel))); connect(manager->getRecordingModel(), SIGNAL(recordingRemoved(DvbSharedRecording)), this, SLOT(recordingRemoved(DvbSharedRecording))); // TODO use SQL to store epg data QFile file(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/epgdata.dvb")); if (!file.open(QIODevice::ReadOnly)) { qCWarning(logEpg, "Cannot open %s", qPrintable(file.fileName())); return; } QDataStream stream(&file); stream.setVersion(QDataStream::Qt_4_4); DvbRecordingModel *recordingModel = manager->getRecordingModel(); - bool hasRecordingKey = true, hasParental = true; + bool hasRecordingKey = true, hasParental = true, hasMultilang = true; int version; stream >> version; if (version == 0x1ce0eca7) { hasRecordingKey = false; } else if (version == 0x79cffd36) { hasParental = false; - } else if (version != 0x140c37b5) { + } else if (version == 0x140c37b5) { + hasMultilang = false; + } else if (version != 0x20171105) { qCWarning(logEpg, "Wrong DB version for: %s", qPrintable(file.fileName())); return; } while (!stream.atEnd()) { DvbEpgEntry entry; QString channelName; stream >> channelName; entry.channel = channelModel->findChannelByName(channelName); stream >> entry.begin; entry.begin = entry.begin.toUTC(); stream >> entry.duration; - stream >> entry.title; - stream >> entry.subheading; - stream >> entry.details; + + if (hasMultilang) { + int i, count; + + stream >> count; + + for (i = 0; i < count; i++) { + QString code; + unsigned type; + + DvbEpgLangEntry langEntry; + stream >> code; + stream >> langEntry.title; + stream >> langEntry.subheading; + stream >> langEntry.details; + + stream >> type; + stream >> entry.content; + stream >> langEntry.parental; + + if (type <= DvbEpgEntry::EitLast) + entry.type = DvbEpgEntry::EitType(type); + else + entry.type = DvbEpgEntry::EitActualTsSchedule; + + entry.langEntry[code] = langEntry; + } + + + } else { + DvbEpgLangEntry langEntry; + + stream >> langEntry.title; + stream >> langEntry.subheading; + stream >> langEntry.details; + + entry.langEntry[FIRST_LANG] = langEntry; + } if (hasRecordingKey) { SqlKey recordingKey; stream >> recordingKey.sqlKey; if (recordingKey.isSqlKeyValid()) { entry.recording = recordingModel->findRecordingByKey(recordingKey); } } - if (hasParental) { + if (hasParental && !hasMultilang) { unsigned tmp; stream >> tmp; stream >> entry.content; - stream >> entry.parental; + stream >> entry.langEntry[FIRST_LANG].parental; if (tmp <= DvbEpgEntry::EitLast) entry.type = DvbEpgEntry::EitType(tmp); else entry.type = DvbEpgEntry::EitActualTsSchedule; } if (stream.status() != QDataStream::Ok) { qCWarning(logEpg, "Corrupt data %s", qPrintable(file.fileName())); break; } addEntry(entry); } } DvbEpgModel::~DvbEpgModel() { if (hasPendingOperation) { qCWarning(logEpg, "Illegal recursive call"); } if (!dvbEpgFilters.isEmpty() || !atscEpgFilters.isEmpty()) { qCWarning(logEpg, "filter list not empty"); } QFile file(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/epgdata.dvb")); if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { qCWarning(logEpg, "Cannot open %s", qPrintable(file.fileName())); return; } QDataStream stream(&file); stream.setVersion(QDataStream::Qt_4_4); - int version = 0x140c37b5; + int version = 0x20171105; stream << version; foreach (const DvbSharedEpgEntry &entry, entries) { SqlKey recordingKey; if (entry->recording.isValid()) { recordingKey = *entry->recording; } stream << entry->channel->name; stream << entry->begin; stream << entry->duration; - stream << entry->title; - stream << entry->subheading; - stream << entry->details; + + stream << entry->langEntry.size(); + + QHashIterator i(entry->langEntry); + + while (i.hasNext()) { + i.next(); + + stream << i.key(); + + DvbEpgLangEntry langEntry = i.value(); + + stream << langEntry.title; + stream << langEntry.subheading; + stream << langEntry.details; + + stream << int(entry->type); + stream << entry->content; + stream << langEntry.parental; + } + stream << recordingKey.sqlKey; - stream << int(entry->type); - stream << entry->content; - stream << entry->parental; } } QMap DvbEpgModel::getRecordings() const { return recordings; } void DvbEpgModel::setRecordings(const QMap map) { recordings = map; } QMap DvbEpgModel::getEntries() const { return entries; } QHash DvbEpgModel::getEpgChannels() const { return epgChannels; } QList DvbEpgModel::getCurrentNext(const DvbSharedChannel &channel) const { QList result; DvbEpgEntry fakeEntry(channel); for (ConstIterator it = entries.lowerBound(DvbEpgEntryId(&fakeEntry)); it != entries.constEnd(); ++it) { const DvbSharedEpgEntry &entry = *it; if (entry->channel != channel) { break; } result.append(entry); if (result.size() == 2) { break; } } return result; } void DvbEpgModel::Debug(QString text, const DvbSharedEpgEntry &entry) { if (!QLoggingCategory::defaultCategory()->isEnabled(QtDebugMsg)) return; QDateTime begin = entry->begin.toLocalTime(); QTime end = entry->begin.addSecs(QTime(0, 0, 0).secsTo(entry->duration)).toLocalTime().time(); - qCDebug(logEpg, "event %s: type %d, lang %s from %s to %s: %s: %s: %s : %s", qPrintable(text), entry->type, qPrintable(entry->language), qPrintable(QLocale().toString(begin, QLocale::ShortFormat)), qPrintable(QLocale().toString(end)), qPrintable(entry->title), qPrintable(entry->subheading), qPrintable(entry->details), qPrintable(entry->content)); + qCDebug(logEpg, "event %s: type %d, from %s to %s: %s: %s: %s : %s", + qPrintable(text), entry->type, qPrintable(QLocale().toString(begin, QLocale::ShortFormat)), qPrintable(QLocale().toString(end)), + qPrintable(entry->title()), qPrintable(entry->subheading()), qPrintable(entry->details()), qPrintable(entry->content)); } DvbSharedEpgEntry DvbEpgModel::addEntry(const DvbEpgEntry &entry) { if (!entry.validate()) { 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"); return DvbSharedEpgEntry(); } if (hasPendingOperation) { qCWarning(logEpg, "Iillegal recursive call"); return DvbSharedEpgEntry(); } EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation); // Check if the event was already recorded const QDateTime end = entry.begin.addSecs(QTime(0, 0, 0).secsTo(entry.duration)); // Optimize duplicated register logic by using find, with is O(log n) Iterator it = entries.find(DvbEpgEntryId(&entry)); while (it != entries.end()) { const DvbSharedEpgEntry &existingEntry = *it; // Don't do anything if the event already exists if (*existingEntry == entry) return DvbSharedEpgEntry(); const QDateTime enEnd = existingEntry->begin.addSecs(QTime(0, 0, 0).secsTo(existingEntry->duration)); // The logic here was simplified due to performance. // It won't check anymore if an event has its start time // switched, as that would require a O(n) loop, with is // too slow, specially on DVB-S/S2. So, we're letting the QMap // to use a key with just channel/begin time, identifying // obsolete entries only if the end time doesn't match. // A new event conflicts with an existing one if (end != enEnd) { Debug("removed", existingEntry); it = removeEntry(it); break; } // New event data for the same event - if (existingEntry->details.isEmpty() && !entry.details.isEmpty()) { + if (existingEntry->details(FIRST_LANG).isEmpty() && !entry.details("first").isEmpty()) { emit entryAboutToBeUpdated(existingEntry); - const_cast(existingEntry.constData())->details = - entry.details; + + QHashIterator i(entry.langEntry); + + while (i.hasNext()) { + i.next(); + + DvbEpgLangEntry langEntry = i.value(); + + const_cast(existingEntry.constData())->langEntry[i.key()].details = langEntry.details; + } emit entryUpdated(existingEntry); Debug("updated", existingEntry); } return existingEntry; } if (entry.begin.addSecs(QTime(0, 0, 0).secsTo(entry.duration)) > currentDateTimeUtc) { DvbSharedEpgEntry existingEntry = entries.value(DvbEpgEntryId(&entry)); if (existingEntry.isValid()) { - if (existingEntry->details.isEmpty() && !entry.details.isEmpty()) { + if (existingEntry->details(FIRST_LANG).isEmpty() && !entry.details("first").isEmpty()) { // needed for atsc emit entryAboutToBeUpdated(existingEntry); - const_cast(existingEntry.constData())->details = - entry.details; + + QHashIterator i(entry.langEntry); + + while (i.hasNext()) { + i.next(); + + DvbEpgLangEntry langEntry = i.value(); + + const_cast(existingEntry.constData())->langEntry[i.key()].details = langEntry.details; + } emit entryUpdated(existingEntry); Debug("updated2", existingEntry); } return existingEntry; } DvbSharedEpgEntry newEntry(new DvbEpgEntry(entry)); entries.insert(DvbEpgEntryId(newEntry), newEntry); if (newEntry->recording.isValid()) { recordings.insert(newEntry->recording, newEntry); } if (++epgChannels[newEntry->channel] == 1) { emit epgChannelAdded(newEntry->channel); } emit entryAdded(newEntry); Debug("new", newEntry); return newEntry; } return DvbSharedEpgEntry(); } void DvbEpgModel::scheduleProgram(const DvbSharedEpgEntry &entry, int extraSecondsBefore, int extraSecondsAfter, bool checkForRecursion, int priority) { if (!entry.isValid() || (entries.value(DvbEpgEntryId(entry)) != entry)) { qCWarning(logEpg, "Can't schedule program: invalid entry"); return; } if (hasPendingOperation) { qCWarning(logEpg, "Illegal recursive call"); return; } EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation); emit entryAboutToBeUpdated(entry); DvbSharedRecording oldRecording; if (!entry->recording.isValid()) { DvbRecording recording; recording.priority = priority; - recording.name = entry->title; + recording.name = entry->title(FIRST_LANG); recording.channel = entry->channel; recording.begin = entry->begin.addSecs(-extraSecondsBefore); recording.beginEPG = entry->begin; recording.duration = entry->duration.addSecs(extraSecondsBefore + extraSecondsAfter); recording.durationEPG = entry->duration; recording.subheading = - entry->subheading; + entry->subheading(FIRST_LANG); recording.details = - entry->details; + entry->details(); recording.disabled = false; const_cast(entry.constData())->recording = manager->getRecordingModel()->addRecording(recording, checkForRecursion); recordings.insert(entry->recording, entry); } else { oldRecording = entry->recording; recordings.remove(entry->recording); const_cast(entry.constData())->recording = DvbSharedRecording(); } emit entryUpdated(entry); if (oldRecording.isValid()) { // recordingRemoved() will be called hasPendingOperation = false; manager->getRecordingModel()->removeRecording(oldRecording); } } void DvbEpgModel::startEventFilter(DvbDevice *device, const DvbSharedChannel &channel) { switch (channel->transponder.getTransmissionType()) { case DvbTransponderBase::Invalid: break; case DvbTransponderBase::DvbC: case DvbTransponderBase::DvbS: case DvbTransponderBase::DvbS2: case DvbTransponderBase::DvbT: case DvbTransponderBase::DvbT2: case DvbTransponderBase::IsdbT: dvbEpgFilters.append(QExplicitlySharedDataPointer( new DvbEpgFilter(manager, device, channel))); break; case DvbTransponderBase::Atsc: atscEpgFilters.append(QExplicitlySharedDataPointer( new AtscEpgFilter(manager, device, channel))); break; } } void DvbEpgModel::stopEventFilter(DvbDevice *device, const DvbSharedChannel &channel) { switch (channel->transponder.getTransmissionType()) { case DvbTransponderBase::Invalid: break; case DvbTransponderBase::DvbC: case DvbTransponderBase::DvbS: case DvbTransponderBase::DvbS2: case DvbTransponderBase::DvbT: case DvbTransponderBase::DvbT2: case DvbTransponderBase::IsdbT: for (int i = 0; i < dvbEpgFilters.size(); ++i) { const DvbEpgFilter *epgFilter = dvbEpgFilters.at(i).constData(); if ((epgFilter->device == device) && (epgFilter->source == channel->source) && (epgFilter->transponder.corresponds(channel->transponder))) { dvbEpgFilters.removeAt(i); break; } } break; case DvbTransponderBase::Atsc: for (int i = 0; i < atscEpgFilters.size(); ++i) { const AtscEpgFilter *epgFilter = atscEpgFilters.at(i).constData(); if ((epgFilter->device == device) && (epgFilter->source == channel->source) && (epgFilter->transponder.corresponds(channel->transponder))) { atscEpgFilters.removeAt(i); break; } } break; } } void DvbEpgModel::channelAboutToBeUpdated(const DvbSharedChannel &channel) { updatingChannel = *channel; } void DvbEpgModel::channelUpdated(const DvbSharedChannel &channel) { if (hasPendingOperation) { qCWarning(logEpg, "Illegal recursive call"); return; } EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation); if (DvbChannelId(channel) != DvbChannelId(&updatingChannel)) { DvbEpgEntry fakeEntry(channel); Iterator it = entries.lowerBound(DvbEpgEntryId(&fakeEntry)); while ((ConstIterator(it) != entries.constEnd()) && ((*it)->channel == channel)) { it = removeEntry(it); } } } void DvbEpgModel::channelRemoved(const DvbSharedChannel &channel) { if (hasPendingOperation) { qCWarning(logEpg, "Illegal recursive call"); return; } EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation); DvbEpgEntry fakeEntry(channel); Iterator it = entries.lowerBound(DvbEpgEntryId(&fakeEntry)); while ((ConstIterator(it) != entries.constEnd()) && ((*it)->channel == channel)) { it = removeEntry(it); } } void DvbEpgModel::recordingRemoved(const DvbSharedRecording &recording) { if (hasPendingOperation) { qCWarning(logEpg, "Illegal recursive call"); return; } EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation); DvbSharedEpgEntry entry = recordings.take(recording); if (entry.isValid()) { emit entryAboutToBeUpdated(entry); const_cast(entry.constData())->recording = DvbSharedRecording(); emit entryUpdated(entry); } } void DvbEpgModel::timerEvent(QTimerEvent *event) { Q_UNUSED(event) if (hasPendingOperation) { qCWarning(logEpg, "Illegal recursive call"); return; } EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation); currentDateTimeUtc = QDateTime::currentDateTime().toUTC(); Iterator it = entries.begin(); while (ConstIterator(it) != entries.constEnd()) { const DvbSharedEpgEntry &entry = *it; if (entry->begin.addSecs(QTime(0, 0, 0).secsTo(entry->duration)) > currentDateTimeUtc) { ++it; } else { it = removeEntry(it); } } } DvbEpgModel::Iterator DvbEpgModel::removeEntry(Iterator it) { const DvbSharedEpgEntry &entry = *it; if (entry->recording.isValid()) { recordings.remove(entry->recording); } if (--epgChannels[entry->channel] == 0) { epgChannels.remove(entry->channel); emit epgChannelRemoved(entry->channel); } emit entryRemoved(entry); return entries.erase(it); } DvbEpgFilter::DvbEpgFilter(DvbManager *manager, DvbDevice *device_, const DvbSharedChannel &channel) : device(device_) { source = channel->source; transponder = channel->transponder; device->addSectionFilter(0x12, this); channelModel = manager->getChannelModel(); epgModel = manager->getEpgModel(); } DvbEpgFilter::~DvbEpgFilter() { device->removeSectionFilter(0x12, this); } QTime DvbEpgFilter::bcdToTime(int bcd) { return QTime(((bcd >> 20) & 0x0f) * 10 + ((bcd >> 16) & 0x0f), ((bcd >> 12) & 0x0f) * 10 + ((bcd >> 8) & 0x0f), ((bcd >> 4) & 0x0f) * 10 + (bcd & 0x0f)); } static const QByteArray contentStr[16][16] = { [0] = {}, [1] = { /* Movie/Drama */ {}, {I18N_NOOP("Detective")}, {I18N_NOOP("Adventure")}, {I18N_NOOP("Science Fiction")}, {I18N_NOOP("Comedy")}, {I18N_NOOP("Soap")}, {I18N_NOOP("Romance")}, {I18N_NOOP("Classical")}, {I18N_NOOP("Adult")}, {I18N_NOOP("User defined")}, }, [2] = { /* News/Current affairs */ {}, {I18N_NOOP("Weather")}, {I18N_NOOP("Magazine")}, {I18N_NOOP("Documentary")}, {I18N_NOOP("Discussion")}, {I18N_NOOP("User Defined")}, }, [3] = { /* Show/Game show */ {}, {I18N_NOOP("Quiz")}, {I18N_NOOP("Variety")}, {I18N_NOOP("Talk")}, {I18N_NOOP("User Defined")}, }, [4] = { /* Sports */ {}, {I18N_NOOP("Events")}, {I18N_NOOP("Magazine")}, {I18N_NOOP("Football")}, {I18N_NOOP("Tennis")}, {I18N_NOOP("Team")}, {I18N_NOOP("Athletics")}, {I18N_NOOP("Motor")}, {I18N_NOOP("Water")}, {I18N_NOOP("Winter")}, {I18N_NOOP("Equestrian")}, {I18N_NOOP("Martial")}, {I18N_NOOP("User Defined")}, }, [5] = { /* Children's/Youth */ {}, {I18N_NOOP("Preschool")}, {I18N_NOOP("06 to 14")}, {I18N_NOOP("10 to 16")}, {I18N_NOOP("Educational")}, {I18N_NOOP("Cartoons")}, {I18N_NOOP("User Defined")}, }, [6] = { /* Music/Ballet/Dance */ {}, {I18N_NOOP("Poprock")}, {I18N_NOOP("Classical")}, {I18N_NOOP("Folk")}, {I18N_NOOP("Jazz")}, {I18N_NOOP("Opera")}, {I18N_NOOP("Ballet")}, {I18N_NOOP("User Defined")}, }, [7] = { /* Arts/Culture */ {}, {I18N_NOOP("Performance")}, {I18N_NOOP("Fine Arts")}, {I18N_NOOP("Religion")}, {I18N_NOOP("Traditional")}, {I18N_NOOP("Literature")}, {I18N_NOOP("Cinema")}, {I18N_NOOP("Experimental")}, {I18N_NOOP("Press")}, {I18N_NOOP("New Media")}, {I18N_NOOP("Magazine")}, {I18N_NOOP("Fashion")}, {I18N_NOOP("User Defined")}, }, [8] = { /* Social/Political/Economics */ {}, {I18N_NOOP("Magazine")}, {I18N_NOOP("Advisory")}, {I18N_NOOP("People")}, {I18N_NOOP("User Defined")}, }, [9] = { /* Education/Science/Factual */ {}, {I18N_NOOP("Nature")}, {I18N_NOOP("Technology")}, {I18N_NOOP("Medicine")}, {I18N_NOOP("Foreign")}, {I18N_NOOP("Social")}, {I18N_NOOP("Further")}, {I18N_NOOP("Language")}, {I18N_NOOP("User Defined")}, }, [10] = { /* Leisure/Hobbies */ {}, {I18N_NOOP("Travel")}, {I18N_NOOP("Handicraft")}, {I18N_NOOP("Motoring")}, {I18N_NOOP("Fitness")}, {I18N_NOOP("Cooking")}, {I18N_NOOP("Shopping")}, {I18N_NOOP("Gardening")}, {I18N_NOOP("User Defined")}, }, [11] = { /* Special characteristics */ {I18N_NOOP("Original Language")}, {I18N_NOOP("Black and White ")}, {I18N_NOOP("Unpublished")}, {I18N_NOOP("Live")}, {I18N_NOOP("Planostereoscopic")}, {I18N_NOOP("User Defined")}, {I18N_NOOP("User Defined 1")}, {I18N_NOOP("User Defined 2")}, {I18N_NOOP("User Defined 3")}, {I18N_NOOP("User Defined 4")} } }; static const QByteArray nibble1Str[16] = { [0] = {I18N_NOOP("Undefined")}, [1] = {I18N_NOOP("Movie")}, [2] = {I18N_NOOP("News")}, [3] = {I18N_NOOP("Show")}, [4] = {I18N_NOOP("Sports")}, [5] = {I18N_NOOP("Children")}, [6] = {I18N_NOOP("Music")}, [7] = {I18N_NOOP("Culture")}, [8] = {I18N_NOOP("Social")}, [9] = {I18N_NOOP("Education")}, [10] = {I18N_NOOP("Leisure")}, [11] = {I18N_NOOP("Special")}, [12] = {I18N_NOOP("Reserved")}, [13] = {I18N_NOOP("Reserved")}, [14] = {I18N_NOOP("Reserved")}, [15] = {I18N_NOOP("User defined")}, }; static const QByteArray braNibble1Str[16] = { [0] = {I18N_NOOP("News")}, [1] = {I18N_NOOP("Sports")}, [2] = {I18N_NOOP("Education")}, [3] = {I18N_NOOP("Soap opera")}, [4] = {I18N_NOOP("Mini-series")}, [5] = {I18N_NOOP("Series")}, [6] = {I18N_NOOP("Variety")}, [7] = {I18N_NOOP("Reality show")}, [8] = {I18N_NOOP("Information")}, [9] = {I18N_NOOP("Comical")}, [10] = {I18N_NOOP("Children")}, [11] = {I18N_NOOP("Erotic")}, [12] = {I18N_NOOP("Movie")}, [13] = {I18N_NOOP("Raffle, television sales, prizing")}, [14] = {I18N_NOOP("Debate/interview")}, [15] = {I18N_NOOP("Other")}, }; // Using the terms from the English version of NBR 15603-2:2007 // The table omits nibble2="Other", as it is better to show nibble 1 // definition instead. // when nibble2[x][0] == nibble1[x] and it has no other definition, // except for "Other", the field will be kept in blank, as the logic // will fall back to the definition at nibble 1. static QByteArray braNibble2Str[16][16] = { [0] = { {I18N_NOOP("News")}, {I18N_NOOP("Report")}, {I18N_NOOP("Documentary")}, {I18N_NOOP("Biography")}, }, [1] = {}, [2] = { {I18N_NOOP("Educative")}, }, [3] = {}, [4] = {}, [5] = {}, [6] = { {I18N_NOOP("Auditorium")}, {I18N_NOOP("Show")}, {I18N_NOOP("Musical")}, {I18N_NOOP("Making of")}, {I18N_NOOP("Feminine")}, {I18N_NOOP("Game show")}, }, [7] = {}, [8] = { {I18N_NOOP("Cooking")}, {I18N_NOOP("Fashion")}, {I18N_NOOP("Country")}, {I18N_NOOP("Health")}, {I18N_NOOP("Travel")}, }, [9] = {}, [10] = {}, [11] = {}, [12] = {}, [13] = { {I18N_NOOP("Raffle")}, {I18N_NOOP("Television sales")}, {I18N_NOOP("Prizing")}, }, [14] = { {I18N_NOOP("Discussion")}, {I18N_NOOP("Interview")}, }, [15] = { {I18N_NOOP("Adult cartoon")}, {I18N_NOOP("Interactive")}, {I18N_NOOP("Policy")}, {I18N_NOOP("Religion")}, }, }; QString DvbEpgFilter::getContent(DvbContentDescriptor &descriptor) { QString content; for (DvbEitContentEntry entry = descriptor.contents(); entry.isValid(); entry.advance()) { const int nibble1 = entry.contentNibbleLevel1(); const int nibble2 = entry.contentNibbleLevel2(); QByteArray s; // FIXME: should do it only for ISDB-Tb (Brazilian variation), // as the Japanese variation uses the same codes as DVB if (transponder.getTransmissionType() == DvbTransponderBase::IsdbT) { s = braNibble2Str[nibble1][nibble2]; if (s == "") s = braNibble1Str[nibble1]; if (s != "") content += i18n(s) + "\n"; } else { s = contentStr[nibble1][nibble2]; if (s == "") s = nibble1Str[nibble1]; if (s != "") content += i18n(s) + "\n"; } } if (content != "") { // xgettext:no-c-format return (i18n("Genre: %1", content)); } return content; } /* As defined at ABNT NBR 15603-2 */ static const QByteArray braRating[] = { [0] = {I18N_NOOP("reserved")}, [1] = {I18N_NOOP("all audiences")}, [2] = {I18N_NOOP("10 years")}, [3] = {I18N_NOOP("12 years")}, [4] = {I18N_NOOP("14 years")}, [5] = {I18N_NOOP("16 years")}, [6] = {I18N_NOOP("18 years")}, }; #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) QString DvbEpgFilter::getParental(QString code, DvbParentalRatingEntry &entry) { QString parental; // Rating from 0x10 to 0xff are broadcaster's specific if (entry.rating() == 0) { // xgettext:no-c-format - parental += i18n("Country %1: not rated\n", code); + parental += i18n("not rated\n"); } else if (entry.rating() < 0x10) { if (code == "BRA" && transponder.getTransmissionType() == DvbTransponderBase::IsdbT) { unsigned int rating = entry.rating(); if (rating >= ARRAY_SIZE(braRating)) rating = 0; // Reserved QString GenStr; int genre = entry.rating() >> 4; if (genre & 0x2) GenStr = i18n("violence / "); if (genre & 0x4) GenStr = i18n("sex / "); if (genre & 0x1) GenStr = i18n("drugs / "); if (genre) { GenStr.truncate(GenStr.size() - 2); GenStr = " (" + GenStr + ")"; } QString ratingStr = i18n(braRating[entry.rating()]); // xgettext:no-c-format - parental += i18n("Country %1: rating: %2%3\n", code, ratingStr, GenStr); + parental += i18n("Parental rate: %2%3\n", ratingStr, GenStr); } else { // xgettext:no-c-format - parental += i18n("Country %1: rating: %2 years.\n", code, entry.rating() + 3); + parental += i18n("Parental rate: %2 years.\n", entry.rating() + 3); } } return parental; } void DvbEpgFilter::processSection(const char *data, int size) { unsigned char tableId = data[0]; if ((tableId < 0x4e) || (tableId > 0x6f)) { return; } DvbEitSection eitSection(data, size); if (!eitSection.isValid()) { qCDebug(logEpg, "section is invalid"); return; } DvbChannel fakeChannel; fakeChannel.source = source; fakeChannel.transponder = transponder; fakeChannel.networkId = eitSection.originalNetworkId(); fakeChannel.transportStreamId = eitSection.transportStreamId(); fakeChannel.serviceId = eitSection.serviceId(); DvbSharedChannel channel = channelModel->findChannelById(fakeChannel); if (!channel.isValid()) { fakeChannel.networkId = -1; channel = channelModel->findChannelById(fakeChannel); } if (!channel.isValid()) { qCDebug(logEpg, "channel invalid"); return; } if (eitSection.entries().getLength()) qCDebug(logEpg, "table 0x%02x, extension 0x%04x, session %d/%d, size %d", eitSection.tableId(), eitSection.tableIdExtension(), eitSection.sectionNumber(), eitSection.lastSectionNumber(), eitSection.entries().getLength()); for (DvbEitSectionEntry entry = eitSection.entries(); entry.isValid(); entry.advance()) { DvbEpgEntry epgEntry; + DvbEpgLangEntry *langEntry; if (tableId == 0x4e) epgEntry.type = DvbEpgEntry::EitActualTsPresentFollowing; else if (tableId == 0x4f) epgEntry.type = DvbEpgEntry::EitOtherTsPresentFollowing; else if (tableId < 0x60) epgEntry.type = DvbEpgEntry::EitActualTsSchedule; else epgEntry.type = DvbEpgEntry::EitOtherTsSchedule; epgEntry.channel = channel; /* * ISDB-T Brazil uses time in UTC-3, * as defined by ABNT NBR 15603-2:2007. */ if (channel->transponder.getTransmissionType() == DvbTransponderBase::IsdbT) epgEntry.begin = QDateTime(QDate::fromJulianDay(entry.startDate() + 2400001), - bcdToTime(entry.startTime()), Qt::OffsetFromUTC, -10800).toUTC(); + bcdToTime(entry.startTime()), Qt::OffsetFromUTC, -10800).toUTC(); else epgEntry.begin = QDateTime(QDate::fromJulianDay(entry.startDate() + 2400001), - bcdToTime(entry.startTime()), Qt::UTC); + bcdToTime(entry.startTime()), Qt::UTC); epgEntry.duration = bcdToTime(entry.duration()); for (DvbDescriptor descriptor = entry.descriptors(); descriptor.isValid(); descriptor.advance()) { switch (descriptor.descriptorTag()) { case 0x4d: { DvbShortEventDescriptor eventDescriptor(descriptor); if (!eventDescriptor.isValid()) { break; } QString code; code.append(QChar(eventDescriptor.languageCode1())); code.append(QChar(eventDescriptor.languageCode2())); code.append(QChar(eventDescriptor.languageCode3())); code = code.toUpper(); - if (epgEntry.language.isEmpty()) { - epgEntry.language = code; - } else { - if (code != epgEntry.language) { - qCDebug(logEpg, "Ignoring short event descriptor for language %s", qPrintable(code)); - continue; - } + if (!epgEntry.langEntry.contains(code)) { + DvbEpgLangEntry e; + epgEntry.langEntry.insert(code, e); } + langEntry = &epgEntry.langEntry[code]; + + langEntry->title += eventDescriptor.eventName(); + langEntry->subheading += eventDescriptor.text(); - epgEntry.title += eventDescriptor.eventName(); - epgEntry.subheading += eventDescriptor.text(); break; } case 0x4e: { DvbExtendedEventDescriptor eventDescriptor(descriptor); if (!eventDescriptor.isValid()) { break; } QString code; code.append(QChar(eventDescriptor.languageCode1())); code.append(QChar(eventDescriptor.languageCode2())); code.append(QChar(eventDescriptor.languageCode3())); code = code.toUpper(); - if (epgEntry.language.isEmpty()) { - epgEntry.language = code; - } else { - if (code != epgEntry.language) { - qCDebug(logEpg, "Ignoring extended event descriptor for language %s", qPrintable(code)); - continue; - } + if (!epgEntry.langEntry.contains(code)) { + DvbEpgLangEntry e; + epgEntry.langEntry.insert(code, e); } + langEntry = &epgEntry.langEntry[code]; - epgEntry.details += eventDescriptor.text(); + langEntry->details += eventDescriptor.text(); break; } case 0x54: { DvbContentDescriptor eventDescriptor(descriptor); if (!eventDescriptor.isValid()) { break; } epgEntry.content += getContent(eventDescriptor); break; } case 0x55: { DvbParentalRatingDescriptor eventDescriptor(descriptor); if (!eventDescriptor.isValid()) { break; } for (DvbParentalRatingEntry entry = eventDescriptor.contents(); entry.isValid(); entry.advance()) { QString code; code.append(QChar(entry.languageCode1())); code.append(QChar(entry.languageCode2())); code.append(QChar(entry.languageCode3())); code = code.toUpper(); - if (epgEntry.language.isEmpty()) { - epgEntry.language = code; - } else { - if (code != epgEntry.language) { - qCDebug(logEpg, "Ignoring parental rating description for language %s", qPrintable(code)); - continue; - } + if (!epgEntry.langEntry.contains(code)) { + DvbEpgLangEntry e; + epgEntry.langEntry.insert(code, e); } + langEntry = &epgEntry.langEntry[code]; - epgEntry.parental += getParental(code, entry); + langEntry->parental += getParental(code, entry); } break; } } } epgModel->addEntry(epgEntry); } } void AtscEpgMgtFilter::processSection(const char *data, int size) { epgFilter->processMgtSection(data, size); } void AtscEpgEitFilter::processSection(const char *data, int size) { epgFilter->processEitSection(data, size); } void AtscEpgEttFilter::processSection(const char *data, int size) { epgFilter->processEttSection(data, size); } AtscEpgFilter::AtscEpgFilter(DvbManager *manager, DvbDevice *device_, const DvbSharedChannel &channel) : device(device_), mgtFilter(this), eitFilter(this), ettFilter(this) { source = channel->source; transponder = channel->transponder; device->addSectionFilter(0x1ffb, &mgtFilter); channelModel = manager->getChannelModel(); epgModel = manager->getEpgModel(); } AtscEpgFilter::~AtscEpgFilter() { foreach (int pid, eitPids) { device->removeSectionFilter(pid, &eitFilter); } foreach (int pid, ettPids) { device->removeSectionFilter(pid, &ettFilter); } device->removeSectionFilter(0x1ffb, &mgtFilter); } void AtscEpgFilter::processMgtSection(const char *data, int size) { unsigned char tableId = data[0]; if (tableId != 0xc7) { return; } AtscMgtSection mgtSection(data, size); if (!mgtSection.isValid()) { return; } int entryCount = mgtSection.entryCount(); QList newEitPids; QList newEttPids; AtscMgtSectionEntry entry = mgtSection.entries(); for (int i = 0; i < entryCount; i++) { if (!entry.isValid()) break; int tableType = entry.tableType(); if ((tableType >= 0x0100) && (tableType <= 0x017f)) { int pid = entry.pid(); int index = (qLowerBound(newEitPids, pid) - newEitPids.constBegin()); if ((index >= newEitPids.size()) || (newEitPids.at(index) != pid)) { newEitPids.insert(index, pid); } } if ((tableType >= 0x0200) && (tableType <= 0x027f)) { int pid = entry.pid(); int index = (qLowerBound(newEttPids, pid) - newEttPids.constBegin()); if ((index >= newEttPids.size()) || (newEttPids.at(index) != pid)) { newEttPids.insert(index, pid); } } if (i < entryCount - 1) entry.advance(); } for (int i = 0; i < eitPids.size(); ++i) { int pid = eitPids.at(i); int index = (qBinaryFind(newEitPids, pid) - newEitPids.constBegin()); if (index < newEitPids.size()) { newEitPids.removeAt(index); } else { device->removeSectionFilter(pid, &eitFilter); eitPids.removeAt(i); --i; } } for (int i = 0; i < ettPids.size(); ++i) { int pid = ettPids.at(i); int index = (qBinaryFind(newEttPids, pid) - newEttPids.constBegin()); if (index < newEttPids.size()) { newEttPids.removeAt(index); } else { device->removeSectionFilter(pid, &ettFilter); ettPids.removeAt(i); --i; } } for (int i = 0; i < newEitPids.size(); ++i) { int pid = newEitPids.at(i); eitPids.append(pid); device->addSectionFilter(pid, &eitFilter); } for (int i = 0; i < newEttPids.size(); ++i) { int pid = newEttPids.at(i); ettPids.append(pid); device->addSectionFilter(pid, &ettFilter); } } void AtscEpgFilter::processEitSection(const char *data, int size) { unsigned char tableId = data[0]; if (tableId != 0xcb) { return; } AtscEitSection eitSection(data, size); if (!eitSection.isValid()) { qCDebug(logEpg, "section is invalid"); return; } DvbChannel fakeChannel; fakeChannel.source = source; fakeChannel.transponder = transponder; fakeChannel.networkId = eitSection.sourceId(); DvbSharedChannel channel = channelModel->findChannelById(fakeChannel); if (!channel.isValid()) { qCDebug(logEpg, "channel is invalid"); return; } qCDebug(logEpg, "Processing EIT section with size %d", size); int entryCount = eitSection.entryCount(); // 1980-01-06T000000 minus 15 secs (= UTC - GPS in 2011) QDateTime baseDateTime = QDateTime(QDate(1980, 1, 5), QTime(23, 59, 45), Qt::UTC); AtscEitSectionEntry eitEntry = eitSection.entries(); for (int i = 0; i < entryCount; i++) { if (!eitEntry.isValid()) break; DvbEpgEntry epgEntry; epgEntry.channel = channel; epgEntry.begin = baseDateTime.addSecs(eitEntry.startTime()); epgEntry.duration = QTime(0, 0, 0).addSecs(eitEntry.duration()); - epgEntry.title = eitEntry.title(); + + + DvbEpgLangEntry *langEntry; + + if (epgEntry.langEntry.contains(FIRST_LANG)) + langEntry = &epgEntry.langEntry[FIRST_LANG]; + else + langEntry = new(DvbEpgLangEntry); + + langEntry->title = eitEntry.title(); quint32 id = ((quint32(fakeChannel.networkId) << 16) | quint32(eitEntry.eventId())); DvbSharedEpgEntry entry = epgEntries.value(id); entry = epgModel->addEntry(epgEntry); epgEntries.insert(id, entry); if ( i < entryCount -1) eitEntry.advance(); } } void AtscEpgFilter::processEttSection(const char *data, int size) { unsigned char tableId = data[0]; if (tableId != 0xcc) { return; } AtscEttSection ettSection(data, size); if (!ettSection.isValid() || (ettSection.messageType() != 0x02)) { return; } quint32 id = ((quint32(ettSection.sourceId()) << 16) | quint32(ettSection.eventId())); DvbSharedEpgEntry entry = epgEntries.value(id); if (entry.isValid()) { QString details = ettSection.text(); - if (entry->details != details) { + if (entry->details() != details) { DvbEpgEntry modifiedEntry = *entry; - modifiedEntry.details = details; + + DvbEpgLangEntry *langEntry; + + if (modifiedEntry.langEntry.contains(FIRST_LANG)) + langEntry = &modifiedEntry.langEntry[FIRST_LANG]; + else + langEntry = new(DvbEpgLangEntry); + + langEntry->details = details; entry = epgModel->addEntry(modifiedEntry); epgEntries.insert(id, entry); } } } diff --git a/src/dvb/dvbepg.h b/src/dvb/dvbepg.h index 685c89a..ff5d0f0 100644 --- a/src/dvb/dvbepg.h +++ b/src/dvb/dvbepg.h @@ -1,159 +1,323 @@ /* * dvbepg.h * * Copyright (C) 2009-2011 Christoph Pfister * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef DVBEPG_H #define DVBEPG_H #include "dvbrecording.h" class AtscEpgFilter; class DvbDevice; class DvbEpgFilter; +#define FIRST_LANG "first" + +class DvbEpgLangEntry +{ +public: + QString title; + QString subheading; + QString details; + QString parental; +}; + class DvbEpgEntry : public SharedData { public: enum EitType { EitActualTsPresentFollowing = 0, EitOtherTsPresentFollowing = 1, EitActualTsSchedule = 2, EitOtherTsSchedule = 3, EitLast = 3 }; DvbEpgEntry(): type(EitActualTsSchedule) { } explicit DvbEpgEntry(const DvbSharedChannel &channel_) : channel(channel_) { } ~DvbEpgEntry() { } // checks that all variables are ok bool validate() const; DvbSharedChannel channel; EitType type; QDateTime begin; // UTC QTime duration; - QString language; - QString title; - QString subheading; - QString details; QString content; - QString parental; + + QHash langEntry; + DvbSharedRecording recording; + QString title(QString lang = QString()) const { + QString s; + + if (!lang.isEmpty() && lang != FIRST_LANG) + return langEntry[lang].title; + + QHashIterator i(langEntry); + bool first = true; + + while (i.hasNext()) { + i.next(); + + QString code = i.key(); + DvbEpgLangEntry entry = i.value(); + + if (!entry.title.isEmpty()) { + if (first) + first = false; + else + s += "/"; + + if (lang != FIRST_LANG) { + s += code; + s += ": "; + } + s += entry.title; + } + + if (lang == FIRST_LANG) + break; + } + return s; + } + + QString subheading(QString lang = QString()) const { + QString s; + + if (!lang.isEmpty() && lang != FIRST_LANG) + return langEntry[lang].subheading; + + QHashIterator i(langEntry); + bool first = true; + + while (i.hasNext()) { + i.next(); + + QString code = i.key(); + DvbEpgLangEntry entry = i.value(); + + if (!entry.subheading.isEmpty()) { + if (first) + first = false; + else + s += "/"; + + if (lang != FIRST_LANG) { + s += code; + s += ": "; + } + s += entry.subheading; + } + + if (lang == FIRST_LANG) + break; + } + return s; + } + + QString details(QString lang = QString()) const { + QString s; + + if (!lang.isEmpty() && lang != FIRST_LANG) + return langEntry[lang].details; + + QHashIterator i(langEntry); + bool first = true; + + while (i.hasNext()) { + i.next(); + + QString code = i.key(); + DvbEpgLangEntry entry = i.value(); + + if (!entry.details.isEmpty()) { + if (first) + first = false; + else + s += "\n\n"; + + if (lang != FIRST_LANG) { + s += code; + s += ": "; + } + s += entry.details; + } + + if (lang == FIRST_LANG) + break; + + s += "\n\n"; + } + return s; + } + + QString parental(QString lang = QString()) const { + QString s; + + if (!lang.isEmpty() && lang != FIRST_LANG) + return langEntry[lang].parental; + + QHashIterator i(langEntry); + bool first = true; + + while (i.hasNext()) { + i.next(); + + QString code = i.key(); + DvbEpgLangEntry entry = i.value(); + + if (!entry.parental.isEmpty()) { + if (first) + first = false; + else + s += " / "; + + if (lang != FIRST_LANG) { + s += code; + s += ": "; + } + s += entry.parental; + } + + if (lang == FIRST_LANG) + break; + + s += "\n\n"; + } + return s; + } + // Check only the user-visible elements bool operator==(const DvbEpgEntry &other) const { if (channel != other.channel) return false; if (begin != other.begin) return false; if (duration != other.duration) return false; - if (language != other.language) - return false; - if (title != other.title) - return false; - if (subheading != other.subheading) - return false; - if (details != other.details) - return false; if (content != other.content) return false; - if (parental != other.parental) - return false; + + QHashIterator i(langEntry); + while (i.hasNext()) { + i.next(); + + QString code = i.key(); + + if (!other.langEntry.contains(code)) + return false; + + DvbEpgLangEntry thisEntry = i.value(); + DvbEpgLangEntry otherEntry = other.langEntry[code]; + + if (thisEntry.title != otherEntry.title) + return false; + if (thisEntry.subheading != otherEntry.subheading) + return false; + if (thisEntry.details != otherEntry.details) + return false; + if (thisEntry.parental != otherEntry.parental) + return false; + + // If first language matches, assume entries are identical + return true; + } return true; } }; typedef ExplicitlySharedDataPointer DvbSharedEpgEntry; Q_DECLARE_TYPEINFO(DvbSharedEpgEntry, Q_MOVABLE_TYPE); class DvbEpgEntryId { public: explicit DvbEpgEntryId(const DvbEpgEntry *entry_) : entry(entry_) { } explicit DvbEpgEntryId(const DvbSharedEpgEntry &entry_) : entry(entry_.constData()) { } ~DvbEpgEntryId() { } // compares entries, 'recording' is ignored // if one 'details' is empty, 'details' is ignored bool operator<(const DvbEpgEntryId &other) const; private: const DvbEpgEntry *entry; }; class DvbEpgModel : public QObject { Q_OBJECT typedef QMap::Iterator Iterator; typedef QMap::ConstIterator ConstIterator; public: DvbEpgModel(DvbManager *manager_, QObject *parent); ~DvbEpgModel(); QMap getEntries() const; QMap getRecordings() const; void setRecordings(const QMap map); QHash getEpgChannels() const; QList getCurrentNext(const DvbSharedChannel &channel) const; DvbSharedEpgEntry addEntry(const DvbEpgEntry &entry); void scheduleProgram(const DvbSharedEpgEntry &entry, int extraSecondsBefore, int extraSecondsAfter, bool checkForRecursion=false, int priority=10); void startEventFilter(DvbDevice *device, const DvbSharedChannel &channel); void stopEventFilter(DvbDevice *device, const DvbSharedChannel &channel); signals: void entryAdded(const DvbSharedEpgEntry &entry); // updating doesn't change the entry pointer (modifies existing content) void entryAboutToBeUpdated(const DvbSharedEpgEntry &entry); void entryUpdated(const DvbSharedEpgEntry &entry); void entryRemoved(const DvbSharedEpgEntry &entry); void epgChannelAdded(const DvbSharedChannel &channel); void epgChannelRemoved(const DvbSharedChannel &channel); private slots: void channelAboutToBeUpdated(const DvbSharedChannel &channel); void channelUpdated(const DvbSharedChannel &channel); void channelRemoved(const DvbSharedChannel &channel); void recordingRemoved(const DvbSharedRecording &recording); private: void timerEvent(QTimerEvent *event); void Debug(QString text, const DvbSharedEpgEntry &entry); Iterator removeEntry(Iterator it); DvbManager *manager; QDateTime currentDateTimeUtc; QMap entries; QMap recordings; QHash epgChannels; QList > dvbEpgFilters; QList > atscEpgFilters; DvbChannel updatingChannel; bool hasPendingOperation; }; #endif /* DVBEPG_H */ diff --git a/src/dvb/dvbepgdialog.cpp b/src/dvb/dvbepgdialog.cpp index 64d1e86..4a6945e 100644 --- a/src/dvb/dvbepgdialog.cpp +++ b/src/dvb/dvbepgdialog.cpp @@ -1,437 +1,437 @@ /* * dvbepgdialog.cpp * * Copyright (C) 2009-2011 Christoph Pfister * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "../log.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "dvbepgdialog.h" #include "dvbepgdialog_p.h" #include "dvbmanager.h" DvbEpgDialog::DvbEpgDialog(DvbManager *manager_, QWidget *parent) : QDialog(parent), manager(manager_) { setWindowTitle(i18nc("@title:window", "Program Guide")); QWidget *mainWidget = new QWidget(this); QBoxLayout *mainLayout = new QHBoxLayout; setLayout(mainLayout); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); mainLayout->addWidget(mainWidget); QWidget *widget = new QWidget(this); epgChannelTableModel = new DvbEpgChannelTableModel(this); epgChannelTableModel->setManager(manager); channelView = new QTreeView(widget); channelView->setMaximumWidth(30 * fontMetrics().averageCharWidth()); channelView->setModel(epgChannelTableModel); channelView->setRootIsDecorated(false); connect(channelView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(channelActivated(QModelIndex))); mainLayout->addWidget(channelView); QBoxLayout *rightLayout = new QVBoxLayout(); QBoxLayout *boxLayout = new QHBoxLayout(); QAction *scheduleAction = new QAction(QIcon::fromTheme(QLatin1String("media-record"), QIcon(":media-record")), i18nc("@action:inmenu tv show", "Record Show"), this); connect(scheduleAction, SIGNAL(triggered()), this, SLOT(scheduleProgram())); QPushButton *pushButton = new QPushButton(scheduleAction->icon(), scheduleAction->text(), widget); connect(pushButton, SIGNAL(clicked()), this, SLOT(scheduleProgram())); boxLayout->addWidget(pushButton); boxLayout->addWidget(new QLabel(i18nc("@label:textbox", "Search:"), widget)); epgTableModel = new DvbEpgTableModel(this); epgTableModel->setEpgModel(manager->getEpgModel()); connect(epgTableModel, SIGNAL(layoutChanged()), this, SLOT(checkEntry())); QLineEdit *lineEdit = new QLineEdit(widget); lineEdit->setClearButtonEnabled(true); connect(lineEdit, SIGNAL(textChanged(QString)), epgTableModel, SLOT(setContentFilter(QString))); boxLayout->addWidget(lineEdit); rightLayout->addLayout(boxLayout); epgView = new QTreeView(widget); epgView->addAction(scheduleAction); epgView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); epgView->setContextMenuPolicy(Qt::ActionsContextMenu); epgView->setMinimumWidth(75 * fontMetrics().averageCharWidth()); epgView->setModel(epgTableModel); epgView->setRootIsDecorated(false); epgView->setUniformRowHeights(true); connect(epgView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(entryActivated(QModelIndex))); rightLayout->addWidget(epgView); contentLabel = new QLabel(widget); contentLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop); contentLabel->setMargin(5); contentLabel->setWordWrap(true); QScrollArea *scrollArea = new QScrollArea(widget); scrollArea->setBackgroundRole(QPalette::Light); scrollArea->setMinimumHeight(12 * fontMetrics().height()); scrollArea->setWidget(contentLabel); scrollArea->setWidgetResizable(true); rightLayout->addWidget(scrollArea); mainLayout->addLayout(rightLayout); mainLayout->addWidget(widget); rightLayout->addWidget(buttonBox); } DvbEpgDialog::~DvbEpgDialog() { } void DvbEpgDialog::setCurrentChannel(const DvbSharedChannel &channel) { channelView->setCurrentIndex(epgChannelTableModel->find(channel)); } void DvbEpgDialog::channelActivated(const QModelIndex &index) { if (!index.isValid()) { epgTableModel->setChannelFilter(DvbSharedChannel()); return; } epgTableModel->setChannelFilter(epgChannelTableModel->value(index)); epgView->setCurrentIndex(epgTableModel->index(0, 0)); } void DvbEpgDialog::entryActivated(const QModelIndex &index) { const DvbSharedEpgEntry &entry = epgTableModel->value(index.row()); if (!entry.isValid()) { contentLabel->setText(QString()); return; } - QString text = "" + entry->title + ""; + QString text = "" + entry->title() + ""; - if (!entry->subheading.isEmpty()) { - text += "
" + entry->subheading + ""; + if (!entry->subheading().isEmpty()) { + text += "
" + entry->subheading() + ""; } QDateTime begin = entry->begin.toLocalTime(); QTime end = entry->begin.addSecs(QTime(0, 0, 0).secsTo(entry->duration)).toLocalTime().time(); text += "

" + QLocale().toString(begin, QLocale::LongFormat) + " - " + QLocale().toString(end) + ""; - if (!entry->details.isEmpty() && entry->details != entry->title) { - text += "

" + entry->details; + if (!entry->details().isEmpty() && entry->details() != entry->title()) { + text += "

" + entry->details(); } if (!entry->content.isEmpty()) { text += "

" + entry->content + ""; } - if (!entry->parental.isEmpty()) { - text += "

" + entry->parental + ""; + if (!entry->parental().isEmpty()) { + text += "

" + entry->parental() + ""; } contentLabel->setText(text); } void DvbEpgDialog::checkEntry() { if (!epgView->currentIndex().isValid()) { // FIXME workaround --> file bug contentLabel->setText(QString()); } } void DvbEpgDialog::scheduleProgram() { const DvbSharedEpgEntry &entry = epgTableModel->value(epgView->currentIndex()); if (entry.isValid()) { manager->getEpgModel()->scheduleProgram(entry, manager->getBeginMargin(), manager->getEndMargin()); } } bool DvbEpgEntryLessThan::operator()(const DvbSharedEpgEntry &x, const DvbSharedEpgEntry &y) const { if (x->channel != y->channel) { return (x->channel->name.localeAwareCompare(y->channel->name) < 0); } if (x->begin != y->begin) { return (x->begin < y->begin); } if (x->duration != y->duration) { return (x->duration < y->duration); } - if (x->title != y->title) { - return (x->title < y->title); + if (x->title(FIRST_LANG) != y->title(FIRST_LANG)) { + return (x->title(FIRST_LANG) < y->title(FIRST_LANG)); } - if (x->subheading != y->subheading) { - return (x->subheading < y->subheading); + if (x->subheading(FIRST_LANG) != y->subheading(FIRST_LANG)) { + return (x->subheading(FIRST_LANG) < y->subheading(FIRST_LANG)); } - if (x->details < y->details) { - return (x->details < y->details); + if (x->details(FIRST_LANG) < y->details(FIRST_LANG)) { + return (x->details(FIRST_LANG) < y->details(FIRST_LANG)); } return (x < y); } DvbEpgChannelTableModel::DvbEpgChannelTableModel(QObject *parent) : TableModel(parent) { } DvbEpgChannelTableModel::~DvbEpgChannelTableModel() { } void DvbEpgChannelTableModel::setManager(DvbManager *manager) { DvbEpgModel *epgModel = manager->getEpgModel(); connect(epgModel, SIGNAL(epgChannelAdded(DvbSharedChannel)), this, SLOT(epgChannelAdded(DvbSharedChannel))); connect(epgModel, SIGNAL(epgChannelRemoved(DvbSharedChannel)), this, SLOT(epgChannelRemoved(DvbSharedChannel))); // theoretically we should monitor the channel model for updated channels, // but it's very unlikely that this has practical relevance QHeaderView *headerView = manager->getChannelView()->header(); DvbChannelLessThan::SortOrder sortOrder; if (headerView->sortIndicatorOrder() == Qt::AscendingOrder) { if (headerView->sortIndicatorSection() == 0) { sortOrder = DvbChannelLessThan::ChannelNameAscending; } else { sortOrder = DvbChannelLessThan::ChannelNumberAscending; } } else { if (headerView->sortIndicatorSection() == 0) { sortOrder = DvbChannelLessThan::ChannelNameDescending; } else { sortOrder = DvbChannelLessThan::ChannelNumberDescending; } } internalSort(sortOrder); resetFromKeys(epgModel->getEpgChannels()); } QVariant DvbEpgChannelTableModel::data(const QModelIndex &index, int role) const { const DvbSharedChannel &channel = value(index); if (channel.isValid() && (role == Qt::DisplayRole) && (index.column() == 0)) { return channel->name; } return QVariant(); } QVariant DvbEpgChannelTableModel::headerData(int section, Qt::Orientation orientation, int role) const { if ((section == 0) && (orientation == Qt::Horizontal) && (role == Qt::DisplayRole)) { return i18nc("@title:column tv show", "Channel"); } return QVariant(); } void DvbEpgChannelTableModel::epgChannelAdded(const DvbSharedChannel &channel) { insert(channel); } void DvbEpgChannelTableModel::epgChannelRemoved(const DvbSharedChannel &channel) { remove(channel); } bool DvbEpgTableModelHelper::filterAcceptsItem(const DvbSharedEpgEntry &entry) const { switch (filterType) { case ChannelFilter: return (entry->channel == channelFilter); case ContentFilter: - return ((contentFilter.indexIn(entry->title) >= 0) || - (contentFilter.indexIn(entry->subheading) >= 0) || - (contentFilter.indexIn(entry->details) >= 0)); + return ((contentFilter.indexIn(entry->title(FIRST_LANG)) >= 0) || + (contentFilter.indexIn(entry->subheading(FIRST_LANG)) >= 0) || + (contentFilter.indexIn(entry->details(FIRST_LANG)) >= 0)); } return false; } DvbEpgTableModel::DvbEpgTableModel(QObject *parent) : TableModel(parent), epgModel(NULL), contentFilterEventPending(false) { helper.contentFilter.setCaseSensitivity(Qt::CaseInsensitive); } DvbEpgTableModel::~DvbEpgTableModel() { } void DvbEpgTableModel::setEpgModel(DvbEpgModel *epgModel_) { if (epgModel != NULL) { qCWarning(logEpg, "EPG model already set"); return; } epgModel = epgModel_; connect(epgModel, SIGNAL(entryAdded(DvbSharedEpgEntry)), this, SLOT(entryAdded(DvbSharedEpgEntry))); connect(epgModel, SIGNAL(entryAboutToBeUpdated(DvbSharedEpgEntry)), this, SLOT(entryAboutToBeUpdated(DvbSharedEpgEntry))); connect(epgModel, SIGNAL(entryUpdated(DvbSharedEpgEntry)), this, SLOT(entryUpdated(DvbSharedEpgEntry))); connect(epgModel, SIGNAL(entryRemoved(DvbSharedEpgEntry)), this, SLOT(entryRemoved(DvbSharedEpgEntry))); } void DvbEpgTableModel::setChannelFilter(const DvbSharedChannel &channel) { helper.channelFilter = channel; helper.contentFilter.setPattern(QString()); helper.filterType = DvbEpgTableModelHelper::ChannelFilter; reset(epgModel->getEntries()); } QVariant DvbEpgTableModel::data(const QModelIndex &index, int role) const { const DvbSharedEpgEntry &entry = value(index); if (entry.isValid()) { switch (role) { case Qt::DecorationRole: if ((index.column() == 2) && entry->recording.isValid()) { return QIcon::fromTheme(QLatin1String("media-record"), QIcon(":media-record")); } break; case Qt::DisplayRole: switch (index.column()) { case 0: return QLocale().toString((entry->begin.toLocalTime()), QLocale::NarrowFormat); case 1: return entry->duration.toString("HH:mm"); case 2: - return entry->title; + return entry->title(FIRST_LANG); case 3: return entry->channel->name; } break; } } return QVariant(); } QVariant DvbEpgTableModel::headerData(int section, Qt::Orientation orientation, int role) const { if ((orientation == Qt::Horizontal) && (role == Qt::DisplayRole)) { switch (section) { case 0: return i18nc("@title:column tv show", "Start"); case 1: return i18nc("@title:column tv show", "Duration"); case 2: return i18nc("@title:column tv show", "Title"); case 3: return i18nc("@title:column tv show", "Channel"); } } return QVariant(); } void DvbEpgTableModel::setContentFilter(const QString &pattern) { helper.channelFilter = DvbSharedChannel(); helper.contentFilter.setPattern(pattern); if (!pattern.isEmpty()) { helper.filterType = DvbEpgTableModelHelper::ContentFilter; if (!contentFilterEventPending) { contentFilterEventPending = true; QCoreApplication::postEvent(this, new QEvent(QEvent::User), Qt::LowEventPriority); } } else { // use channel filter so that content won't be unnecessarily filtered helper.filterType = DvbEpgTableModelHelper::ChannelFilter; reset(QMap()); } } void DvbEpgTableModel::entryAdded(const DvbSharedEpgEntry &entry) { insert(entry); } void DvbEpgTableModel::entryAboutToBeUpdated(const DvbSharedEpgEntry &entry) { aboutToUpdate(entry); } void DvbEpgTableModel::entryUpdated(const DvbSharedEpgEntry &entry) { update(entry); } void DvbEpgTableModel::entryRemoved(const DvbSharedEpgEntry &entry) { remove(entry); } void DvbEpgTableModel::customEvent(QEvent *event) { Q_UNUSED(event) contentFilterEventPending = false; if (helper.filterType == DvbEpgTableModelHelper::ContentFilter) { reset(epgModel->getEntries()); } } diff --git a/src/dvb/dvbliveview.cpp b/src/dvb/dvbliveview.cpp index 099f525..9012931 100644 --- a/src/dvb/dvbliveview.cpp +++ b/src/dvb/dvbliveview.cpp @@ -1,739 +1,739 @@ /* * dvbliveview.cpp * * Copyright (C) 2007-2011 Christoph Pfister * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "../log.h" #include #include #include #include #include #include #include #include #include #include // bsd compatibility #include // bsd compatibility #include #include "dvbdevice.h" #include "dvbliveview.h" #include "dvbliveview_p.h" #include "dvbmanager.h" void DvbOsd::init(OsdLevel level_, const QString &channelName_, const QList &epgEntries) { level = level_; channelName = channelName_; if (epgEntries.size() < 1) { DvbEpgEntry empty; firstEntry = empty; secondEntry = empty; return; } firstEntry = *epgEntries.at(0); if (epgEntries.size() < 2) { DvbEpgEntry empty; secondEntry = empty; return; } secondEntry = *epgEntries.at(1); } QPixmap DvbOsd::paintOsd(QRect &rect, const QFont &font, Qt::LayoutDirection) { QFont osdFont = font; osdFont.setPointSize(20); QString timeString = QLocale().toString(QTime::currentTime()); QString entryString; int elapsedTime = 0; int totalTime = 0; if (firstEntry.channel.isValid()) { entryString = QLocale().toString(firstEntry.begin.toLocalTime().time()) - + QLatin1Char(' ') + firstEntry.title; + + QLatin1Char(' ') + firstEntry.title(); elapsedTime = firstEntry.begin.secsTo(QDateTime::currentDateTime()); totalTime = QTime(0, 0, 0).secsTo(firstEntry.duration); } if ((level == ShortOsd) && secondEntry.channel.isValid()) { entryString = entryString + QLatin1Char('\n') + QLocale().toString(secondEntry.begin.toLocalTime().time()) + - QLatin1Char(' ') + secondEntry.title; + QLatin1Char(' ') + secondEntry.title(); } int lineHeight = QFontMetrics(osdFont).height(); QRect headerRect(5, 0, rect.width() - 10, lineHeight); QRect entryRect; if (level == ShortOsd) { entryRect = QRect(5, lineHeight + 9, rect.width() - 10, 2 * lineHeight); rect.setHeight(entryRect.bottom() + 1); } else { entryRect = QRect(5, lineHeight + 9, rect.width() - 10, lineHeight); } QPixmap pixmap(rect.size()); { QPainter painter(&pixmap); painter.fillRect(rect, Qt::black); painter.setFont(osdFont); painter.setPen(Qt::white); painter.drawText(headerRect, Qt::AlignLeft, channelName); painter.drawText(headerRect, Qt::AlignRight, timeString); painter.fillRect(5, lineHeight + 2, rect.width() - 10, 5, Qt::gray); painter.fillRect(6, lineHeight + 3, rect.width() - 12, 3, Qt::black); if ((elapsedTime > 0) && (elapsedTime <= totalTime)) { int width = (((rect.width() - 12) * elapsedTime + (totalTime / 2)) / totalTime); painter.fillRect(6, lineHeight + 3, width, 3, Qt::green); } painter.drawText(entryRect, Qt::AlignLeft, entryString); if (level == LongOsd) { QRect boundingRect = entryRect; - if (!firstEntry.subheading.isEmpty()) { + if (!firstEntry.subheading().isEmpty()) { painter.drawText(entryRect.x(), boundingRect.bottom() + 1, entryRect.width(), lineHeight, Qt::AlignLeft, - firstEntry.subheading, &boundingRect); + firstEntry.subheading(), &boundingRect); } - if (!firstEntry.details.isEmpty() && firstEntry.details != firstEntry.title) { + if (!firstEntry.details().isEmpty() && firstEntry.details() != firstEntry.title()) { painter.drawText(entryRect.x(), boundingRect.bottom() + 1, entryRect.width(), rect.height() - boundingRect.bottom() - 1, - Qt::AlignLeft | Qt::TextWordWrap, firstEntry.details, + Qt::AlignLeft | Qt::TextWordWrap, firstEntry.details(), &boundingRect); } if (boundingRect.bottom() < rect.bottom()) { rect.setBottom(boundingRect.bottom()); } } } return pixmap; } DvbLiveView::DvbLiveView(DvbManager *manager_, QObject *parent) : QObject(parent), manager(manager_), device(NULL), videoPid(-1), audioPid(-1), subtitlePid(-1), pausedTime(0) { mediaWidget = manager->getMediaWidget(); osdWidget = mediaWidget->getOsdWidget(); internal = new DvbLiveViewInternal(this); internal->mediaWidget = mediaWidget; connect(&internal->pmtFilter, SIGNAL(pmtSectionChanged(QByteArray)), this, SLOT(pmtSectionChanged(QByteArray))); connect(&patPmtTimer, SIGNAL(timeout()), this, SLOT(insertPatPmt())); connect(&osdTimer, SIGNAL(timeout()), this, SLOT(osdTimeout())); connect(internal, SIGNAL(currentAudioStreamChanged(int)), this, SLOT(currentAudioStreamChanged(int))); connect(internal, SIGNAL(currentSubtitleChanged(int)), this, SLOT(currentSubtitleChanged(int))); connect(internal, SIGNAL(replay()), this, SLOT(replay())); connect(internal, SIGNAL(playbackFinished()), this, SLOT(playbackFinished())); connect(internal, SIGNAL(playbackStatusChanged(MediaWidget::PlaybackStatus)), this, SLOT(playbackStatusChanged(MediaWidget::PlaybackStatus))); connect(internal, SIGNAL(previous()), this, SIGNAL(previous())); connect(internal, SIGNAL(next()), this, SIGNAL(next())); } DvbLiveView::~DvbLiveView() { } void DvbLiveView::replay() { if (device == NULL) { device = manager->requestDevice(channel->source, channel->transponder, DvbManager::Shared); } if (device == NULL) { channel = DvbSharedChannel(); mediaWidget->stop(); if (manager->getRecordingModel()->hasActiveRecordings()) { KMessageBox::sorry(manager->getParentWidget(), i18nc("@info", "All devices are used for recordings.")); } else { KMessageBox::information(manager->getParentWidget(), i18nc("@info", "No device found.")); } return; } internal->channelName = channel->name; internal->resetPipe(); mediaWidget->play(internal); internal->pmtFilter.setProgramNumber(channel->serviceId); startDevice(); internal->patGenerator.initPat(channel->transportStreamId, channel->serviceId, channel->pmtPid); videoPid = -1; audioPid = channel->audioPid; subtitlePid = -1; pmtSectionChanged(channel->pmtSectionData); patPmtTimer.start(500); internal->buffer.reserve(87 * 188); QTimer::singleShot(2000, this, SLOT(showOsd())); } void DvbLiveView::playbackFinished() { mediaWidget->play(internal); } const DvbSharedChannel &DvbLiveView::getChannel() const { return channel; } DvbDevice *DvbLiveView::getDevice() const { return device; } void DvbLiveView::playChannel(const DvbSharedChannel &channel_) { DvbDevice *newDevice = NULL; if ((channel.constData() != NULL) && (channel->source == channel_->source) && (channel->transponder.corresponds(channel_->transponder))) { newDevice = manager->requestDevice(channel->source, channel->transponder, DvbManager::Shared); } playbackStatusChanged(MediaWidget::Idle); channel = channel_; device = newDevice; replay(); } void DvbLiveView::toggleOsd() { if (channel.constData() == NULL) { return; } switch (internal->dvbOsd.level) { case DvbOsd::Off: internal->dvbOsd.init(DvbOsd::ShortOsd, QString(QLatin1String("%1 - %2")).arg(channel->number).arg(channel->name), manager->getEpgModel()->getCurrentNext(channel)); osdWidget->showObject(&internal->dvbOsd, 2500); osdTimer.start(2500); break; case DvbOsd::ShortOsd: internal->dvbOsd.level = DvbOsd::LongOsd; osdWidget->showObject(&internal->dvbOsd, -1); osdTimer.stop(); break; case DvbOsd::LongOsd: internal->dvbOsd.level = DvbOsd::Off; osdWidget->hideObject(); osdTimer.stop(); break; } } void DvbLiveView::pmtSectionChanged(const QByteArray &pmtSectionData) { internal->pmtSectionData = pmtSectionData; DvbPmtSection pmtSection(internal->pmtSectionData); DvbPmtParser pmtParser(pmtSection); videoPid = pmtParser.videoPid; for (int i = 0;; ++i) { if (i == pmtParser.audioPids.size()) { if (i > 0) { audioPid = pmtParser.audioPids.at(0).first; } else { audioPid = -1; } break; } if (pmtParser.audioPids.at(i).first == audioPid) { break; } } for (int i = 0;; ++i) { if (i == pmtParser.subtitlePids.size()) { subtitlePid = -1; break; } if (pmtParser.subtitlePids.at(i).first == subtitlePid) { break; } } updatePids(true); if (channel->isScrambled) { device->startDescrambling(internal->pmtSectionData, this); } if (internal->timeShiftFile.isOpen()) { return; } internal->audioStreams.clear(); audioPids.clear(); for (int i = 0; i < pmtParser.audioPids.size(); ++i) { const QPair &it = pmtParser.audioPids.at(i); if (!it.second.isEmpty()) { internal->audioStreams.append(it.second); } else { internal->audioStreams.append(QString::number(it.first)); } audioPids.append(it.first); } internal->currentAudioStream = audioPids.indexOf(audioPid); mediaWidget->audioStreamsChanged(); subtitlePids.clear(); for (int i = 0; i < pmtParser.subtitlePids.size(); ++i) { const QPair &it = pmtParser.subtitlePids.at(i); subtitlePids.append(it.first); } internal->currentSubtitle = subtitlePids.indexOf(subtitlePid); mediaWidget->subtitlesChanged(); } void DvbLiveView::insertPatPmt() { internal->buffer.append(internal->patGenerator.generatePackets()); internal->buffer.append(internal->pmtGenerator.generatePackets()); } void DvbLiveView::deviceStateChanged() { switch (device->getDeviceState()) { case DvbDevice::DeviceReleased: stopDevice(); device = manager->requestDevice(channel->source, channel->transponder, DvbManager::Shared); if (device != NULL) { startDevice(); } else { mediaWidget->stop(); osdWidget->showText(i18nc("message box", "No available device found."), 2500); } break; case DvbDevice::DeviceIdle: case DvbDevice::DeviceRotorMoving: case DvbDevice::DeviceTuning: case DvbDevice::DeviceTuned: break; } } void DvbLiveView::currentAudioStreamChanged(int currentAudioStream) { audioPid = -1; if ((currentAudioStream >= 0) && (currentAudioStream < audioPids.size())) { audioPid = audioPids.at(currentAudioStream); } updatePids(); } void DvbLiveView::currentSubtitleChanged(int currentSubtitle) { subtitlePid = -1; if ((currentSubtitle >= 0) && (currentSubtitle < subtitlePids.size())) { subtitlePid = subtitlePids.at(currentSubtitle); } updatePids(); } void DvbLiveView::playbackStatusChanged(MediaWidget::PlaybackStatus playbackStatus) { switch (playbackStatus) { case MediaWidget::Idle: if (device != NULL) { stopDevice(); manager->releaseDevice(device, DvbManager::Shared); device = NULL; } pids.clear(); patPmtTimer.stop(); osdTimer.stop(); internal->pmtSectionData.clear(); internal->patGenerator = DvbSectionGenerator(); internal->pmtGenerator = DvbSectionGenerator(); internal->buffer.clear(); internal->timeShiftFile.close(); internal->retryCounter = 0; internal->updateUrl(); internal->dvbOsd.init(DvbOsd::Off, QString(), QList()); osdWidget->hideObject(); break; case MediaWidget::Playing: if (internal->timeShiftFile.isOpen()) { // FIXME mediaWidget->play(internal); mediaWidget->setPosition(pausedTime); } break; case MediaWidget::Paused: pausedTime = mediaWidget->getPosition() - 10; if (pausedTime < 0) pausedTime = 0; if (internal->timeShiftFile.isOpen()) { break; } internal->timeShiftFile.setFileName(manager->getTimeShiftFolder() + QLatin1String("/TimeShift-") + QDateTime::currentDateTime().toString(QLatin1String("yyyyMMddThhmmss")) + QLatin1String(".m2t")); if (internal->timeShiftFile.exists() || !internal->timeShiftFile.open(QIODevice::WriteOnly)) { qCWarning(logDvb, "Cannot open file %s", qPrintable(internal->timeShiftFile.fileName())); internal->timeShiftFile.setFileName(QDir::homePath() + QLatin1String("/TimeShift-") + QDateTime::currentDateTime().toString(QLatin1String("yyyyMMddThhmmss")) + QLatin1String(".m2t")); if (internal->timeShiftFile.exists() || !internal->timeShiftFile.open(QIODevice::WriteOnly)) { qCWarning(logDvb, "Cannot open file %s", qPrintable(internal->timeShiftFile.fileName())); mediaWidget->stop(); break; } } updatePids(); // Use either the timeshift or the standard file URL internal->updateUrl(); // don't allow changes after starting time shift internal->audioStreams.clear(); internal->currentAudioStream = -1; mediaWidget->audioStreamsChanged(); internal->currentSubtitle = -1; mediaWidget->subtitlesChanged(); break; } } void DvbLiveView::showOsd() { if (internal->dvbOsd.level == DvbOsd::Off) { toggleOsd(); } } void DvbLiveView::osdTimeout() { internal->dvbOsd.level = DvbOsd::Off; osdTimer.stop(); } void DvbLiveView::startDevice() { foreach (int pid, pids) { device->addPidFilter(pid, internal); } device->addSectionFilter(channel->pmtPid, &internal->pmtFilter); connect(device, SIGNAL(stateChanged()), this, SLOT(deviceStateChanged())); if (channel->isScrambled && !internal->pmtSectionData.isEmpty()) { device->startDescrambling(internal->pmtSectionData, this); } manager->getEpgModel()->startEventFilter(device, channel); } void DvbLiveView::stopDevice() { manager->getEpgModel()->stopEventFilter(device, channel); if (channel->isScrambled && !internal->pmtSectionData.isEmpty()) { device->stopDescrambling(internal->pmtSectionData, this); } foreach (int pid, pids) { device->removePidFilter(pid, internal); } device->removeSectionFilter(channel->pmtPid, &internal->pmtFilter); disconnect(device, SIGNAL(stateChanged()), this, SLOT(deviceStateChanged())); } void DvbLiveView::updatePids(bool forcePatPmtUpdate) { DvbPmtSection pmtSection(internal->pmtSectionData); DvbPmtParser pmtParser(pmtSection); QSet newPids; int pcrPid = pmtSection.pcrPid(); bool updatePatPmt = forcePatPmtUpdate; bool isTimeShifting = internal->timeShiftFile.isOpen(); if (videoPid != -1) { newPids.insert(videoPid); } if (!isTimeShifting) { if (audioPid != -1) { newPids.insert(audioPid); } } else { for (int i = 0; i < pmtParser.audioPids.size(); ++i) { newPids.insert(pmtParser.audioPids.at(i).first); } } for (int i = 0; i < pmtParser.subtitlePids.size(); ++i) { newPids.insert(pmtParser.subtitlePids.at(i).first); } if (pmtParser.teletextPid != -1) { newPids.insert(pmtParser.teletextPid); } /* check PCR PID is set */ if (pcrPid != 0x1fff) { /* Check not already in list */ if (!newPids.contains(pcrPid)) newPids.insert(pcrPid); } for (int i = 0; i < pids.size(); ++i) { int pid = pids.at(i); if (!newPids.remove(pid)) { device->removePidFilter(pid, internal); pids.removeAt(i); updatePatPmt = true; --i; } } foreach (int pid, newPids) { device->addPidFilter(pid, internal); pids.append(pid); updatePatPmt = true; } if (updatePatPmt) { internal->pmtGenerator.initPmt(channel->pmtPid, pmtSection, pids); insertPatPmt(); } } DvbLiveViewInternal::DvbLiveViewInternal(QObject *parent) : QObject(parent), mediaWidget(NULL), retryCounter(0), readFd(-1), writeFd(-1) { fileName = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation) + QLatin1String("/dvbpipe.m2t"); QFile::remove(fileName); updateUrl(); if (mkfifo(QFile::encodeName(fileName).constData(), 0600) != 0) { qCWarning(logDvb, "Failed to open a fifo. Error: %d", errno); return; } readFd = open(QFile::encodeName(fileName).constData(), O_RDONLY | O_NONBLOCK); if (readFd < 0) { qCWarning(logDvb, "Failed to open fifo for read. Error: %d", errno); return; } writeFd = open(QFile::encodeName(fileName).constData(), O_WRONLY | O_NONBLOCK); if (writeFd < 0) { qCWarning(logDvb, "Failed to open fifo for write. Error: %d", errno); return; } notifier = new QSocketNotifier(writeFd, QSocketNotifier::Write, this); notifier->setEnabled(false); connect(notifier, SIGNAL(activated(int)), this, SLOT(writeToPipe())); emptyBuffer = true; } DvbLiveViewInternal::~DvbLiveViewInternal() { if (writeFd >= 0) { close(writeFd); } if (readFd >= 0) { close(readFd); } } void DvbLiveViewInternal::resetPipe() { retryCounter = 0; notifier->setEnabled(false); if (!buffers.isEmpty()) { buffer = buffers.at(0); buffers.clear(); } if (readFd >= 0) { if (buffer.isEmpty()) { buffer.resize(87 * 188); } while (read(readFd, buffer.data(), buffer.size()) > 0) { } } emptyBuffer = true; buffer.clear(); } void DvbLiveViewInternal::writeToPipe() { if (buffers.isEmpty()) { // disable notifier if the buffers are empty notifier->setEnabled(false); return; } do { const QByteArray ¤tBuffer = buffers.at(0); int bytesWritten = int(write(writeFd, currentBuffer.constData(), currentBuffer.size())); if (bytesWritten < 0) { // Some interrupt happened while writing. Retry. if (errno == EINTR) continue; if (++retryCounter > 50) { // Too much failures. Warn the user qCWarning(logDvb, "Stream seems to be too havy to be displayed"); return; } // EAGAIN may happen when the pipe is full. // That's a normal condition. No need to report. if (errno != EAGAIN) qCWarning(logDvb, "Error %d while writing to pipe", errno); } else { retryCounter = 0; if (bytesWritten == currentBuffer.size()) { buffers.removeFirst(); continue; } if (bytesWritten > 0) buffers.first().remove(0, bytesWritten); } // If bytesWritten is less than buffer size, or returns an // error, there's no sense on wasting CPU time inside a loop break; } while (!buffers.isEmpty()); if (!buffers.isEmpty()) { // Wait for a notification that writeFd is ready to write notifier->setEnabled(true); } } void DvbLiveViewInternal::validateCurrentTotalTime(int ¤tTime, int &totalTime) const { if (emptyBuffer) return; totalTime = startTime.msecsTo(QTime::currentTime()); // Adjust it, if needed if (currentTime > totalTime) currentTime = totalTime -1; } void DvbLiveViewInternal::processData(const char data[188]) { buffer.append(data, 188); if (buffer.size() < (87 * 188)) { return; } if (!timeShiftFile.isOpen()) { if (writeFd >= 0) { buffers.append(buffer); writeToPipe(); if (emptyBuffer) { startTime = QTime::currentTime(); emptyBuffer = false; } } } else { notifier->setEnabled(false); timeShiftFile.write(buffer); // FIXME avoid buffer reallocation if (emptyBuffer) { startTime = QTime::currentTime(); emptyBuffer = false; } } buffer.clear(); buffer.reserve(87 * 188); } diff --git a/src/dvb/dvbrecording.cpp b/src/dvb/dvbrecording.cpp index b1c998f..e27e279 100644 --- a/src/dvb/dvbrecording.cpp +++ b/src/dvb/dvbrecording.cpp @@ -1,1062 +1,1062 @@ /* * dvbrecording.cpp * * Copyright (C) 2009-2011 Christoph Pfister * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "../log.h" #include #include #include #include #include #include #include #include #include #include "../ensurenopendingoperation.h" #include "dvbdevice.h" #include "dvbepg.h" #include "dvbliveview.h" #include "dvbmanager.h" #include "dvbrecording.h" #include "dvbrecording_p.h" #include "dvbtab.h" bool DvbRecording::validate() { if (!name.isEmpty() && channel.isValid() && begin.isValid() && (begin.timeSpec() == Qt::UTC) && duration.isValid()) { // the seconds and milliseconds aren't visible --> set them to zero begin = begin.addMSecs(-(QTime(0, 0, 0).msecsTo(begin.time()) % 60000)); end = begin.addSecs(QTime(0, 0, 0).secsTo(duration)); beginEPG = beginEPG.addMSecs(-(QTime(0, 0, 0).msecsTo(beginEPG.time()) % 60000)); endEPG = beginEPG.addSecs(QTime(0, 0, 0).secsTo(durationEPG)); repeat &= ((1 << 7) - 1); return true; } return false; } DvbRecordingModel::DvbRecordingModel(DvbManager *manager_, QObject *parent) : QObject(parent), manager(manager_), hasPendingOperation(false) { sqlInit(QLatin1String("RecordingSchedule"), QStringList() << QLatin1String("Name") << QLatin1String("Channel") << QLatin1String("Begin") << QLatin1String("Duration") << QLatin1String("Repeat") << QLatin1String("Subheading") << QLatin1String("Details") << QLatin1String("beginEPG") << QLatin1String("endEPG") << QLatin1String("durationEPG") << QLatin1String("Priority") << QLatin1String("Disabled")); // we regularly recheck the status of the recordings // this way we can keep retrying if the device was busy / tuning failed startTimer(5000); // compatibility code QFile file(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/recordings.dvb")); if (!file.exists()) { return; } if (!file.open(QIODevice::ReadOnly)) { qCWarning(logDvb, "Cannot open file %s", qPrintable(file.fileName())); return; } DvbChannelModel *channelModel = manager->getChannelModel(); QDataStream stream(&file); stream.setVersion(QDataStream::Qt_4_4); int version; stream >> version; if (version != 0x4d848730) { // the older version didn't store a magic number ... file.seek(0); version = 0; } while (!stream.atEnd()) { DvbRecording recording; recording.disabled = false; QString channelName; stream >> channelName; recording.channel = channelModel->findChannelByName(channelName); stream >> recording.beginEPG; recording.begin = recording.beginEPG.toUTC(); recording.beginEPG = recording.beginEPG.toLocalTime(); stream >> recording.duration; recording.durationEPG = recording.duration; QDateTime end; stream >> end; if (version != 0) { stream >> recording.repeat; } stream >> recording.subheading; stream >> recording.details; if (stream.status() != QDataStream::Ok) { qCWarning(logDvb, "Invalid recordings in file %s", qPrintable(file.fileName())); break; } addRecording(recording); } if (!file.remove()) { qCWarning(logDvb, "Cannot remove file %s", qPrintable(file.fileName())); } } DvbRecordingModel::~DvbRecordingModel() { if (hasPendingOperation) { qCWarning(logDvb, "Illegal recursive call"); } sqlFlush(); } bool DvbRecordingModel::hasRecordings() const { return !recordings.isEmpty(); } bool DvbRecordingModel::hasActiveRecordings() const { return !recordingFiles.isEmpty(); } DvbSharedRecording DvbRecordingModel::findRecordingByKey(const SqlKey &sqlKey) const { return recordings.value(sqlKey); } DvbRecording DvbRecordingModel::getCurrentRecording() { return currentRecording; } void DvbRecordingModel::setCurrentRecording(DvbRecording _currentRecording) { currentRecording = _currentRecording; } QMap DvbRecordingModel::getRecordings() const { return recordings; } QList DvbRecordingModel::getUnwantedRecordings() const { return unwantedRecordings; } DvbSharedRecording DvbRecordingModel::addRecording(DvbRecording &recording, bool checkForRecursion) { if (checkForRecursion) { if (hasPendingOperation) { qCWarning(logDvb, "Illegal recursive call"); return DvbSharedRecording(); } EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation); } if (!recording.validate()) { qCWarning(logDvb, "Invalid recording"); return DvbSharedRecording(); } recording.setSqlKey(sqlFindFreeKey(recordings)); if (!updateStatus(recording)) { return DvbSharedRecording(); } DvbSharedRecording newRecording(new DvbRecording(recording)); recordings.insert(*newRecording, newRecording); sqlInsert(*newRecording); emit recordingAdded(newRecording); return newRecording; } void DvbRecordingModel::updateRecording(DvbSharedRecording recording, DvbRecording &modifiedRecording) { if (hasPendingOperation) { qCWarning(logDvb, "Illegal recursive call"); return; } EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation); if (!recording.isValid() || (recordings.value(*recording) != recording) || !modifiedRecording.validate()) { qCWarning(logDvb, "Invalid recording"); return; } modifiedRecording.setSqlKey(*recording); if (!updateStatus(modifiedRecording)) { recordings.remove(*recording); recordingFiles.remove(*recording); sqlRemove(*recording); emit recordingRemoved(recording); return; } emit recordingAboutToBeUpdated(recording); *const_cast(recording.constData()) = modifiedRecording; sqlUpdate(*recording); emit recordingUpdated(recording); } void DvbRecordingModel::removeRecording(DvbSharedRecording recording) { if (hasPendingOperation) { qCWarning(logDvb, "Illegal recursive call"); return; } EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation); if (!recording.isValid() || (recordings.value(*recording) != recording)) { qCWarning(logDvb, "Invalid recording"); return; } recordings.remove(*recording); recordingFiles.remove(*recording); sqlRemove(*recording); emit recordingRemoved(recording); executeActionAfterRecording(*recording); findNewRecordings(); removeDuplicates(); disableConflicts(); } void DvbRecordingModel::disableLessImportant(DvbSharedRecording &recording1, DvbSharedRecording &recording2) { if (recording1->priority < recording2->priority) { DvbRecording rec1 = *(recording1.constData()); rec1.disabled = true; qCWarning(logDvb, "Disabled %s because %s has more priority", qPrintable(recording1->name), qPrintable(recording2->name)); } if (recording2->priority < recording1->priority) { DvbRecording rec2 = *(recording1.constData()); rec2.disabled = true; qCWarning(logDvb, "Disabled %s because %s has more priority", qPrintable(recording2->name), qPrintable(recording1->name)); } } void DvbRecordingModel::addToUnwantedRecordings(DvbSharedRecording recording) { unwantedRecordings.append(recording); qCDebug(logDvb, "executed %s", qPrintable(recording->name)); } void DvbRecordingModel::executeActionAfterRecording(DvbRecording recording) { QString stopCommand = manager->getActionAfterRecording(); stopCommand.replace("%filename", recording.filename); if (!stopCommand.isEmpty()) { QProcess* child = new QProcess(); child->start(stopCommand); qCWarning(logDvb, "Not executing command after recording"); } qCDebug(logDvb, "executed."); } void DvbRecordingModel::removeDuplicates() { QList recordingList = QList(); DvbEpgModel *epgModel = manager->getEpgModel(); if (!epgModel) return; QMap recordingMap = epgModel->getRecordings(); foreach(DvbSharedRecording key, recordings.values()) { recordingList.append(key); } int i = 0; foreach(DvbSharedRecording rec1, recordingList) { int j = 0; DvbRecording loopEntry1 = *rec1; foreach(DvbSharedRecording rec2, recordingList) { DvbRecording loopEntry2 = *rec2; if (i < j) { if (loopEntry1.begin == loopEntry2.begin && loopEntry1.duration == loopEntry2.duration && loopEntry1.channel->name == loopEntry2.channel->name && loopEntry1.name == loopEntry2.name) { recordings.remove(recordings.key(rec1)); recordingMap.remove(rec1); qCDebug(logDvb, "Removed. %s", qPrintable(loopEntry1.name)); } } j = j + 1; } i = i + 1; } epgModel->setRecordings(recordingMap); qCDebug(logDvb, "executed."); } bool DvbRecordingModel::existsSimilarRecording(DvbEpgEntry recording) { bool found = false; DvbEpgEntry entry = recording; DvbEpgModel *epgModel = manager->getEpgModel(); if (!epgModel) return found; QMap recordingMap = epgModel->getRecordings(); foreach(DvbSharedRecording key, recordingMap.keys()) { DvbEpgEntry loopEntry = *(recordingMap.value(key)); int loopLength = 60 * 60 * loopEntry.duration.hour() + 60 * loopEntry.duration.minute() + loopEntry.duration.second(); int length = 60 * 60 * entry.duration.hour() + 60 * entry.duration.minute() + entry.duration.second(); QDateTime end = entry.begin.addSecs(length); QDateTime loopEnd = loopEntry.begin.addSecs(loopLength); if (QString::compare(entry.channel->name, loopEntry.channel->name) == 0) { // Is included in an existing recording if (entry.begin <= loopEntry.begin && end >= loopEnd) { found = true; break; // Includes an existing recording } else if (entry.begin >= loopEntry.begin && end <= loopEnd) { found = true; break; } } } QList unwantedRecordingsList = manager->getRecordingModel()->getUnwantedRecordings(); foreach(DvbSharedRecording unwanted, unwantedRecordingsList) { DvbRecording loopEntry = *unwanted; if (QString::compare(entry.begin.toString(), ((loopEntry.begin.addSecs(manager->getBeginMargin()))).toString()) == 0 && QString::compare(entry.channel->name, loopEntry.channel->name) == 0 && QString::compare((entry.duration).toString(), loopEntry.duration.addSecs(- manager->getBeginMargin() - manager->getEndMargin()).toString()) == 0) { qCDebug(logDvb, "Found from unwanteds %s", qPrintable(loopEntry.name)); found = true; break; } } return found; } void DvbRecordingModel::disableConflicts() { int maxSize = 1; // manager->getDeviceConfigs().size(); // foreach(DvbDeviceConfig config, manager->getDeviceConfigs()) // { // maxSize = maxSize + config.numberOfTuners; // } QList recordingList = QList(); foreach(DvbSharedRecording rec, recordings.values()) { if (!(rec->disabled)) { recordingList.append(rec); } } foreach(DvbSharedRecording rec1, recordingList) { QList conflictList = QList(); conflictList.append(rec1); foreach(DvbSharedRecording rec2, recordingList) { if (isInConflictWithAll(rec2, conflictList)) { conflictList.append(rec2); qCDebug(logDvb, "conflict: '%s' '%s' and '%s' '%s'", qPrintable(rec1->name), qPrintable(rec1->begin.toString()), qPrintable(rec2->name), qPrintable(rec2->begin.toString())); } } if (conflictList.size() > maxSize) { disableLeastImportants(conflictList); } } } bool DvbRecordingModel::areInConflict(DvbSharedRecording recording1, DvbSharedRecording recording2) { int length1 = 60 * 60 * recording1->duration.hour() + 60 * recording1->duration.minute() + recording1->duration.second(); QDateTime end1 = recording1->begin.addSecs(length1); int length2 = 60 * 60 * recording2->duration.hour() + 60 * recording2->duration.minute() + recording2->duration.second(); QDateTime end2 = recording2->begin.addSecs(length2); if (!recording1->disabled && !recording1->disabled) { if (recording1->channel->transportStreamId != recording2->channel->transportStreamId) { if (recording2->begin > recording1->begin && recording2->begin < end1) { return true; } if (recording1->begin > recording2->begin && recording1->begin < end2) { return true; } } } return false; } bool DvbRecordingModel::isInConflictWithAll(DvbSharedRecording rec, QList recList) { foreach(DvbSharedRecording listRec, recList) { if (!areInConflict(rec, listRec)) { return false; } } return true; } int DvbRecordingModel::getNumberOfLeastImportants(QList recList) { DvbSharedRecording leastImportantShared = getLeastImportant(recList); int leastImportance = leastImportantShared->priority; int count = 0; foreach(DvbSharedRecording listRecShared, recList) { if (listRecShared->priority == leastImportance) { count = count + 1; } } return count; } DvbSharedRecording DvbRecordingModel::getLeastImportant(QList recList) { DvbSharedRecording leastImportant = recList.value(0); foreach(DvbSharedRecording listRec, recList) { qCDebug(logDvb, "name and priority %s %s", qPrintable(listRec->name), qPrintable(listRec->priority)); if (listRec->priority < leastImportant->priority) { leastImportant = listRec; } } qCDebug(logDvb, "least important: %s", qPrintable(leastImportant->name)); return leastImportant; } void DvbRecordingModel::disableLeastImportants(QList recList) { int numberOfLeastImportants = getNumberOfLeastImportants(recList); if (numberOfLeastImportants < recList.size()) { DvbSharedRecording leastImportantShared = getLeastImportant(recList); int leastImportance = leastImportantShared->priority; foreach(DvbSharedRecording listRecShared, recList) { DvbRecording listRec = *(listRecShared.constData()); if (listRecShared->priority == leastImportance) { listRec.disabled = true; updateRecording(listRecShared, listRec); qCDebug(logDvb, "disabled: %s %s", qPrintable(listRec.name), qPrintable(listRec.begin.toString())); } } } } void DvbRecordingModel::findNewRecordings() { DvbEpgModel *epgModel = manager->getEpgModel(); if (!epgModel) return; QMap epgMap = epgModel->getEntries(); foreach(DvbEpgEntryId key, epgMap.keys()) { DvbEpgEntry entry = *(epgMap.value(key)); - QString title = entry.title; + QString title = entry.title(FIRST_LANG); QStringList regexList = manager->getRecordingRegexList(); int i = 0; foreach(QString regex, regexList) { QRegExp recordingRegex = QRegExp(regex); if (!recordingRegex.isEmpty()) { if (recordingRegex.indexIn(title) != -1) { if (!DvbRecordingModel::existsSimilarRecording(*epgMap.value(key))) { int priority = manager->getRecordingRegexPriorityList().value(i); epgModel->scheduleProgram(epgMap.value(key), manager->getBeginMargin(), manager->getEndMargin(), false, priority); qCDebug(logDvb, "scheduled %s", qPrintable(title)); } } } i = i + 1; } } qCDebug(logDvb, "executed."); } void DvbRecordingModel::timerEvent(QTimerEvent *event) { Q_UNUSED(event) QDateTime currentDateTime = QDateTime::currentDateTime().toUTC(); foreach (const DvbSharedRecording &recording, recordings) { if (recording->end <= currentDateTime) { DvbRecording modifiedRecording = *recording; updateRecording(recording, modifiedRecording); } } foreach (const DvbSharedRecording &recording, recordings) { if ((recording->status != DvbRecording::Recording) && (recording->begin <= currentDateTime)) { DvbRecording modifiedRecording = *recording; updateRecording(recording, modifiedRecording); } } } void DvbRecordingModel::bindToSqlQuery(SqlKey sqlKey, QSqlQuery &query, int index) const { DvbSharedRecording recording = recordings.value(sqlKey); if (!recording.isValid()) { qCWarning(logDvb, "Invalid recording"); return; } query.bindValue(index++, recording->name); query.bindValue(index++, recording->channel->name); query.bindValue(index++, recording->begin.toString(Qt::ISODate) + QLatin1Char('Z')); query.bindValue(index++, recording->duration.toString(Qt::ISODate)); query.bindValue(index++, recording->repeat); query.bindValue(index++, recording->subheading); query.bindValue(index++, recording->details); query.bindValue(index++, recording->beginEPG.toString(Qt::ISODate)); query.bindValue(index++, recording->endEPG.toString(Qt::ISODate)); query.bindValue(index++, recording->durationEPG.toString(Qt::ISODate)); query.bindValue(index++, recording->priority); query.bindValue(index++, recording->disabled); } bool DvbRecordingModel::insertFromSqlQuery(SqlKey sqlKey, const QSqlQuery &query, int index) { DvbRecording *recording = new DvbRecording(); DvbSharedRecording newRecording(recording); recording->name = query.value(index++).toString(); recording->channel = manager->getChannelModel()->findChannelByName(query.value(index++).toString()); recording->begin = QDateTime::fromString(query.value(index++).toString(), Qt::ISODate).toUTC(); recording->duration = QTime::fromString(query.value(index++).toString(), Qt::ISODate); recording->repeat = query.value(index++).toInt(); recording->subheading = query.value(index++).toString(); recording->details = query.value(index++).toString(); recording->beginEPG = QDateTime::fromString(query.value(index++).toString(), Qt::ISODate).toLocalTime(); recording->endEPG = QDateTime::fromString(query.value(index++).toString(), Qt::ISODate).toLocalTime(); recording->durationEPG = QTime::fromString(query.value(index++).toString(), Qt::ISODate); recording->priority = query.value(index++).toInt(); recording->disabled = query.value(index++).toBool(); if (recording->validate()) { recording->setSqlKey(sqlKey); recordings.insert(*newRecording, newRecording); return true; } return false; } /* * Returns -1 if no upcoming recordings. */ int DvbRecordingModel::getSecondsUntilNextRecording() const { signed long timeUntil = -1; foreach (const DvbSharedRecording &recording, recordings) { DvbRecording rec = *recording; int length = 60 * 60 * rec.duration.hour() + 60 * rec.duration.minute() + rec.duration.second(); QDateTime end = rec.begin.addSecs(length); if (rec.disabled || end < QDateTime::currentDateTime().toUTC()) { continue; } if (end > QDateTime::currentDateTime().toUTC() && rec.begin <= QDateTime::currentDateTime().toUTC()) { timeUntil = 0; qCDebug(logDvb, "Rec ongoing %s", qPrintable(rec.name)); break; } if (rec.begin > QDateTime::currentDateTime().toUTC()) { if (timeUntil == -1 || timeUntil > rec.begin.toTime_t() - QDateTime::currentDateTime().toUTC().toTime_t()) { timeUntil = rec.begin.toTime_t() - QDateTime::currentDateTime().toUTC().toTime_t(); } } } qCDebug(logDvb, "returned TRUE %ld", timeUntil); return timeUntil; } bool DvbRecordingModel::isScanWhenIdle() const { return manager->isScanWhenIdle(); } bool DvbRecordingModel::shouldWeScanChannels() const { int numberOfChannels = manager->getChannelModel()->getChannels().size(); int idleTime = 1000 * 3600 + 1; // TODO if (idleTime > 1000 * 3600) { if (DvbRecordingModel::getSecondsUntilNextRecording() > numberOfChannels * 10) { if (DvbRecordingModel::isScanWhenIdle()) { qCDebug(logDvb, "Scan on Idle enabled"); return true; } } } return false; } void delay(int seconds) { QTime dieTime= QTime::currentTime().addSecs(seconds); while (QTime::currentTime() < dieTime) QCoreApplication::processEvents(QEventLoop::AllEvents, 100); qCInfo(logDvb, "Delayed for %d seconds", seconds); } void DvbRecordingModel::scanChannels() { qCDebug(logDvb, "auto-scan channels"); if (shouldWeScanChannels()) { DvbChannelModel *channelModel = manager->getChannelModel(); QMap channelMap = channelModel->getChannels(); foreach (int channelInt, channelMap.keys()) { DvbSharedChannel channel; if (channelInt > 0) { channel = channelModel->findChannelByNumber(channelInt); } if (channel.isValid()) { // TODO update tab qCDebug(logDvb, "Executed %s", qPrintable(channel->name)); manager->getLiveView()->playChannel(channel); delay(5); } } } } bool DvbRecordingModel::updateStatus(DvbRecording &recording) { QDateTime currentDateTimeLocal = QDateTime::currentDateTime(); QDateTime currentDateTime = currentDateTimeLocal.toUTC(); if (recording.end <= currentDateTime) { if (recording.repeat == 0) { return false; } recordingFiles.remove(recording); // take care of DST switches QDateTime beginLocal = recording.begin.toLocalTime(); QDateTime endLocal = recording.end.toLocalTime(); int days = endLocal.daysTo(currentDateTimeLocal); if (endLocal.addDays(days) <= currentDateTimeLocal) { ++days; } // QDate::dayOfWeek() and our dayOfWeek differ by one int dayOfWeek = (beginLocal.date().dayOfWeek() - 1 + days); for (int j = 0; j < 7; ++j) { if ((recording.repeat & (1 << (dayOfWeek % 7))) != 0) { break; } ++days; ++dayOfWeek; } recording.begin = beginLocal.addDays(days).toUTC(); recording.end = recording.begin.addSecs(QTime().secsTo(recording.duration)); } if (recording.begin <= currentDateTime) { QExplicitlySharedDataPointer recordingFile = recordingFiles.value(recording); if (recordingFile.constData() == NULL) { recordingFile = new DvbRecordingFile(manager); recordingFiles.insert(recording, recordingFile); } if (recordingFile->start(recording)) { recording.status = DvbRecording::Recording; } else { recording.status = DvbRecording::Error; } } else { recording.status = DvbRecording::Inactive; recordingFiles.remove(recording); } return true; } DvbRecordingFile::DvbRecordingFile(DvbManager *manager_) : manager(manager_), device(NULL), pmtValid(false) { connect(&pmtFilter, SIGNAL(pmtSectionChanged(QByteArray)), this, SLOT(pmtSectionChanged(QByteArray))); connect(&patPmtTimer, SIGNAL(timeout()), this, SLOT(insertPatPmt())); } DvbRecordingFile::~DvbRecordingFile() { stop(); } bool DvbRecordingFile::start(DvbRecording &recording) { if (recording.disabled) { return false; } if (!file.isOpen()) { QString folder = manager->getRecordingFolder(); QDate currentDate = QDate::currentDate(); QTime currentTime = QTime::currentTime(); QString filename = manager->getNamingFormat(); filename = filename.replace("%year", currentDate.toString("yyyy")); filename = filename.replace("%month", currentDate.toString("MM")); filename = filename.replace("%day", currentDate.toString("dd")); filename = filename.replace("%hour", currentTime.toString("hh")); filename = filename.replace("%min", currentTime.toString("mm")); filename = filename.replace("%sec", currentTime.toString("ss")); filename = filename.replace("%channel", recording.channel->name); filename = filename.replace("%title", QString(recording.name)); filename = filename.replace(QLatin1Char('/'), QLatin1Char('_')); if (filename == "") { filename = QString(recording.name); } QString path = folder + QLatin1Char('/') + filename; for (int attempt = 0; attempt < 100; ++attempt) { if (attempt == 0) { file.setFileName(path + QLatin1String(".m2t")); recording.filename = filename + QLatin1String(".m2t"); } else { file.setFileName(path + QLatin1Char('-') + QString::number(attempt) + QLatin1String(".m2t")); recording.filename = filename + QLatin1Char('-') + QString::number(attempt) + QLatin1String(".m2t"); } if (file.exists()) { continue; } if (file.open(QIODevice::WriteOnly)) { break; } else { qCWarning(logDvb, "Cannot open file %s. Error: %d", qPrintable(file.fileName()), errno); } if ((attempt == 0) && !QDir(folder).exists()) { if (QDir().mkpath(folder)) { attempt = -1; continue; } else { qCWarning(logDvb, "Cannot create folder %s", qPrintable(folder)); } } if (folder != QDir::homePath()) { folder = QDir::homePath(); path = folder + QLatin1Char('/') + QString(recording.name).replace(QLatin1Char('/'), QLatin1Char('_')); attempt = -1; continue; } break; } if (manager->createInfoFile()) { QString infoFile = path + ".txt"; QFile file(infoFile); if (file.open(QIODevice::ReadWrite)) { QTextStream stream(&file); stream << "EPG info" << endl; stream << "Title: " + QString(recording.name) << endl; stream << "Description: " + QString(recording.subheading) << endl; //stream << "Details: " + QString(recording.details) << endl; // Details is almost always empty stream << "Channel: " + QString(recording.channel->name) << endl; stream << "Date: " + recording.beginEPG.toLocalTime().toString("yyyy-MM-dd") << endl; stream << "Start time: " + recording.beginEPG.toLocalTime().toString("hh:mm:ss") << endl; stream << "Duration: " + recording.durationEPG.toString("HH:mm:ss") << endl; } } if (!file.isOpen()) { qCWarning(logDvb, "Cannot open file %s", qPrintable(file.fileName())); return false; } } if (device == NULL) { channel = recording.channel; device = manager->requestDevice(channel->source, channel->transponder, DvbManager::Prioritized); if (device == NULL) { qCWarning(logDvb, "Cannot find a suitable device"); return false; } connect(device, SIGNAL(stateChanged()), this, SLOT(deviceStateChanged())); pmtFilter.setProgramNumber(channel->serviceId); device->addSectionFilter(channel->pmtPid, &pmtFilter); pmtSectionData = channel->pmtSectionData; patGenerator.initPat(channel->transportStreamId, channel->serviceId, channel->pmtPid); if (channel->isScrambled && !pmtSectionData.isEmpty()) { device->startDescrambling(pmtSectionData, this); } } manager->getRecordingModel()->setCurrentRecording(recording); return true; } void DvbRecordingFile::stop() { if (device != NULL) { if (channel->isScrambled && !pmtSectionData.isEmpty()) { device->stopDescrambling(pmtSectionData, this); } foreach (int pid, pids) { device->removePidFilter(pid, this); } device->removeSectionFilter(channel->pmtPid, &pmtFilter); disconnect(device, SIGNAL(stateChanged()), this, SLOT(deviceStateChanged())); manager->releaseDevice(device, DvbManager::Prioritized); device = NULL; } pmtValid = false; patPmtTimer.stop(); patGenerator.reset(); pmtGenerator.reset(); pmtSectionData.clear(); pids.clear(); buffers.clear(); file.close(); channel = DvbSharedChannel(); manager->getRecordingModel()->executeActionAfterRecording(manager->getRecordingModel()->getCurrentRecording()); manager->getRecordingModel()->findNewRecordings(); manager->getRecordingModel()->removeDuplicates(); manager->getRecordingModel()->disableConflicts(); } void DvbRecordingFile::deviceStateChanged() { if (device->getDeviceState() == DvbDevice::DeviceReleased) { foreach (int pid, pids) { device->removePidFilter(pid, this); } device->removeSectionFilter(channel->pmtPid, &pmtFilter); disconnect(device, SIGNAL(stateChanged()), this, SLOT(deviceStateChanged())); if (channel->isScrambled && !pmtSectionData.isEmpty()) { device->stopDescrambling(pmtSectionData, this); } device = manager->requestDevice(channel->source, channel->transponder, DvbManager::Prioritized); if (device != NULL) { connect(device, SIGNAL(stateChanged()), this, SLOT(deviceStateChanged())); device->addSectionFilter(channel->pmtPid, &pmtFilter); foreach (int pid, pids) { device->addPidFilter(pid, this); } if (channel->isScrambled && !pmtSectionData.isEmpty()) { device->startDescrambling(pmtSectionData, this); } } else { // TODO stop(); } } } void DvbRecordingFile::pmtSectionChanged(const QByteArray &pmtSectionData_) { pmtSectionData = pmtSectionData_; DvbPmtSection pmtSection(pmtSectionData); DvbPmtParser pmtParser(pmtSection); int pcrPid = pmtSection.pcrPid(); QSet newPids; if (pmtParser.videoPid != -1) { newPids.insert(pmtParser.videoPid); } for (int i = 0; i < pmtParser.audioPids.size(); ++i) { newPids.insert(pmtParser.audioPids.at(i).first); } for (int i = 0; i < pmtParser.subtitlePids.size(); ++i) { newPids.insert(pmtParser.subtitlePids.at(i).first); } if (pmtParser.teletextPid != -1) { newPids.insert(pmtParser.teletextPid); } /* check PCR PID is set */ if (pcrPid != 0x1fff) { /* Check not already in list */ if (!newPids.contains(pcrPid)) newPids.insert(pcrPid); } for (int i = 0; i < pids.size(); ++i) { int pid = pids.at(i); if (!newPids.remove(pid)) { device->removePidFilter(pid, this); pids.removeAt(i); --i; } } foreach (int pid, newPids) { device->addPidFilter(pid, this); pids.append(pid); } pmtGenerator.initPmt(channel->pmtPid, pmtSection, pids); if (!pmtValid) { pmtValid = true; file.write(patGenerator.generatePackets()); file.write(pmtGenerator.generatePackets()); foreach (const QByteArray &buffer, buffers) { file.write(buffer); } buffers.clear(); patPmtTimer.start(500); } insertPatPmt(); if (channel->isScrambled) { device->startDescrambling(pmtSectionData, this); } } void DvbRecordingFile::insertPatPmt() { if (!pmtValid) { pmtSectionChanged(channel->pmtSectionData); return; } file.write(patGenerator.generatePackets()); file.write(pmtGenerator.generatePackets()); } void DvbRecordingFile::processData(const char data[188]) { if (!pmtValid) { if (!patPmtTimer.isActive()) { patPmtTimer.start(1000); QByteArray nextBuffer; nextBuffer.reserve(348 * 188); buffers.append(nextBuffer); } QByteArray &buffer = buffers.last(); buffer.append(data, 188); if (buffer.size() >= (348 * 188)) { QByteArray nextBuffer; nextBuffer.reserve(348 * 188); buffers.append(nextBuffer); } return; } file.write(data, 188); } diff --git a/src/dvb/dvbtab.cpp b/src/dvb/dvbtab.cpp index 8b540d3..f8ddebd 100644 --- a/src/dvb/dvbtab.cpp +++ b/src/dvb/dvbtab.cpp @@ -1,476 +1,476 @@ /* * dvbtab.cpp * * Copyright (C) 2007-2011 Christoph Pfister * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "../log.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../osdwidget.h" #include "dvbchanneldialog.h" #include "dvbconfigdialog.h" #include "dvbepg.h" #include "dvbepgdialog.h" #include "dvbliveview.h" #include "dvbmanager.h" #include "dvbrecordingdialog.h" #include "dvbscandialog.h" #include "dvbtab.h" class DvbTimeShiftCleaner : public QThread { public: explicit DvbTimeShiftCleaner(QObject *parent) : QThread(parent) { } ~DvbTimeShiftCleaner() { wait(); } void remove(const QString &path_, const QStringList &files_); private: void run(); QString path; QStringList files; }; void DvbTimeShiftCleaner::remove(const QString &path_, const QStringList &files_) { path = path_; files = files_; start(); } void DvbTimeShiftCleaner::run() { // delete files asynchronously because it may block for several seconds foreach (const QString &file, files) { QFile::remove(path + QLatin1Char('/') + file); } } DvbTab::DvbTab(QMenu *menu, KActionCollection *collection, MediaWidget *mediaWidget_) : mediaWidget(mediaWidget_) { manager = new DvbManager(mediaWidget, this); QAction *channelsAction = new QAction(QIcon::fromTheme(QLatin1String("video-television"), QIcon(":video-television")), i18n("Channels"), this); channelsAction->setShortcut(Qt::Key_C); connect(channelsAction, SIGNAL(triggered(bool)), this, SLOT(showChannelDialog())); menu->addAction(collection->addAction(QLatin1String("dvb_channels"), channelsAction)); QAction *epgAction = new QAction(QIcon::fromTheme(QLatin1String("view-list-details"), QIcon(":view-list-details")), i18n("Program Guide"), this); epgAction->setShortcut(Qt::Key_G); connect(epgAction, SIGNAL(triggered(bool)), this, SLOT(toggleEpgDialog())); menu->addAction(collection->addAction(QLatin1String("dvb_epg"), epgAction)); QAction *osdAction = new QAction(QIcon::fromTheme(QLatin1String("dialog-information"), QIcon(":dialog-information")), i18n("OSD"), this); osdAction->setShortcut(Qt::Key_O); connect(osdAction, SIGNAL(triggered(bool)), manager->getLiveView(), SLOT(toggleOsd())); menu->addAction(collection->addAction(QLatin1String("dvb_osd"), osdAction)); QAction *recordingsAction = new QAction(QIcon::fromTheme(QLatin1String("view-pim-calendar"), QIcon(":view-pim-calendar")), i18nc("dialog", "Recording Schedule"), this); recordingsAction->setShortcut(Qt::Key_R); connect(recordingsAction, SIGNAL(triggered(bool)), this, SLOT(showRecordingDialog())); menu->addAction(collection->addAction(QLatin1String("dvb_recordings"), recordingsAction)); menu->addSeparator(); instantRecordAction = new QAction(QIcon::fromTheme(QLatin1String("document-save"), QIcon(":document-save")), i18n("Instant Record"), this); instantRecordAction->setCheckable(true); connect(instantRecordAction, SIGNAL(triggered(bool)), this, SLOT(instantRecord(bool))); menu->addAction(collection->addAction(QLatin1String("dvb_instant_record"), instantRecordAction)); menu->addSeparator(); QAction *configureAction = new QAction(QIcon::fromTheme(QLatin1String("configure"), QIcon(":configure")), i18nc("@action:inmenu", "Configure Television..."), this); connect(configureAction, SIGNAL(triggered()), this, SLOT(configureDvb())); menu->addAction(collection->addAction(QLatin1String("settings_dvb"), configureAction)); connect(manager->getLiveView(), SIGNAL(previous()), this, SLOT(previousChannel())); connect(manager->getLiveView(), SIGNAL(next()), this, SLOT(nextChannel())); connect(manager->getRecordingModel(), SIGNAL(recordingRemoved(DvbSharedRecording)), this, SLOT(recordingRemoved(DvbSharedRecording))); QBoxLayout *boxLayout = new QHBoxLayout(this); boxLayout->setMargin(0); splitter = new QSplitter(this); boxLayout->addWidget(splitter); QWidget *leftWidget = new QWidget(splitter); QBoxLayout *leftLayout = new QVBoxLayout(leftWidget); boxLayout = new QHBoxLayout(); boxLayout->addWidget(new QLabel(i18n("Search:"))); QLineEdit *lineEdit = new QLineEdit(leftWidget); lineEdit->setClearButtonEnabled(true); boxLayout->addWidget(lineEdit); leftLayout->addLayout(boxLayout); channelView = new DvbChannelView(leftWidget); channelView->setContextMenuPolicy(Qt::ActionsContextMenu); channelProxyModel = new DvbChannelTableModel(this); channelView->setModel(channelProxyModel); channelView->setRootIsDecorated(false); if (!channelView->header()->restoreState(QByteArray::fromBase64( KSharedConfig::openConfig()->group("DVB").readEntry("ChannelViewState", QByteArray())))) { channelView->sortByColumn(0, Qt::AscendingOrder); } channelView->setSortingEnabled(true); channelView->addEditAction(); connect(channelView, SIGNAL(activated(QModelIndex)), this, SLOT(playChannel(QModelIndex))); channelProxyModel->setChannelModel(manager->getChannelModel()); connect(lineEdit, SIGNAL(textChanged(QString)), channelProxyModel, SLOT(setFilter(QString))); manager->setChannelView(channelView); leftLayout->addWidget(channelView); boxLayout = new QHBoxLayout(); QToolButton *toolButton = new QToolButton(leftWidget); toolButton->setDefaultAction(configureAction); toolButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); boxLayout->addWidget(toolButton); toolButton = new QToolButton(leftWidget); toolButton->setDefaultAction(channelsAction); toolButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); boxLayout->addWidget(toolButton); toolButton = new QToolButton(leftWidget); toolButton->setDefaultAction(epgAction); toolButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); boxLayout->addWidget(toolButton); toolButton = new QToolButton(leftWidget); toolButton->setDefaultAction(recordingsAction); toolButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); boxLayout->addWidget(toolButton); toolButton = new QToolButton(leftWidget); toolButton->setDefaultAction(instantRecordAction); toolButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); boxLayout->addWidget(toolButton); leftLayout->addLayout(boxLayout); QWidget *mediaContainer = new QWidget(splitter); mediaLayout = new QHBoxLayout(mediaContainer); mediaLayout->setMargin(0); splitter->setStretchFactor(1, 1); connect(mediaWidget, SIGNAL(osdKeyPressed(int)), this, SLOT(osdKeyPressed(int))); connect(&osdChannelTimer, SIGNAL(timeout()), this, SLOT(tuneOsdChannel())); lastChannel = KSharedConfig::openConfig()->group("DVB").readEntry("LastChannel"); splitter->restoreState(QByteArray::fromBase64( KSharedConfig::openConfig()->group("DVB").readEntry("TabSplitterState", QByteArray()))); timeShiftCleaner = new DvbTimeShiftCleaner(this); QTimer *timer = new QTimer(this); timer->start(30000); connect(timer, SIGNAL(timeout()), this, SLOT(cleanTimeShiftFiles())); } DvbTab::~DvbTab() { KSharedConfig::openConfig()->group("DVB").writeEntry("TabSplitterState", splitter->saveState().toBase64()); KSharedConfig::openConfig()->group("DVB").writeEntry("ChannelViewState", channelView->header()->saveState().toBase64()); if (!currentChannel.isEmpty()) { lastChannel = currentChannel; } KSharedConfig::openConfig()->group("DVB").writeEntry("LastChannel", lastChannel); } void DvbTab::playChannel(const QString &nameOrNumber) { DvbChannelModel *channelModel = manager->getChannelModel(); DvbSharedChannel channel; int number = nameOrNumber.toInt(); if (number > 0) { channel = channelModel->findChannelByNumber(number); } if (!channel.isValid()) { channel = channelModel->findChannelByName(nameOrNumber); } if (channel.isValid()) { playChannel(channel, channelProxyModel->find(channel)); } } void DvbTab::playLastChannel() { if (!manager->getLiveView()->getChannel().isValid() && !currentChannel.isEmpty()) { lastChannel = currentChannel; } DvbSharedChannel channel = manager->getChannelModel()->findChannelByName(lastChannel); if (channel.isValid()) { playChannel(channel, channelProxyModel->find(channel)); } } void DvbTab::toggleOsd() { manager->getLiveView()->toggleOsd(); } void DvbTab::toggleInstantRecord() { instantRecordAction->trigger(); } void DvbTab::enableDvbDump() { manager->enableDvbDump(); } void DvbTab::osdKeyPressed(int key) { if ((key >= Qt::Key_0) && (key <= Qt::Key_9)) { osdChannel += QString::number(key - Qt::Key_0); osdChannelTimer.start(1500); mediaWidget->getOsdWidget()->showText(i18nc("osd", "Channel: %1_", osdChannel), 1500); } } void DvbTab::mayCloseApplication(bool *ok, QWidget *parent) { if (*ok) { DvbRecordingModel *recordingModel = manager->getRecordingModel(); if (recordingModel->hasActiveRecordings()) { if (KMessageBox::warningYesNo(parent, i18nc("message box", "Kaffeine is currently recording programs.\n" "Do you really want to close the application?")) != KMessageBox::Yes) { *ok = false; } return; } if (recordingModel->hasRecordings()) { if (KMessageBox::questionYesNo(parent, i18nc("message box", "Kaffeine has scheduled recordings.\n" "Do you really want to close the application?"), QString(), KStandardGuiItem::yes(), KStandardGuiItem::no(), QLatin1String("ScheduledRecordings")) != KMessageBox::Yes) { *ok = false; } return; } } } void DvbTab::showChannelDialog() { QDialog *dialog = new DvbScanDialog(manager, this); dialog->setAttribute(Qt::WA_DeleteOnClose, true); dialog->setModal(true); dialog->show(); } void DvbTab::showRecordingDialog() { DvbRecordingDialog::showDialog(manager, this); } void DvbTab::toggleEpgDialog() { if (epgDialog.isNull()) { epgDialog = new DvbEpgDialog(manager, this); epgDialog->setAttribute(Qt::WA_DeleteOnClose, true); epgDialog->setCurrentChannel(manager->getLiveView()->getChannel()); epgDialog->setModal(false); epgDialog->show(); } else { epgDialog->deleteLater(); epgDialog = NULL; } } void DvbTab::instantRecord(bool checked) { if (checked) { const DvbSharedChannel &channel = manager->getLiveView()->getChannel(); if (!channel.isValid()) { instantRecordAction->setChecked(false); return; } DvbRecording recording; QList epgEntries = manager->getEpgModel()->getCurrentNext(channel); if (!epgEntries.isEmpty()) { - recording.name = epgEntries.at(0)->title; + recording.name = epgEntries.at(0)->title(); } if (recording.name.isEmpty()) { recording.name = (channel->name + QTime::currentTime().toString(QLatin1String("-hhmmss"))); } recording.channel = channel; recording.begin = QDateTime::currentDateTime().toUTC(); recording.duration = QTime(12, 0); instantRecording = manager->getRecordingModel()->addRecording(recording); mediaWidget->getOsdWidget()->showText(i18nc("osd", "Instant Record Started"), 1500); } else { manager->getRecordingModel()->removeRecording(instantRecording); mediaWidget->getOsdWidget()->showText(i18nc("osd", "Instant Record Stopped"), 1500); } } void DvbTab::recordingRemoved(const DvbSharedRecording &recording) { if (instantRecording == recording) { instantRecording = DvbSharedRecording(); instantRecordAction->setChecked(false); mediaWidget->getOsdWidget()->showText(i18nc("osd", "Instant Record Stopped"), 1500); } } void DvbTab::configureDvb() { QDialog *dialog = new DvbConfigDialog(manager, this); dialog->setAttribute(Qt::WA_DeleteOnClose, true); dialog->setModal(true); dialog->show(); } void DvbTab::tuneOsdChannel() { int number = osdChannel.toInt(); osdChannel.clear(); osdChannelTimer.stop(); DvbSharedChannel channel = manager->getChannelModel()->findChannelByNumber(number); if (channel.isValid()) { playChannel(channel, channelProxyModel->find(channel)); } } void DvbTab::playChannel(const QModelIndex &index) { if (index.isValid()) { playChannel(channelProxyModel->value(index), index); } } void DvbTab::previousChannel() { QModelIndex index = channelView->currentIndex(); if (index.isValid()) { playChannel(index.sibling(index.row() - 1, index.column())); } } void DvbTab::nextChannel() { QModelIndex index = channelView->currentIndex(); if (index.isValid()) { playChannel(index.sibling(index.row() + 1, index.column())); } } void DvbTab::cleanTimeShiftFiles() { if (timeShiftCleaner->isRunning()) { return; } QDir dir(manager->getTimeShiftFolder()); QStringList entries = dir.entryList(QStringList(QLatin1String("TimeShift-*.m2t")), QDir::Files, QDir::Name); if (entries.count() < 2) { return; } entries.removeLast(); timeShiftCleaner->remove(dir.path(), entries); } void DvbTab::activate() { mediaLayout->addWidget(mediaWidget); mediaWidget->setFocus(); } void DvbTab::playChannel(const DvbSharedChannel &channel, const QModelIndex &index) { if (!channel.isValid()) { qCWarning(logDvb, "Channel is invalid"); return; } if (!currentChannel.isEmpty()) { lastChannel = currentChannel; } channelView->setCurrentIndex(index); currentChannel = channel->name; manager->getLiveView()->playChannel(channel); if (!epgDialog.isNull()) { epgDialog->setCurrentChannel(manager->getLiveView()->getChannel()); } }