diff --git a/mythtv/bindings/perl/MythTV.pm b/mythtv/bindings/perl/MythTV.pm index d094a4ccada..279f876de0a 100644 --- a/mythtv/bindings/perl/MythTV.pm +++ b/mythtv/bindings/perl/MythTV.pm @@ -116,7 +116,7 @@ package MythTV; # schema version supported in the main code. We need to check that the schema # version in the database is as expected by the bindings, which are expected # to be kept in sync with the main code. - our $SCHEMA_VERSION = "1379"; + our $SCHEMA_VERSION = "1380"; # NUMPROGRAMLINES is defined in mythtv/libs/libmythtv/programinfo.h and is # the number of items in a ProgramInfo QStringList group used by diff --git a/mythtv/bindings/python/MythTV/_versions.py.in b/mythtv/bindings/python/MythTV/_versions.py.in index 47b81408dc7..230e1065913 100644 --- a/mythtv/bindings/python/MythTV/_versions.py.in +++ b/mythtv/bindings/python/MythTV/_versions.py.in @@ -7,7 +7,7 @@ The file MythTV/_versions.py is usually generated by ./configure. """ OWN_VERSION = @MYTHTV_PYTHON_OWN_VERSION@ -SCHEMA_VERSION = 1379 +SCHEMA_VERSION = 1380 NVSCHEMA_VERSION = 1007 MUSICSCHEMA_VERSION = 1025 PROTO_VERSION = '91' diff --git a/mythtv/libs/libmythbase/mythversion.h.in b/mythtv/libs/libmythbase/mythversion.h.in index 1f1fb34e3ec..08a265d123a 100644 --- a/mythtv/libs/libmythbase/mythversion.h.in +++ b/mythtv/libs/libmythbase/mythversion.h.in @@ -76,7 +76,7 @@ static constexpr const char* MYTH_PROTO_TOKEN { "BuzzOff" }; * mythtv/bindings/php/MythBackend.php */ -static constexpr const char* MYTH_DATABASE_VERSION { "1379" }; +static constexpr const char* MYTH_DATABASE_VERSION { "1380" }; MBASE_PUBLIC const char *GetMythSourceVersion(); MBASE_PUBLIC const char *GetMythSourcePath(); diff --git a/mythtv/libs/libmythtv/channelinfo.cpp b/mythtv/libs/libmythtv/channelinfo.cpp index 4b9c2faef34..293b3bcb97d 100644 --- a/mythtv/libs/libmythtv/channelinfo.cpp +++ b/mythtv/libs/libmythtv/channelinfo.cpp @@ -308,7 +308,7 @@ bool ChannelInsertInfo::SaveScan(uint scanid, uint transportid) const " in_nit, in_sdt, is_encrypted, " " is_data_service, is_audio_service, is_opencable, " " could_be_opencable, decryption_status, default_authority, " - " service_type " + " service_type, logical_channel, simulcast_channel " " ) " "VALUES " " ( :SCANID, :TRANSPORTID, " @@ -324,7 +324,7 @@ bool ChannelInsertInfo::SaveScan(uint scanid, uint transportid) const " :IN_NIT, :IN_SDT, :IS_ENCRYPTED, " " :IS_DATA_SERVICE, :IS_AUDIO_SERVICE, :IS_OPENCABLE, " " :COULD_BE_OPENCABLE,:DECRYPTION_STATUS, :DEFAULT_AUTHORITY, " - " :SERVICE_TYPE " + " :SERVICE_TYPE, :LOGICAL_CHANNEL, :SIMULCAST_CHANNEL " " );"); query.bindValue(":SCANID", scanid); @@ -366,6 +366,8 @@ bool ChannelInsertInfo::SaveScan(uint scanid, uint transportid) const query.bindValue(":DECRYPTION_STATUS", m_decryptionStatus); query.bindValueNoNull(":DEFAULT_AUTHORITY", m_defaultAuthority); query.bindValue(":SERVICE_TYPE", m_serviceType); + query.bindValue(":LOGICAL_CHANNEL", m_logicalChannel); + query.bindValue(":SIMULCAST_CHANNEL", m_simulcastChannel); if (!query.exec()) { diff --git a/mythtv/libs/libmythtv/channelinfo.h b/mythtv/libs/libmythtv/channelinfo.h index d9acf750f78..1832b5bb25a 100644 --- a/mythtv/libs/libmythtv/channelinfo.h +++ b/mythtv/libs/libmythtv/channelinfo.h @@ -161,7 +161,8 @@ class MTV_PUBLIC ChannelInsertInfo bool _is_encrypted, bool _is_data_service, bool _is_audio_service, bool _is_opencable, bool _could_be_opencable, int _decryption_status, - QString _default_authority, uint _service_type) : + QString _default_authority, uint _service_type, + uint _logical_channel, uint _simulcast_channel ) : m_dbMplexId(_db_mplexid), m_sourceId(_source_id), m_channelId(_channel_id), @@ -198,7 +199,9 @@ class MTV_PUBLIC ChannelInsertInfo m_isAudioService(_is_audio_service), m_isOpencable(_is_opencable), m_couldBeOpencable(_could_be_opencable), - m_decryptionStatus(_decryption_status) {} + m_decryptionStatus(_decryption_status), + m_logicalChannel(_logical_channel), + m_simulcastChannel(_simulcast_channel) {} ChannelInsertInfo(const ChannelInsertInfo &other) { (*this = other); } ChannelInsertInfo &operator=(const ChannelInsertInfo&) = default; @@ -250,6 +253,8 @@ class MTV_PUBLIC ChannelInsertInfo bool m_isOpencable {false}; bool m_couldBeOpencable {false}; int m_decryptionStatus {0}; + uint m_logicalChannel {0}; + uint m_simulcastChannel {0}; // Service relocated descriptor uint m_oldOrigNetId {0}; diff --git a/mythtv/libs/libmythtv/channelscan/channelimporter.cpp b/mythtv/libs/libmythtv/channelscan/channelimporter.cpp index 4ad6e5cd438..ea99a975828 100644 --- a/mythtv/libs/libmythtv/channelscan/channelimporter.cpp +++ b/mythtv/libs/libmythtv/channelscan/channelimporter.cpp @@ -45,6 +45,31 @@ static void channum_not_empty(ChannelInsertInfo &chan) } } +static uint getLcnOffset(int sourceid) +{ + uint lcnOffset = 0; + + MSqlQuery query(MSqlQuery::InitCon()); + query.prepare( + "SELECT lcnoffset " + "FROM videosource " + "WHERE videosource.sourceid = :SOURCEID"); + query.bindValue(":SOURCEID", sourceid); + if (!query.exec() || !query.isActive()) + { + MythDB::DBError("ChannelImporter", query); + } + else if (query.next()) + { + lcnOffset = query.value(0).toUInt(); + } + + LOG(VB_CHANSCAN, LOG_INFO, LOC + + QString("Logical Channel Number offset:%1") + .arg(lcnOffset)); + + return lcnOffset; +} ChannelImporter::ChannelImporter(bool gui, bool interactive, bool _delete, bool insert, bool save, @@ -76,6 +101,8 @@ ChannelImporter::ChannelImporter(bool gui, bool interactive, void ChannelImporter::Process(const ScanDTVTransportList &_transports, int sourceid) { + m_lcnOffset = getLcnOffset(sourceid); + if (_transports.empty()) { if (m_useGui) @@ -173,6 +200,12 @@ void ChannelImporter::Process(const ScanDTVTransportList &_transports, } } + // Process Logical Channel Numbers + if (m_doLcn) + { + ChannelNumbers(transports); + } + // Remove the channels that do not pass various criteria. FilterServices(transports); @@ -1152,6 +1185,101 @@ void ChannelImporter::FilterRelocatedServices(ScanDTVTransportList &transports) } } +// Process DVB Channel Numbers +void ChannelImporter::ChannelNumbers(ScanDTVTransportList &transports) const +{ + QMap map_sid_scn; // HD Simulcast channel numbers, service ID is key + QMap map_sid_lcn; // Logical channel numbers, service ID is key + QMap map_lcn_sid; // Logical channel numbers, channel number is key + + LOG(VB_CHANSCAN, LOG_INFO, LOC + QString("Process DVB Channel Numbers")); + for (auto & transport : transports) + { + for (auto & channel : transport.m_channels) + { + LOG(VB_CHANSCAN, LOG_DEBUG, LOC + QString("Channel onid:%1 sid:%2 lcn:%3 scn:%4") + .arg(channel.m_origNetId).arg(channel.m_serviceId).arg(channel.m_logicalChannel) + .arg(channel.m_simulcastChannel)); + qlonglong key = ((qlonglong)channel.m_origNetId<<32) | channel.m_serviceId; + if (channel.m_logicalChannel > 0) + { + map_sid_lcn[key] = channel.m_logicalChannel; + map_lcn_sid[channel.m_logicalChannel] = key; + } + if (channel.m_simulcastChannel > 0) + { + map_sid_scn[key] = channel.m_simulcastChannel; + } + } + } + + // Process the HD Simulcast Channel Numbers + // + // For each channel with a HD Simulcast Channel Number, do use that + // number as the Logical Channel Number; the SD channel that now has + // this LCN does get the original LCN of the HD Simulcast channel. + // If this is not selected then the Logical Channel Numbers are used + // without the override from the HD Simulcast channel numbers. + // This usually means that channel numbers 1, 2, 3 etc are used for SD channels + // while the corresponding HD channels do have higher channel numbers. + // When the HD Simulcast channel numbers are enabled then channel numbers 1, 2, 3 etc are + // used for the HD channels and the corresponding SD channels use the high channel numbers. + if (m_doScn) + { + LOG(VB_CHANSCAN, LOG_INFO, LOC + QString("Process Simulcast Channel Numbers")); + + QMap::iterator it; + for (it = map_sid_scn.begin(); it != map_sid_scn.end(); ++it) + { + // Exchange LCN between the SD channel and the HD simulcast channel + qlonglong key_hd = it.key(); // Key of HD channel + uint scn_hd = *it; // SCN of the HD channel + uint lcn_sd = scn_hd; // Old LCN of the SD channel + uint lcn_hd = map_sid_lcn[key_hd]; // Old LCN of the HD channel + + qlonglong key_sd = map_lcn_sid[lcn_sd]; // Key of the SD channel + + map_sid_lcn[key_sd] = lcn_hd; // SD channel gets old LCN of HD channel + map_sid_lcn[key_hd] = lcn_sd; // HD channel gets old LCN of SD channel + map_lcn_sid[lcn_hd] = key_sd; // SD channel gets key of SD channel + map_lcn_sid[lcn_sd] = key_hd; // HD channel gets key of SD channel + } + } + + // Update channels with the resulting Logical Channel Numbers + LOG(VB_CHANSCAN, LOG_INFO, LOC + QString("Process Logical Channel Numbers")); + for (auto & transport : transports) + { + for (auto & channel : transport.m_channels) + { + if (channel.m_chanNum.isEmpty()) + { + qlonglong key = ((qlonglong)channel.m_origNetId<<32) | channel.m_serviceId; + QMap::const_iterator it = map_sid_lcn.constFind(key); + if (it != map_sid_lcn.cend()) + { + channel.m_chanNum = QString::number(*it + m_lcnOffset); + LOG(VB_CHANSCAN, LOG_DEBUG, LOC + + QString("Final channel sid:%1 channel %2") + .arg(channel.m_serviceId).arg(channel.m_chanNum)); + } + else + { + LOG(VB_CHANSCAN, LOG_DEBUG, LOC + + QString("Final channel sid:%1 NO channel number") + .arg(channel.m_serviceId)); + } + } + else + { + LOG(VB_CHANSCAN, LOG_DEBUG, LOC + + QString("Final channel sid:%1 has already channel number %2") + .arg(channel.m_serviceId).arg(channel.m_chanNum)); + } + } + } +} + /** \fn ChannelImporter::GetDBTransports(uint,ScanDTVTransportList&) const * \brief Adds found channel info to transports list, * returns channels in DB which were not found in scan diff --git a/mythtv/libs/libmythtv/channelscan/channelimporter.h b/mythtv/libs/libmythtv/channelscan/channelimporter.h index c9990b76eda..9e0db8986be 100644 --- a/mythtv/libs/libmythtv/channelscan/channelimporter.h +++ b/mythtv/libs/libmythtv/channelscan/channelimporter.h @@ -132,6 +132,7 @@ class MTV_PUBLIC ChannelImporter : public QObject static void RemoveDuplicates(ScanDTVTransportList &transports, ScanDTVTransportList &duplicates); void FilterServices(ScanDTVTransportList &transports) const; static void FilterRelocatedServices(ScanDTVTransportList &transports); + void ChannelNumbers(ScanDTVTransportList &transports) const; ScanDTVTransportList GetDBTransports( uint sourceid, ScanDTVTransportList &transports) const; @@ -248,6 +249,8 @@ class MTV_PUBLIC ChannelImporter : public QObject bool m_doDelete; bool m_doInsert; bool m_doSave; + bool m_doLcn {true}; // Use Logical Channel Numbers + bool m_doScn {true}; // Use HD Simulcast logical channel numbers bool m_ftaOnly {true}; // Only FreeToAir (non-encrypted) channels desired post scan? bool m_lcnOnly {false}; // Only services with logical channel numbers desired post scan? bool m_completeOnly {true}; // Only services with complete scandata desired post scan? @@ -255,6 +258,7 @@ class MTV_PUBLIC ChannelImporter : public QObject bool m_fullChannelSearch {false}; // Full search for old channels across transports in database bool m_removeDuplicates {false}; // Remove duplicate transports and channels in scan bool m_success {false}; // To pass information IPTV channel scan succeeded + int m_lcnOffset {0}; // Read from database table videosource int m_functorRetval {0}; ChannelScannerWeb * m_pWeb {nullptr}; diff --git a/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp b/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp index 3cc36efc358..2a174bf9e72 100644 --- a/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp +++ b/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp @@ -1710,9 +1710,8 @@ ChannelScanSM::GetChannelList(transport_scan_items_it_t trans_info, dbchan_it != pnum_to_dbchan.end(); ++dbchan_it) { ChannelInsertInfo &info = *dbchan_it; - - if (!info.m_chanNum.isEmpty()) - continue; + qlonglong key = ((qlonglong)info.m_origNetId<<32) | info.m_serviceId; + QMap::const_iterator it; // DVB HD Simulcast channel numbers // @@ -1721,45 +1720,30 @@ ChannelScanSM::GetChannelList(transport_scan_items_it_t trans_info, // and the receiver is capable of receiving the HD signal. // The latter is assumed correct for a MythTV system. // - if (info.m_chanNum.isEmpty()) + it = scnChanNums.constFind(key); + if (it != scnChanNums.constEnd()) { - qlonglong key = ((qlonglong)info.m_origNetId<<32) | info.m_serviceId; - QMap::const_iterator it = scnChanNums.constFind(key); - - if (it != scnChanNums.constEnd()) - { - info.m_chanNum = QString::number(*it + m_lcnOffset); - } + info.m_simulcastChannel = *it; } // DVB Logical Channel Numbers (a.k.a. UK channel numbers) - if (info.m_chanNum.isEmpty()) + it = ukChanNums.constFind(key); + if (it != ukChanNums.constEnd()) { - qlonglong key = ((qlonglong)info.m_origNetId<<32) | info.m_serviceId; - QMap::const_iterator it = ukChanNums.constFind(key); - - if (it != ukChanNums.constEnd()) - { - info.m_chanNum = QString::number(*it + m_lcnOffset); - } + info.m_logicalChannel = *it; } // Freesat and Sky channel numbers - if (info.m_chanNum.isEmpty()) + it = sid_lcn.constFind(key); + if (it != sid_lcn.constEnd()) { - qlonglong key = ((qlonglong)info.m_origNetId<<32) | info.m_serviceId; - QMap::const_iterator it = sid_lcn.constFind(key); - - if (it != sid_lcn.constEnd()) - { - info.m_chanNum = QString::number(*it + m_lcnOffset); - } + info.m_logicalChannel = *it; } LOG(VB_CHANSCAN, LOG_INFO, LOC + - QString("GetChannelList: service %1 (0x%2) chan_num '%3' callsign '%4'") + QString("service %1 (0x%2) lcn:%3 scn:%4 callsign '%5'") .arg(info.m_serviceId).arg(info.m_serviceId,4,16,QChar('0')) - .arg(info.m_chanNum, info.m_callSign)); + .arg(info.m_logicalChannel).arg(info.m_simulcastChannel).arg(info.m_callSign)); } // Get QAM/SCTE/MPEG channel numbers @@ -1990,7 +1974,7 @@ void ChannelScanSM::StartScanner(void) } /** \fn ChannelScanSM::run(void) - * \brief This runs the event loop for ChannelScanSM until 'threadExit' is true. + * \brief This runs the event loop for ChannelScanSM until 'm_threadExit' is true. */ void ChannelScanSM::run(void) { diff --git a/mythtv/libs/libmythtv/channelscan/scaninfo.cpp b/mythtv/libs/libmythtv/channelscan/scaninfo.cpp index a7c55830eb5..098a32a6c39 100644 --- a/mythtv/libs/libmythtv/channelscan/scaninfo.cpp +++ b/mythtv/libs/libmythtv/channelscan/scaninfo.cpp @@ -126,7 +126,7 @@ ScanDTVTransportList LoadScan(uint scanid) " in_nit, in_sdt, is_encrypted, " // 27, 28, 29 " is_data_service, is_audio_service, is_opencable, " // 30, 31, 32 " could_be_opencable, decryption_status, default_authority, " // 33, 34, 35 - " service_type " // 36 + " service_type, logical_channel, simulcast_channel " // 36, 37, 38 "FROM channelscan_channel " "WHERE transportid = :TRANSPORTID"); query2.bindValue(":TRANSPORTID", query.value(15).toUInt()); @@ -187,7 +187,9 @@ ScanDTVTransportList LoadScan(uint scanid) query2.value(33).toBool(), // could_be_opencable query2.value(34).toInt(), // decryption_status query2.value(35).toString(), // default_authority - query2.value(36).toUInt()); // service_type + query2.value(36).toUInt(), // service_type + query2.value(37).toUInt(), // logical_channel + query2.value(38).toUInt()); // simulcast_channel mux.m_channels.push_back(chan); } diff --git a/mythtv/libs/libmythtv/dbcheck.cpp b/mythtv/libs/libmythtv/dbcheck.cpp index c59368d650b..cb3747854e2 100644 --- a/mythtv/libs/libmythtv/dbcheck.cpp +++ b/mythtv/libs/libmythtv/dbcheck.cpp @@ -4055,6 +4055,17 @@ static bool doUpgradeTVDatabaseSchema(void) return false; } + if (dbver == "1379") + { + DBUpdates updates { + "ALTER TABLE channelscan_channel ADD COLUMN logical_channel INT UNSIGNED NOT NULL DEFAULT 0 AFTER service_type;" + "ALTER TABLE channelscan_channel ADD COLUMN simulcast_channel INT UNSIGNED NOT NULL DEFAULT 0 AFTER logical_channel;" + }; + if (!performActualUpdate("MythTV", "DBSchemaVer", + updates, "1380", dbver)) + return false; + } + return true; } diff --git a/mythtv/libs/libmythtv/dtvmultiplex.cpp b/mythtv/libs/libmythtv/dtvmultiplex.cpp index d744c7e17a7..ffefa363fbb 100644 --- a/mythtv/libs/libmythtv/dtvmultiplex.cpp +++ b/mythtv/libs/libmythtv/dtvmultiplex.cpp @@ -692,7 +692,9 @@ bool ScanDTVTransport::FillFromDB(DTVTunerType type, uint mplexid) false, false, false, false, false, false, false, 0, query.value(17).toString(), /* default_authority */ - query.value(18).toUInt()); /* service_type */ + query.value(18).toUInt(), /* service_type */ + 0, /* logical_channel */ + 0); /* simulcast_channel */ m_channels.push_back(chan); }