15 #include <QTextStream>
16 #include <QElapsedTimer>
26 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
29 #define QT_ENDL Qt::endl
32 #define LOC QString("ChanImport: ")
56 bool _delete,
bool insert,
bool save,
57 bool fta_only,
bool lcn_only,
bool complete_only,
58 bool full_channel_search,
59 bool remove_duplicates,
63 m_isInteractive(interactive),
69 m_completeOnly(complete_only),
70 m_fullChannelSearch(full_channel_search),
71 m_removeDuplicates(remove_duplicates),
73 m_serviceRequirements(service_requirements)
85 if (_transports.empty())
91 LOG(VB_GENERAL, LOG_INFO,
LOC + (channels ?
93 QString(
"Found %1 channels")
95 "No new channels to process") :
96 "No channels to process.."));
100 (
m_success ? tr(
"Found %n channel(s)",
"", channels) :
101 tr(
"Failed to find any new channels!"))
102 : tr(
"Failed to find any channels."));
112 "No new channels to process" :
113 "No channels to process..");
127 QTextStream ssMsg(&msg);
134 ssMsg <<
"Scan parameters:" <<
QT_ENDL;
135 ssMsg <<
"Desired Services : " << (require_av ?
"tv" : require_a ?
"tv+radio" :
"all") <<
QT_ENDL;
137 ssMsg <<
"Logical Channel Numbers only: " << (
m_lcnOnly ?
"yes" :
"no") <<
QT_ENDL;
144 if (!transports.empty())
147 ssMsg <<
"Transport list before processing (" << transports.size() <<
"):" <<
QT_ENDL;
152 ssMsg <<
"Channel list before processing (";
154 ssMsg <<
FormatChannels(transports, &info).toLatin1().constData();
156 LOG(VB_GENERAL, LOG_INFO,
LOC + msg);
170 if (!duplicates.empty())
174 ssMsg <<
"Discarded duplicate transports (" << duplicates.size() <<
"):" <<
QT_ENDL;
178 LOG(VB_CHANSCAN, LOG_INFO,
LOC + msg);
196 if (!db_trans.empty())
199 ssMsg <<
"Transports with channels in DB but not in scan (";
200 ssMsg << db_trans.size() <<
"):" <<
QT_ENDL;
211 ssMsg <<
"Channel list after compare with database (";
213 ssMsg <<
FormatChannels(transports, &info).toLatin1().constData();
221 std::copy(db_trans.cbegin(), db_trans.cend(), std::back_inserter(trans));
243 LOG(VB_GENERAL, LOG_INFO,
LOC + msg);
280 std::vector<uint> off_air_list;
281 QMap<uint,bool> deleted;
284 for (
size_t i = 0; i < transports.size(); ++i)
287 for (
size_t j = 0; j < transports[i].m_channels.size(); ++j)
296 off_air_list.push_back(i<<16|j);
301 off_air_transports.push_back(transport_copy);
304 if (off_air_list.empty())
308 std::cout << std::endl <<
"Off-air channels (" <<
SimpleCountChannels(off_air_transports) <<
"):" << std::endl;
310 std::cout <<
FormatChannels(off_air_transports, &infoA).toLatin1().constData() << std::endl;
315 QString msg = tr(
"Found %n off-air channel(s).",
"", off_air_list.size());
324 for (
uint item : off_air_list)
327 int j = item & 0xFFFF;
329 transports[i].m_channels[j].m_channelId);
330 deleted[item] =
true;
335 for (
uint item : off_air_list)
338 int j = item & 0xFFFF;
339 int chanid = transports[i].m_channels[j].m_channelId;
358 for (
size_t i = 0; i < transports.size(); ++i)
360 newlist.push_back(transports[i]);
361 newlist.back().m_channels.clear();
362 for (
size_t j = 0; j < transports[i].m_channels.size(); ++j)
364 if (!deleted.contains(i<<16|j))
366 newlist.back().m_channels.push_back(
367 transports[i].m_channels[j]);
372 transports = newlist;
373 return deleted.size();
380 "SELECT mplexid FROM dtv_multiplex "
381 "WHERE sourceid = :SOURCEID1 AND "
385 " WHERE sourceid = :SOURCEID2)");
394 QString msg = tr(
"Found %n unused transport(s).",
"", query.
size());
395 LOG(VB_GENERAL, LOG_INFO,
LOC + msg);
399 if (query.
size() == 0)
409 "DELETE FROM dtv_multiplex "
410 "WHERE sourceid = :SOURCEID1 AND "
414 " WHERE sourceid = :SOURCEID2)");
426 LOG(VB_GENERAL, LOG_INFO,
LOC +
"Manual delete of transport not implemented");
457 QString msg = tr(
"Found %n old %1 channel(s).",
"", old_chan)
466 QString msg = tr(
"Found %n new %1 channel(s).",
"", new_chan)
494 QString msg = tr(
"Found %n conflicting old %1 channel(s).",
503 QString msg = tr(
"Found %n new conflicting %1 channel(s).",
512 QTextStream ssMsg(&msg);
514 if (!updated.empty())
517 ssMsg <<
"Updated old transports (" << updated.size() <<
"):" <<
QT_ENDL;
524 if (!skipped_updates.empty())
530 if (!inserted.empty())
536 if (!skipped_inserts.empty())
551 ssMsg <<
GetSummary(ninfo, nstats).toLatin1().constData();
553 LOG(VB_GENERAL, LOG_INFO,
LOC + msg);
577 bool cancel_all =
false;
582 for (
const auto & transport : transports)
588 for (
size_t j = 0; j < transport.m_channels.size(); ++j)
630 bool conflicting =
false;
639 bool ok_done =
false;
679 bool inserted =
false;
721 if (!transport.m_iptvTuning.GetDataURL().isEmpty())
723 transport.m_iptvTuning);
746 next_list.push_back(new_transport);
749 skipped_list.push_back(skipped_transport);
752 inserted_list.push_back(inserted_transport);
781 for (
const auto & transport : transports)
787 for (
size_t j = 0; j < transport.m_channels.size(); ++j)
805 bool conflicting =
false;
824 bool updated =
false;
882 next_list.push_back(new_transport);
885 skipped_list.push_back(skipped_transport);
888 updated_list.push_back(updated_transport);
911 transport_copy = transport;
927 if (!transports.empty())
928 tuner_type = transports[0].m_tunerType;
933 uint freq_mult = (is_dvbs) ? 1 : 1000;
935 std::vector<bool> ignore;
936 ignore.resize(transports.size());
937 for (
size_t i = 0; i < transports.size(); ++i)
942 for (
size_t j = i+1; j < transports.size(); ++j)
944 if (!transports[i].IsEqual(
945 tuner_type, transports[j], 500 * freq_mult))
950 for (
size_t k = 0; k < transports[j].m_channels.size(); ++k)
952 bool found_same =
false;
953 for (
size_t l = 0; l < transports[i].m_channels.size(); ++l)
955 if (transports[j].m_channels[k].IsSameChannel(
956 transports[i].m_channels[l]))
959 transports[i].m_channels[l].ImportExtraInfo(
960 transports[j].m_channels[k]);
964 transports[i].m_channels.push_back(transports[j].m_channels[k]);
966 LOG(VB_CHANSCAN, LOG_INFO,
LOC +
967 QString(
"Transport on same frequency:") +
FormatTransport(transports[j]));
970 no_dups.push_back(transports[i]);
972 transports = no_dups;
987 LOG(VB_CHANSCAN, LOG_INFO,
LOC +
988 QString(
"Number of transports:%1").arg(transports.size()));
991 std::vector<bool> ignore;
992 ignore.resize(transports.size());
993 for (
size_t i = 0; i < transports.size(); ++i)
996 LOG(VB_CHANSCAN, LOG_INFO,
LOC +
"Transport " +
1001 for (
size_t j = i+1; j < transports.size(); ++j)
1004 bool found_same =
true;
1005 bool found_diff =
true;
1008 LOG(VB_CHANSCAN, LOG_DEBUG,
LOC +
"Comparing transport A " +
1010 LOG(VB_CHANSCAN, LOG_DEBUG,
LOC +
"Comparing transport B " +
1013 for (
size_t k = 0; found_same && k < tb.
m_channels.size(); ++k)
1027 if (found_same && !found_diff)
1029 size_t lowss = transports[i].m_signalStrength < transports[j].m_signalStrength ? i : j;
1030 ignore[lowss] =
true;
1031 duplicates.push_back(transports[lowss]);
1033 LOG(VB_CHANSCAN, LOG_INFO,
LOC +
"Duplicate transports found:");
1042 no_dups.push_back(transports[i]);
1046 transports = no_dups;
1054 for (
auto & transport : transports)
1057 for (
auto & channel : transport.m_channels)
1059 if (
m_ftaOnly && channel.m_isEncrypted &&
1063 if (require_a && channel.m_isDataService)
1066 if (require_av && channel.m_isAudioService)
1070 if (
m_lcnOnly && channel.m_chanNum.isEmpty())
1073 LOG(VB_CHANSCAN, LOG_INFO,
LOC + QString(
"No LCN: %1").arg(msg));
1079 !(channel.m_inPat &&
1083 LOG(VB_CHANSCAN, LOG_INFO,
LOC + QString(
"Not in PAT/PMT: %1").arg(msg));
1089 channel.m_atscMajorChannel == 0 &&
1090 channel.m_atscMinorChannel == 0 &&
1091 (!channel.m_inPat ||
1094 (channel.m_patTsId !=
1095 channel.m_sdtTsId)))
1098 LOG(VB_CHANSCAN, LOG_INFO,
LOC + QString(
"Not in PAT/PMT/SDT: %1").arg(msg));
1106 LOG(VB_CHANSCAN, LOG_INFO,
LOC + QString(
"No name: %1").arg(msg));
1111 if (channel.m_inChannelsConf &&
1112 !(channel.m_inPat ||
1119 filtered.push_back(channel);
1121 transport.m_channels = filtered;
1127 QMap<uint64_t, bool> rs;
1130 for (
auto & transport : transports)
1132 for (
auto & channel : transport.m_channels)
1134 if (channel.m_oldOrigNetId > 0)
1136 uint64_t key = ((uint64_t)channel.m_oldOrigNetId << 32) | (channel.m_oldTsId << 16) | channel.m_oldServiceId;
1143 for (
auto & transport : transports)
1146 for (
auto & channel : transport.m_channels)
1148 uint64_t key = ((uint64_t)channel.m_origNetId << 32) | (channel.m_sdtTsId << 16) | channel.m_serviceId;
1149 if (rs.value(key,
false))
1152 LOG(VB_CHANSCAN, LOG_INFO,
LOC + QString(
"Relocated: %1").arg(msg));
1155 filtered.push_back(channel);
1157 transport.m_channels = filtered;
1172 int found_in_same_transport = 0;
1173 int found_in_other_transport = 0;
1174 int found_nowhere = 0;
1177 if (!transports.empty())
1178 tuner_type = transports[0].m_tunerType;
1184 uint freq_mult = (is_dvbs) ? 1 : 1000;
1189 "FROM dtv_multiplex "
1190 "WHERE sourceid = :SOURCEID "
1192 "ORDER BY mplexid");
1201 QMap<uint,bool> found_in_scan;
1202 while (query.
next())
1206 if (db_transport.
FillFromDB(tuner_type, mplexid))
1214 bool found_transport =
false;
1215 QMap<uint,bool> found_in_database;
1218 for (
size_t ist = 0; ist < transports.size(); ++ist)
1221 if (scan_transport.
IsEqual(tuner_type, db_transport, 500 * freq_mult,
true))
1223 found_transport =
true;
1225 for (
size_t jdc = 0; jdc < db_transport.
m_channels.size(); ++jdc)
1227 if (!found_in_database[jdc])
1230 for (
size_t ksc = 0; ksc < scan_transport.
m_channels.size(); ++ksc)
1232 if (!found_in_scan[(ist<<16)+ksc])
1237 found_in_same_transport++;
1238 found_in_database[jdc] =
true;
1239 found_in_scan[(ist<<16)+ksc] =
true;
1256 for (
size_t ist = 0; ist < transports.size(); ++ist)
1259 for (
size_t jdc = 0; jdc < db_transport.
m_channels.size(); ++jdc)
1261 if (!found_in_database[jdc])
1264 for (
size_t ksc = 0; ksc < scan_transport.
m_channels.size(); ++ksc)
1266 if (!found_in_scan[(ist<<16)+ksc])
1271 found_in_other_transport++;
1272 found_in_database[jdc] =
true;
1273 found_in_scan[(ist<<16)+ksc] =
true;
1287 if (found_transport)
1290 tmp.m_channels.clear();
1292 for (
size_t idc = 0; idc < db_transport.
m_channels.size(); ++idc)
1294 if (!found_in_database[idc])
1301 if (!
tmp.m_channels.empty())
1302 not_in_scan.push_back(
tmp);
1305 LOG(VB_GENERAL, LOG_INFO,
LOC +
1306 QString(
"Old channels found in same transport: %1")
1307 .arg(found_in_same_transport));
1308 LOG(VB_GENERAL, LOG_INFO,
LOC +
1309 QString(
"Old channels found in other transport: %1")
1310 .arg(found_in_other_transport));
1311 LOG(VB_GENERAL, LOG_INFO,
LOC +
1312 QString(
"Old channels not found (off-air): %1")
1313 .arg(found_nowhere));
1321 for (
auto & transport : transports)
1323 for (
auto & chan : transport.m_channels)
1325 if (((chan.m_couldBeOpencable && (chan.m_siStandard ==
"mpeg")) ||
1326 chan.m_isOpencable) && !chan.m_inVct)
1328 chan.m_siStandard =
"opencable";
1338 for (
const auto & transport : transports)
1340 for (
const auto & chan : transport.m_channels)
1342 int enc = (chan.m_isEncrypted) ?
1345 if (chan.m_siStandard ==
"dvb") info.
m_dvbChannels[enc] += 1;
1347 if (chan.m_siStandard ==
"opencable") info.
m_scteChannels[enc] += 1;
1349 if (chan.m_siStandard !=
"ntsc")
1354 if (chan.m_siStandard ==
"atsc")
1357 (chan.m_atscMinorChannel)];
1361 if (chan.m_siStandard ==
"ntsc")
1364 (chan.m_atscMinorChannel)];
1378 for (
const auto & transport : transports)
1380 for (
const auto & chan : transport.m_channels)
1387 if (chan.m_siStandard ==
"atsc")
1391 (chan.m_atscMinorChannel)] == 1) ? 1 : 0;
1393 (info.
m_atscMinCnt[(chan.m_atscMinorChannel)] == 1) ? 1 : 0;
1414 QTextStream ssMsg(&msg);
1418 QString si_standard = (chan.
m_siStandard==
"opencable") ?
1421 if (si_standard ==
"atsc" || si_standard ==
"scte")
1423 ssMsg << (QString(
"%1:%2:%3-%4:%5:%6=%7=%8:%9")
1431 .arg(si_standard)).toLatin1().constData();
1433 else if (si_standard ==
"dvb")
1435 ssMsg << (QString(
"%1:%2:%3:%4:%5:%6=%7:%8")
1441 .arg(si_standard)).toLatin1().constData();
1445 ssMsg << (QString(
"%1:%2:%3:%4:%5")
1449 .arg(si_standard)).toLatin1().constData();
1455 msg = msg.leftJustified(72);
1460 << (QString(
"cnt(pnum:%1,channum:%2)")
1463 ).toLatin1().constData();
1468 (QString(
":atsc_cnt(tot:%1,minor:%2)")
1473 ).toLatin1().constData();
1496 QTextStream ssMsg(&msg);
1498 QString si_standard = (chan.
m_siStandard==
"opencable") ?
1501 if (si_standard ==
"atsc" || si_standard ==
"scte")
1503 if (si_standard ==
"atsc")
1511 ssMsg << (QString(
"%1-%2")
1517 ssMsg << (QString(
"%1-%2")
1523 ssMsg << (QString(
" (%1)")
1524 .arg(chan.
m_callSign)).toLatin1().constData();
1526 else if (si_standard ==
"dvb")
1528 ssMsg << (QString(
"%1 (%2 %3)")
1530 .arg(chan.
m_netId)).toLatin1().constData();
1534 ssMsg << (QString(
"%1-%2")
1536 .toLatin1().constData();
1540 ssMsg << (QString(
"%1-%2")
1542 .toLatin1().constData();
1553 struct less_than_key
1561 std::sort(transports.begin(), transports.end(), less_than_key());
1565 for (
auto & transport : transports)
1567 auto fmt_chan = [transport, info](
const QString & m,
const auto & chan)
1568 {
return m +
FormatChannel(transport, chan, info) +
"\n"; };
1569 msg = std::accumulate(transport.m_channels.cbegin(), transport.m_channels.cend(),
1580 QTextStream ssMsg(&msg);
1582 ssMsg << QString(
" onid:%1").arg(transport.
m_networkID);
1592 struct less_than_key
1600 std::sort(transports.begin(), transports.end(), less_than_key());
1602 auto fmt_trans = [](
const QString& msg,
const auto & transport)
1604 return std::accumulate(transports.cbegin(), transports.cend(),
1605 QString(), fmt_trans);
1612 QString msg = tr(
"Channels: FTA Enc Dec\n") +
1613 QString(
"ATSC %1 %2 %3\n")
1617 QString(
"DVB %1 %2 %3\n")
1621 QString(
"SCTE %1 %2 %3\n")
1625 QString(
"MPEG %1 %2 %3\n")
1629 QString(
"NTSC %1\n")
1631 tr(
"Unique: prog %1 atsc %2 atsc minor %3 channum %4\n")
1634 tr(
"Max atsc major count: %1")
1700 new_chan = old_chan = 0;
1701 for (
const auto & transport : transports)
1703 for (
const auto& chan : transport.m_channels)
1707 if (chan.m_channelId)
1719 auto add_count = [](
int count,
const auto & transport)
1720 {
return count + transport.m_channels.size(); };
1721 return std::accumulate(transports.cbegin(), transports.cend(),
1742 static QMutex s_lastFreeLock;
1743 static QMap<uint,uint> s_lastFreeChanNumMap;
1751 for (
char suffix =
'A'; suffix <=
'Z'; ++suffix)
1759 QMutexLocker locker(&s_lastFreeLock);
1761 for (last_free_chan_num++; ; ++last_free_chan_num)
1763 chanNum = QString::number(last_free_chan_num);
1767 s_lastFreeChanNumMap[chan.
m_sourceId] = last_free_chan_num;
1795 auto *deleteDialog =
1798 if (deleteDialog->Create())
1800 deleteDialog->AddButton(tr(
"Delete All"));
1801 deleteDialog->AddButton(tr(
"Set all invisible"));
1803 deleteDialog->AddButton(tr(
"Ignore All"));
1805 [
this](
const QString & ,
int result)
1825 std::cout << msg.toLatin1().constData()
1827 << tr(
"Do you want to:").toLatin1().constData()
1829 << tr(
"1. Delete All").toLatin1().constData()
1831 << tr(
"2. Set all invisible").toLatin1().constData()
1834 << tr(
"4. Ignore All").toLatin1().constData()
1841 uint val = QString(ret.c_str()).toUInt(&ok);
1842 if (ok && (val == 1 || val == 2 || val == 4))
1852 std::cout << tr(
"Please enter either 1, 2 or 4:")
1853 .toLatin1().constData() << std::endl;
1883 auto *insertDialog =
1886 if (insertDialog->Create())
1888 insertDialog->AddButton(tr(
"Insert All"));
1889 insertDialog->AddButton(tr(
"Insert Manually"));
1890 insertDialog->AddButton(tr(
"Ignore All"));
1892 [
this](
const QString & ,
int result)
1912 std::cout << msg.toLatin1().constData()
1914 << tr(
"Do you want to:").toLatin1().constData()
1916 << tr(
"1. Insert All").toLatin1().constData()
1918 << tr(
"2. Insert Manually").toLatin1().constData()
1920 << tr(
"3. Ignore All").toLatin1().constData()
1927 uint val = QString(ret.c_str()).toUInt(&ok);
1928 if (ok && (1 <= val) && (val <= 3))
1936 std::cout << tr(
"Please enter either 1, 2, or 3:")
1937 .toLatin1().constData() << std::endl;
1968 auto *updateDialog =
1971 if (updateDialog->Create())
1973 updateDialog->AddButton(tr(
"Update All"));
1974 updateDialog->AddButton(tr(
"Ignore All"));
1976 [
this](
const QString& ,
int result)
1995 std::cout << msg.toLatin1().constData()
1997 << tr(
"Do you want to:").toLatin1().constData()
1999 << tr(
"1. Update All").toLatin1().constData()
2001 << tr(
"2. Update Manually").toLatin1().constData()
2003 << tr(
"3. Ignore All").toLatin1().constData()
2010 uint val = QString(ret.c_str()).toUInt(&ok);
2011 if (ok && (1 <= val) && (val <= 3))
2019 std::cout << tr(
"Please enter either 1, 2, or 3:")
2020 .toLatin1().constData() << std::endl;
2028 const QString& title,
2029 const QString& message, QString &text)
2049 popupStack = parent->
GetStack(
"popup stack");
2051 "manualchannelpopup");
2053 if (popup->Create())
2055 popup->AddButtonD(QCoreApplication::translate(
"(Common)",
"OK"), 0 == dmc);
2056 popup->AddButtonD(tr(
"Edit"), 1 == dmc);
2057 popup->AddButtonD(QCoreApplication::translate(
"(Common)",
"Cancel"), 2 == dmc);
2058 popup->AddButtonD(QCoreApplication::translate(
"(Common)",
"Cancel All"), 3 == dmc);
2060 [
this](
const QString & ,
int result)
2091 tr(
"Please enter a unique channel number."),
2093 if (textEdit->Create())
2096 [
this,&text](QString result)
2099 text = std::move(result);
2116 case 0: rval =
kOCTOk;
break;
2126 const QString& title,
2127 const QString& message, QString &text)
2148 popupStack = parent->
GetStack(
"popup stack");
2150 "resolvechannelpopup");
2152 if (popup->Create())
2154 popup->AddButtonD(QCoreApplication::translate(
"(Common)",
"OK"), 0 == dmc);
2155 popup->AddButtonD(QCoreApplication::translate(
"(Common)",
"OK All"), 1 == dmc);
2156 popup->AddButtonD(tr(
"Edit"), 2 == dmc);
2157 popup->AddButtonD(QCoreApplication::translate(
"(Common)",
"Cancel"), 3 == dmc);
2158 popup->AddButtonD(QCoreApplication::translate(
"(Common)",
"Cancel All"), 4 == dmc);
2160 [
this](
const QString & ,
int result)
2190 tr(
"Please enter a unique channel number."),
2192 if (textEdit->Create())
2195 [
this,&text](QString result)
2198 text = std::move(result);
2216 case 0: rval =
kOCTOk;
break;
2230 QString msg = tr(
"Channel %1 has channel number %2 but that is already in use.")
2242 msg2 += tr(
"Please enter a unique channel number.");
2246 msg2 += tr(
"Default value is %1.").arg(val);
2248 tr(
"Channel Importer"),
2264 std::cout << msg.toLatin1().constData() << std::endl;
2266 QString cancelStr = QCoreApplication::translate(
"(Common)",
2267 "Cancel").toLower();
2268 QString cancelAllStr = QCoreApplication::translate(
"(Common)",
2269 "Cancel All").toLower();
2270 QString msg2 = tr(
"Please enter a non-conflicting channel number "
2271 "(or type '%1' to skip, '%2' to skip all):")
2272 .arg(cancelStr, cancelAllStr);
2276 std::cout << msg2.toLatin1().constData() << std::endl;
2279 QString val = QString(sret.c_str());
2280 if (val.toLower() == cancelStr)
2285 if (val.toLower() == cancelAllStr)
2308 QString msg = tr(
"You chose to manually insert channel %1.")
2319 msg2 += tr(
"Please enter a unique channel number.");
2323 msg2 += tr(
"Default value is %1").arg(val);
2325 tr(
"Channel Importer"),
2342 std::cout << msg.toLatin1().constData() << std::endl;
2344 QString cancelStr = QCoreApplication::translate(
"(Common)",
"Cancel").toLower();
2345 QString cancelAllStr = QCoreApplication::translate(
"(Common)",
"Cancel All").toLower();
2348 QString msg2 = tr(
"Please enter a non-conflicting channel number "
2349 "(or type '%1' to skip, '%2' to skip all): ")
2350 .arg(cancelStr, cancelAllStr);
2354 std::cout << msg2.toLatin1().constData() << std::endl;
2357 QString val = QString(sret.c_str());
2358 if (val.toLower() == cancelStr)
2363 if (val.toLower() == cancelAllStr)
2392 bool ok = (num.length() >= 1);
2393 ok = ok && ((num[0] >=
'0') && (num[0] <=
'9'));