MythTV master
channelimporter.cpp
Go to the documentation of this file.
1// -*- Mode: c++ -*-
2/*
3 * Copyright (C) Daniel Kristjansson 2007
4 *
5 * This file is licensed under GPL v2 or (at your option) any later version.
6 *
7 */
8
9// C++ includes
10#include <algorithm>
11#include <iostream>
12#include <utility>
13
14// Qt includes
15#include <QTextStream>
16#include <QElapsedTimer>
17
18// MythTV headers
20#include "libmythbase/mythdb.h"
23
24#include "channelimporter.h"
25#include "channelutil.h"
26#include "mpeg/mpegstreamdata.h" // for kEncDecrypted
27
28#define LOC QString("ChanImport: ")
29
30static const QString kATSCChannelFormat = "%1_%2";
31
32static QString map_str(QString str)
33{
34 if (str.isEmpty())
35 return "";
36 return str;
37}
38
39// Use the service ID as default channel number when there is no
40// DVB logical channel number or ATSC channel number found in the scan.
41//
43{
44 if (chan.m_chanNum.isEmpty())
45 {
46 chan.m_chanNum = QString("%1").arg(chan.m_serviceId);
47 }
48}
49
50static uint getLcnOffset(int sourceid)
51{
52 uint lcnOffset = 0;
53
55 query.prepare(
56 "SELECT lcnoffset "
57 "FROM videosource "
58 "WHERE videosource.sourceid = :SOURCEID");
59 query.bindValue(":SOURCEID", sourceid);
60 if (!query.exec() || !query.isActive())
61 {
62 MythDB::DBError("ChannelImporter", query);
63 }
64 else if (query.next())
65 {
66 lcnOffset = query.value(0).toUInt();
67 }
68
69 LOG(VB_CHANSCAN, LOG_INFO, LOC +
70 QString("Logical Channel Number offset:%1")
71 .arg(lcnOffset));
72
73 return lcnOffset;
74}
75
76ChannelImporter::ChannelImporter(bool gui, bool interactive,
77 bool _delete, bool insert, bool save,
78 bool fta_only, bool lcn_only, bool complete_only,
79 bool full_channel_search,
80 bool remove_duplicates,
81 ServiceRequirements service_requirements,
82 bool success) :
83 m_useGui(gui),
84 m_isInteractive(interactive),
85 m_doDelete(_delete),
86 m_doInsert(insert),
87 m_doSave(save),
88 m_ftaOnly(fta_only),
89 m_lcnOnly(lcn_only),
90 m_completeOnly(complete_only),
91 m_fullChannelSearch(full_channel_search),
92 m_removeDuplicates(remove_duplicates),
93 m_success(success),
94 m_serviceRequirements(service_requirements)
95{
97 {
98 m_useWeb = true;
100 }
101}
102
104 int sourceid)
105{
106 m_lcnOffset = getLcnOffset(sourceid);
107
108 if (_transports.empty())
109 {
110 if (m_useGui)
111 {
112 int channels = ChannelUtil::GetChannelCount(sourceid);
113
114 LOG(VB_GENERAL, LOG_INFO, LOC + (channels ?
115 (m_success ?
116 QString("Found %1 channels")
117 .arg(channels) :
118 "No new channels to process") :
119 "No channels to process.."));
120
121 QString msg;
122 if (!channels)
123 msg = tr("Failed to find any channels.");
124 else if (m_success)
125 msg = tr("Found %n channel(s)", "", channels);
126 else
127 msg = tr("Failed to find any new channels!");
128
129 if (m_useWeb)
130 m_pWeb->m_dlgMsg = msg;
131 else
132 ShowOkPopup(msg);
133
134 }
135 else
136 {
137 std::cout << (ChannelUtil::GetChannelCount() ?
138 "No new channels to process" :
139 "No channels to process..");
140 }
141
142 return;
143 }
144
145
146 // Temporary check, incomplete code
147 // Otherwise may crash the backend
148 // if (m_useWeb)
149 // return;
150
151 ScanDTVTransportList transports = _transports;
152 QString msg;
153 QTextStream ssMsg(&msg);
154
155 // Scan parameters
156 {
157 bool require_av = (m_serviceRequirements & kRequireAV) == kRequireAV;
158 bool require_a = (m_serviceRequirements & kRequireAudio) != 0;
159 const char *desired { "all" };
160 if (require_av)
161 desired = "tv";
162 else if (require_a)
163 desired = "tv+radio";
164 ssMsg << Qt::endl << Qt::endl;
165 ssMsg << "Scan parameters:" << Qt::endl;
166 ssMsg << "Desired Services : " << desired << Qt::endl;
167 ssMsg << "Unencrypted Only : " << (m_ftaOnly ? "yes" : "no") << Qt::endl;
168 ssMsg << "Logical Channel Numbers only: " << (m_lcnOnly ? "yes" : "no") << Qt::endl;
169 ssMsg << "Complete scan data required : " << (m_completeOnly ? "yes" : "no") << Qt::endl;
170 ssMsg << "Full search for old channels: " << (m_fullChannelSearch ? "yes" : "no") << Qt::endl;
171 ssMsg << "Remove duplicates : " << (m_removeDuplicates ? "yes" : "no") << Qt::endl;
172 }
173
174 // Transports and channels before processing
175 if (!transports.empty())
176 {
177 ssMsg << Qt::endl;
178 ssMsg << "Transport list before processing (" << transports.size() << "):" << Qt::endl;
179 ssMsg << FormatTransports(transports).toLatin1().constData();
180
182 ssMsg << Qt::endl;
183 ssMsg << "Channel list before processing (";
184 ssMsg << SimpleCountChannels(transports) << "):" << Qt::endl;
185 ssMsg << FormatChannels(transports, &info).toLatin1().constData();
186 }
187 LOG(VB_GENERAL, LOG_INFO, LOC + msg);
188
189 uint saved_scan = 0;
190 if (m_doSave)
191 saved_scan = SaveScan(transports);
192
193 // Merge transports with the same frequency into one
194 MergeSameFrequency(transports);
195
196 // Remove duplicate transports with a lower signal strength.
198 {
199 ScanDTVTransportList duplicates;
200 RemoveDuplicates(transports, duplicates);
201 if (!duplicates.empty())
202 {
203 msg = "";
204 ssMsg << Qt::endl;
205 ssMsg << "Discarded duplicate transports (" << duplicates.size() << "):" << Qt::endl;
206 ssMsg << FormatTransports(duplicates).toLatin1().constData() << Qt::endl;
207 ssMsg << "Discarded duplicate channels (" << SimpleCountChannels(duplicates) << "):" << Qt::endl;
208 ssMsg << FormatChannels(duplicates).toLatin1().constData() << Qt::endl;
209 LOG(VB_CHANSCAN, LOG_INFO, LOC + msg);
210 }
211 }
212
213 // Process Logical Channel Numbers
214 if (m_doLcn)
215 {
216 ChannelNumbers(transports);
217 }
218
219 // Remove the channels that do not pass various criteria.
220 FilterServices(transports);
221
222 // Remove the channels that have been relocated.
224 {
225 FilterRelocatedServices(transports);
226 }
227
228 // Pull in DB info in transports
229 // Channels not found in scan but only in DB are returned in db_trans
230 ScanDTVTransportList db_trans = GetDBTransports(sourceid, transports);
231 msg = "";
232 ssMsg << Qt::endl;
233 if (!db_trans.empty())
234 {
235 ssMsg << Qt::endl;
236 ssMsg << "Transports with channels in DB but not in scan (";
237 ssMsg << db_trans.size() << "):" << Qt::endl;
238 ssMsg << FormatTransports(db_trans).toLatin1().constData();
239 }
240
241 // Make sure "Open Cable" channels are marked that way.
242 FixUpOpenCable(transports);
243
244 // All channels in the scan after comparing with the database
245 {
247 ssMsg << Qt::endl;
248 ssMsg << "Channel list after compare with database (";
249 ssMsg << SimpleCountChannels(transports) << "):" << Qt::endl;
250 ssMsg << FormatChannels(transports, &info).toLatin1().constData();
251 }
252
253 // Add channels from the DB to the channels from the scan
254 // and possibly delete one or more of the off-air channels
255 if (m_doDelete)
256 {
257 ScanDTVTransportList trans = transports;
258 std::ranges::copy(db_trans, std::back_inserter(trans));
259 uint deleted_count = DeleteChannels(trans);
260 if (deleted_count)
261 transports = trans;
262 }
263
264 // Determine System Info standards..
266
267 // Determine uniqueness of various naming schemes
269 CollectUniquenessStats(transports, info);
270
271 // Final channel list
272 ssMsg << Qt::endl;
273 ssMsg << "Channel list (" << SimpleCountChannels(transports) << "):" << Qt::endl;
274 ssMsg << FormatChannels(transports).toLatin1().constData();
275
276 // Create summary
277 ssMsg << Qt::endl;
278 ssMsg << GetSummary(info, stats) << Qt::endl;
279
280 LOG(VB_GENERAL, LOG_INFO, LOC + msg);
281
282 if (m_doInsert)
283 InsertChannels(transports, info);
284
285 if (m_doDelete && sourceid)
286 DeleteUnusedTransports(sourceid);
287
288 if (m_doDelete || m_doInsert)
289 ScanInfo::MarkProcessed(saved_scan);
290}
291
293{
294 switch (type)
295 {
296 // non-conflicting
297 case kATSCNonConflicting: return "ATSC";
298 case kDVBNonConflicting: return "DVB";
299 case kSCTENonConflicting: return "SCTE";
300 case kMPEGNonConflicting: return "MPEG";
301 case kNTSCNonConflicting: return "NTSC";
302 // conflicting
303 case kATSCConflicting: return "ATSC";
304 case kDVBConflicting: return "DVB";
305 case kSCTEConflicting: return "SCTE";
306 case kMPEGConflicting: return "MPEG";
307 case kNTSCConflicting: return "NTSC";
308 }
309 return "Unknown";
310}
311
312// Ask user what to do with the off-air channels
313//
315 ScanDTVTransportList &transports)
316{
317 std::vector<uint> off_air_list;
318 QMap<uint,bool> deleted;
319 ScanDTVTransportList off_air_transports;
320
321 for (size_t i = 0; i < transports.size(); ++i)
322 {
323 ScanDTVTransport transport_copy;
324 for (size_t j = 0; j < transports[i].m_channels.size(); ++j)
325 {
326 ChannelInsertInfo chan = transports[i].m_channels[j];
327 bool was_in_db = (chan.m_dbMplexId != 0U) && (chan.m_channelId != 0U);
328 if (!was_in_db)
329 continue;
330
331 if (!chan.m_inPmt)
332 {
333 off_air_list.push_back(i<<16|j);
334 AddChanToCopy(transport_copy, transports[i], chan);
335 }
336 }
337 if (!transport_copy.m_channels.empty())
338 off_air_transports.push_back(transport_copy);
339 }
340
341 if (off_air_list.empty())
342 return 0;
343
344 // List of off-air channels (in database but not in the scan)
345 std::cout << "\nOff-air channels (" << SimpleCountChannels(off_air_transports) << "):\n";
346 ChannelImporterBasicStats infoA = CollectStats(off_air_transports);
347 std::cout << FormatChannels(off_air_transports, &infoA).toLatin1().constData() << '\n';
348
349 // Ask user whether to delete all or some of these stale channels
350 // if some is selected ask about each individually
351 //: %n is the number of channels
352 QString msg = tr("Found %n off-air channel(s).", "", off_air_list.size());
353 if (m_useWeb)
354 m_pWeb->log(msg);
357 return 0;
358
359 if (kDeleteAll == action)
360 {
361 for (uint item : off_air_list)
362 {
363 int i = item >> 16;
364 int j = item & 0xFFFF;
366 transports[i].m_channels[j].m_channelId);
367 deleted[item] = true;
368 }
369 }
370 else if (kDeleteInvisibleAll == action)
371 {
372 for (uint item : off_air_list)
373 {
374 int i = item >> 16;
375 int j = item & 0xFFFF;
376 int chanid = transports[i].m_channels[j].m_channelId;
377 QString channum = ChannelUtil::GetChanNum(chanid);
379 ChannelUtil::SetChannelValue("channum", QString("_%1").arg(channum),
380 chanid);
381 }
382 }
383 else
384 {
385 // TODO manual delete
386 }
387
388 // TODO delete encrypted channels when m_ftaOnly set
389
390 if (deleted.empty())
391 return 0;
392
393 // Create a new transports list without the deleted channels
394 ScanDTVTransportList newlist;
395 newlist.reserve(transports.size());
396 for (size_t i = 0; i < transports.size(); ++i)
397 {
398 newlist.push_back(transports[i]);
399 newlist.back().m_channels.clear();
400 for (size_t j = 0; j < transports[i].m_channels.size(); ++j)
401 {
402 if (!deleted.contains(i<<16|j))
403 {
404 newlist.back().m_channels.push_back(
405 transports[i].m_channels[j]);
406 }
407 }
408 }
409
410 transports = newlist;
411 return deleted.size();
412}
413
415{
417 query.prepare(
418 "SELECT mplexid FROM dtv_multiplex "
419 "WHERE sourceid = :SOURCEID1 AND "
420 " mplexid NOT IN "
421 " (SELECT mplexid "
422 " FROM channel "
423 " WHERE sourceid = :SOURCEID2)");
424 query.bindValue(":SOURCEID1", sourceid);
425 query.bindValue(":SOURCEID2", sourceid);
426 if (!query.exec())
427 {
428 MythDB::DBError("DeleteUnusedTransports() -- select", query);
429 return 0;
430 }
431
432 QString msg = tr("Found %n unused transport(s).", "", query.size());
433 LOG(VB_GENERAL, LOG_INFO, LOC + msg);
434 if (m_useWeb)
435 m_pWeb->log(msg);
436
437 if (query.size() == 0)
438 return 0;
439
442 return 0;
443
444 if (kDeleteAll == action)
445 {
446 query.prepare(
447 "DELETE FROM dtv_multiplex "
448 "WHERE sourceid = :SOURCEID1 AND "
449 " mplexid NOT IN "
450 " (SELECT mplexid "
451 " FROM channel "
452 " WHERE sourceid = :SOURCEID2)");
453 query.bindValue(":SOURCEID1", sourceid);
454 query.bindValue(":SOURCEID2", sourceid);
455 if (!query.exec())
456 {
457 MythDB::DBError("DeleteUnusedTransports() -- delete", query);
458 return 0;
459 }
460 }
461 else
462 {
463 // TODO manual delete
464 LOG(VB_GENERAL, LOG_INFO, LOC + "Manual delete of transport not implemented");
465 }
466 return 0;
467}
468
470 const ScanDTVTransportList &transports,
472{
473 ScanDTVTransportList list = transports;
474 ScanDTVTransportList inserted;
475 ScanDTVTransportList updated;
476 ScanDTVTransportList skipped_inserts;
477 ScanDTVTransportList skipped_updates;
478
479 // Insert or update all channels with non-conflicting channum
480 // and complete tuning information.
482 for (; chantype <= (uint) kChannelTypeNonConflictingLast; ++chantype)
483 {
484 auto type = (ChannelType) chantype;
485 uint new_chan = 0;
486 uint old_chan = 0;
487 CountChannels(list, info, type, new_chan, old_chan);
488
490 continue;
491
492 if (old_chan)
493 {
494 //: %n is the number of channels, %1 is the type of channel
495 QString msg = tr("Found %n old %1 channel(s).", "", old_chan)
496 .arg(toString(type));
497
499 list = UpdateChannels(list, info, action, type, updated, skipped_updates);
500 }
501 if (new_chan)
502 {
503 //: %n is the number of channels, %1 is the type of channel
504 QString msg = tr("Found %n new %1 channel(s).", "", new_chan)
505 .arg(toString(type));
506
508 list = InsertChannels(list, info, action, type, inserted, skipped_inserts);
509 }
510 }
511
512 if (!m_isInteractive)
513 return;
514
515 // If any of the potential uniques is high and inserting
516 // with those as the channum would result in few conflicts
517 // ask user if it is ok to to proceed using it as the channum
518
519 // For remaining channels with complete tuning information
520 // insert channels with contiguous list of numbers as the channums
522 for (; chantype <= (uint) kChannelTypeConflictingLast; ++chantype)
523 {
524 auto type = (ChannelType) chantype;
525 uint new_chan = 0;
526 uint old_chan = 0;
527 CountChannels(list, info, type, new_chan, old_chan);
528
529 if (old_chan)
530 {
531 //: %n is the number of channels, %1 is the type of channel
532 QString msg = tr("Found %n conflicting old %1 channel(s).",
533 "", old_chan).arg(toString(type));
534
536 list = UpdateChannels(list, info, action, type, updated, skipped_updates);
537 }
538 if (new_chan)
539 {
540 //: %n is the number of channels, %1 is the type of channel
541 QString msg = tr("Found %n new conflicting %1 channel(s).",
542 "", new_chan).arg(toString(type));
543
545 list = InsertChannels(list, info, action, type, inserted, skipped_inserts);
546 }
547 }
548
549 QString msg;
550 QTextStream ssMsg(&msg);
551
552 if (!updated.empty())
553 {
554 ssMsg << Qt::endl << Qt::endl;
555 ssMsg << "Updated old transports (" << updated.size() << "):" << Qt::endl;
556 ssMsg << FormatTransports(updated).toLatin1().constData();
557
558 ssMsg << Qt::endl;
559 ssMsg << "Updated old channels (" << SimpleCountChannels(updated) << "):" << Qt::endl;
560 ssMsg << FormatChannels(updated).toLatin1().constData();
561 }
562 if (!skipped_updates.empty())
563 {
564 ssMsg << Qt::endl;
565 ssMsg << "Skipped old channels (" << SimpleCountChannels(skipped_updates) << "):" << Qt::endl;
566 ssMsg << FormatChannels(skipped_updates).toLatin1().constData();
567 }
568 if (!inserted.empty())
569 {
570 ssMsg << Qt::endl;
571 ssMsg << "Inserted new channels (" << SimpleCountChannels(inserted) << "):" << Qt::endl;
572 ssMsg << FormatChannels(inserted).toLatin1().constData();
573 }
574 if (!skipped_inserts.empty())
575 {
576 ssMsg << Qt::endl;
577 ssMsg << "Skipped new channels (" << SimpleCountChannels(skipped_inserts) << "):" << Qt::endl;
578 ssMsg << FormatChannels(skipped_inserts).toLatin1().constData();
579 }
580
581 // Remaining channels and sum uniques again
582 if (!list.empty())
583 {
586 ssMsg << Qt::endl;
587 ssMsg << "Remaining channels (" << SimpleCountChannels(list) << "):" << Qt::endl;
588 ssMsg << FormatChannels(list).toLatin1().constData() << Qt::endl;
589 ssMsg << GetSummary(ninfo, nstats).toLatin1().constData();
590 }
591 LOG(VB_GENERAL, LOG_INFO, LOC + msg);
592}
593
594// ChannelImporter::InsertChannels
595//
596// transports List of channels to insert
597// info Channel statistics
598// action Insert all, Insert manually, Ignore all
599// type Channel type such as dvb or atsc
600// inserted_list List of inserted channels
601// skipped_list List of skipped channels
602//
603// return: List of channels that have not been inserted
604//
606 const ScanDTVTransportList &transports,
610 ScanDTVTransportList &inserted_list,
611 ScanDTVTransportList &skipped_list)
612{
613 ScanDTVTransportList next_list;
614
615 bool cancel_all = false;
616 bool ok_all = false;
617
618 // Reserve list memory up front. These lists will likely be over-sized.
619 inserted_list.reserve(transports.size());
620 skipped_list.reserve(transports.size());
621 next_list.reserve(transports.size());
622
623 // Insert all channels with non-conflicting channum
624 // and complete tuning information.
625 for (const auto & transport : transports)
626 {
627 ScanDTVTransport new_transport;
628 ScanDTVTransport inserted_transport;
629 ScanDTVTransport skipped_transport;
630
631 for (size_t j = 0; j < transport.m_channels.size(); ++j)
632 {
633 ChannelInsertInfo chan = transport.m_channels[j];
634
635 bool asked = false;
636 bool filter = false;
637 bool handle = false;
638 if (!chan.m_channelId && (kInsertIgnoreAll == action) &&
639 IsType(info, chan, type))
640 {
641 filter = true;
642 }
643 else if (!chan.m_channelId && IsType(info, chan, type))
644 {
645 handle = true;
646 }
647
648 if (cancel_all)
649 {
650 handle = false;
651 }
652
653 if (handle && kInsertManual == action)
654 {
655 OkCancelType rc = QueryUserInsert(transport, chan);
656 if (kOCTCancelAll == rc)
657 {
658 cancel_all = true;
659 handle = false;
660 }
661 else if (kOCTCancel == rc)
662 {
663 handle = false;
664 }
665 else if (kOCTOk == rc)
666 {
667 asked = true;
668 }
669 }
670
671 if (handle)
672 {
673 bool conflicting = false;
674 channum_not_empty(chan);
675 conflicting = ChannelUtil::IsConflicting(
676 chan.m_chanNum, chan.m_sourceId);
677
678 // Only ask if not already asked before with kInsertManual
679 if (m_isInteractive && !asked &&
680 (conflicting || (kChannelTypeConflictingFirst <= type)))
681 {
682 bool ok_done = false;
683 if (ok_all)
684 {
685 QString val = ComputeSuggestedChannelNum(chan);
686 bool ok = CheckChannelNumber(val, chan);
687 if (ok)
688 {
689 chan.m_chanNum = val;
690 conflicting = false;
691 ok_done = true;
692 }
693 }
694 if (!ok_done)
695 {
696 OkCancelType rc =
697 QueryUserResolve(transport, chan);
698
699 conflicting = true;
700 if (kOCTCancelAll == rc)
701 {
702 cancel_all = true;
703 }
704 else if (kOCTOk == rc)
705 {
706 conflicting = false;
707 }
708 else if (kOCTOkAll == rc)
709 {
710 conflicting = false;
711 ok_all = true;
712 }
713 }
714 }
715
716 if (conflicting)
717 {
718 handle = false;
719 }
720 }
721
722 bool inserted = false;
723 if (handle)
724 {
725 int chanid = ChannelUtil::CreateChanID(
726 chan.m_sourceId, chan.m_chanNum);
727
728 chan.m_channelId = (chanid > 0) ? chanid : 0;
729
730 if (chan.m_channelId)
731 {
732 uint tsid = chan.m_vctTsId;
733 tsid = tsid ? tsid : chan.m_sdtTsId;
734 tsid = tsid ? tsid : chan.m_patTsId;
735 tsid = tsid ? tsid : chan.m_vctChanTsId;
736
738 chan.m_sourceId, transport, tsid, chan.m_origNetId);
739 }
740
741 if (chan.m_channelId && chan.m_dbMplexId)
742 {
743 chan.m_channelId = chanid;
744
746 chan.m_dbMplexId,
747 chan.m_sourceId,
748 chan.m_channelId,
749 chan.m_callSign,
750 chan.m_serviceName,
751 chan.m_chanNum,
752 chan.m_serviceId,
755 chan.m_useOnAirGuide,
757 chan.m_freqId,
758 QString(),
759 chan.m_format,
760 QString(),
762 chan.m_serviceType);
763
764 if (!transport.m_iptvTuning.GetDataURL().isEmpty())
766 transport.m_iptvTuning);
767 }
768 }
769
770 if (inserted)
771 {
772 // Update list of inserted channels
773 AddChanToCopy(inserted_transport, transport, chan);
774 }
775
776 if (filter)
777 {
778 // Update list of skipped channels
779 AddChanToCopy(skipped_transport, transport, chan);
780 }
781 else if (!inserted)
782 {
783 // Update list of remaining channels
784 AddChanToCopy(new_transport, transport, chan);
785 }
786 }
787
788 if (!new_transport.m_channels.empty())
789 next_list.push_back(new_transport);
790
791 if (!skipped_transport.m_channels.empty())
792 skipped_list.push_back(skipped_transport);
793
794 if (!inserted_transport.m_channels.empty())
795 inserted_list.push_back(inserted_transport);
796 }
797
798 return next_list;
799}
800
801// ChannelImporter::UpdateChannels
802//
803// transports List of channels to update
804// info Channel statistics
805// action Update All, Ignore All
806// type Channel type such as dvb or atsc
807// updated_list List of updated channels
808// skipped_list List of skipped channels
809//
810// return: List of channels that have not been updated
811//
813 const ScanDTVTransportList &transports,
817 ScanDTVTransportList &updated_list,
818 ScanDTVTransportList &skipped_list) const
819{
820 ScanDTVTransportList next_list;
821
822 // Reserve list memory up front. These lists will likely be over-sized.
823 updated_list.reserve(transports.size());
824 skipped_list.reserve(transports.size());
825 next_list.reserve(transports.size());
826
827 // update all channels with non-conflicting channum
828 // and complete tuning information.
829 for (const auto & transport : transports)
830 {
831 ScanDTVTransport new_transport;
832 ScanDTVTransport updated_transport;
833 ScanDTVTransport skipped_transport;
834
835 for (size_t j = 0; j < transport.m_channels.size(); ++j)
836 {
837 ChannelInsertInfo chan = transport.m_channels[j];
838
839 bool filter = false;
840 bool handle = false;
841 if (chan.m_channelId && (kUpdateIgnoreAll == action) &&
842 IsType(info, chan, type))
843 {
844 filter = true;
845 }
846 else if (chan.m_channelId && IsType(info, chan, type))
847 {
848 handle = true;
849 }
850
851 if (handle)
852 {
853 bool conflicting = false;
854
856 {
858 }
859 channum_not_empty(chan);
860 conflicting = ChannelUtil::IsConflicting(
861 chan.m_chanNum, chan.m_sourceId, chan.m_channelId);
862
863 if (conflicting)
864 {
865 handle = false;
866
867 // Update list of skipped channels
868 AddChanToCopy(skipped_transport, transport, chan);
869 }
870 }
871
872 bool updated = false;
873 if (handle)
874 {
876
877 // Find the matching multiplex. This updates the
878 // transport and network ID's in case the transport
879 // was created manually
880 uint tsid = chan.m_vctTsId;
881 tsid = tsid ? tsid : chan.m_sdtTsId;
882 tsid = tsid ? tsid : chan.m_patTsId;
883 tsid = tsid ? tsid : chan.m_vctChanTsId;
884
886 chan.m_sourceId, transport, tsid, chan.m_origNetId);
887
889 if (chan.m_visible == kChannelAlwaysVisible ||
891 visible = chan.m_visible;
892 else if (chan.m_hidden)
893 visible = kChannelNotVisible;
894
896 chan.m_dbMplexId,
897 chan.m_sourceId,
898 chan.m_channelId,
899 chan.m_callSign,
900 chan.m_serviceName,
901 chan.m_chanNum,
902 chan.m_serviceId,
905 chan.m_useOnAirGuide,
906 visible,
907 chan.m_freqId,
908 QString(),
909 chan.m_format,
910 QString(),
912 chan.m_serviceType);
913 }
914
915 if (updated)
916 {
917 // Update list of updated channels
918 AddChanToCopy(updated_transport, transport, chan);
919 }
920
921 if (filter)
922 {
923 // Update list of skipped channels
924 AddChanToCopy(skipped_transport, transport, chan);
925 }
926 else if (!updated)
927 {
928 // Update list of remaining channels
929 AddChanToCopy(new_transport, transport, chan);
930 }
931 }
932
933 if (!new_transport.m_channels.empty())
934 next_list.push_back(new_transport);
935
936 if (!skipped_transport.m_channels.empty())
937 skipped_list.push_back(skipped_transport);
938
939 if (!updated_transport.m_channels.empty())
940 updated_list.push_back(updated_transport);
941 }
942
943 return next_list;
944}
945
946// ChannelImporter::AddChanToCopy
947//
948// Add channel to copy of transport.
949// This is used to keep track of what is done with each channel
950//
951// transport_copy with zero to all channels of transport
952// transport transport with channel info as scanned
953// chan one channel of transport, to be copied
954//
956 ScanDTVTransport &transport_copy,
957 const ScanDTVTransport &transport,
958 const ChannelInsertInfo &chan
959)
960{
961 if (transport_copy.m_channels.empty())
962 {
963 transport_copy = transport;
964 transport_copy.m_channels.clear();
965 }
966 transport_copy.m_channels.push_back(chan);
967}
968
969// ChannelImporter::MergeSameFrequency
970//
971// Merge transports that are on the same frequency by
972// combining all channels of both transports into one transport
973//
975{
976 ScanDTVTransportList no_dups;
977
979 if (!transports.empty())
980 tuner_type = transports[0].m_tunerType;
981
982 bool is_dvbs = ((DTVTunerType::kTunerTypeDVBS1 == tuner_type) ||
983 (DTVTunerType::kTunerTypeDVBS2 == tuner_type));
984
985 uint freq_mult = is_dvbs ? 1 : 1000;
986
987 std::vector<bool> ignore;
988 ignore.resize(transports.size());
989 for (size_t i = 0; i < transports.size(); ++i)
990 {
991 if (ignore[i])
992 continue;
993
994 for (size_t j = i+1; j < transports.size(); ++j)
995 {
996 if (!transports[i].IsEqual(
997 tuner_type, transports[j], 500 * freq_mult))
998 {
999 continue;
1000 }
1001
1002 for (size_t k = 0; k < transports[j].m_channels.size(); ++k)
1003 {
1004 bool found_same = false;
1005 for (size_t l = 0; l < transports[i].m_channels.size(); ++l)
1006 {
1007 if (transports[j].m_channels[k].IsSameChannel(
1008 transports[i].m_channels[l]))
1009 {
1010 found_same = true;
1011 transports[i].m_channels[l].ImportExtraInfo(
1012 transports[j].m_channels[k]);
1013 }
1014 }
1015 if (!found_same)
1016 transports[i].m_channels.push_back(transports[j].m_channels[k]);
1017 }
1018 LOG(VB_CHANSCAN, LOG_INFO, LOC +
1019 QString("Transport on same frequency:") + FormatTransport(transports[j]));
1020 ignore[j] = true;
1021 }
1022 no_dups.push_back(transports[i]);
1023 }
1024 transports = no_dups;
1025}
1026
1027// ChannelImporter::RemoveDuplicates
1028//
1029// When there are two transports that have the same list of channels
1030// but that are received on different frequencies then remove
1031// the transport with the weakest signal.
1032//
1033// In DVB two transports are duplicates when the original network ID and the
1034// transport ID are the same. This is possibly different in ATSC.
1035// Here all channels of both transports are compared.
1036//
1038{
1039 LOG(VB_CHANSCAN, LOG_INFO, LOC +
1040 QString("Number of transports:%1").arg(transports.size()));
1041
1042 ScanDTVTransportList no_dups;
1043 std::vector<bool> ignore;
1044 no_dups.reserve(transports.size());
1045 ignore.resize(transports.size());
1046 for (size_t i = 0; i < transports.size(); ++i)
1047 {
1048 ScanDTVTransport &ta = transports[i];
1049 LOG(VB_CHANSCAN, LOG_INFO, LOC + "Transport " +
1050 FormatTransport(ta) + QString(" size(%1)").arg(ta.m_channels.size()));
1051
1052 if (!ignore[i])
1053 {
1054 for (size_t j = i+1; j < transports.size(); ++j)
1055 {
1056 ScanDTVTransport &tb = transports[j];
1057 bool found_same = true;
1058 bool found_diff = true;
1059 if (ta.m_channels.size() == tb.m_channels.size())
1060 {
1061 LOG(VB_CHANSCAN, LOG_DEBUG, LOC + "Comparing transport A " +
1062 FormatTransport(ta) + QString(" size(%1)").arg(ta.m_channels.size()));
1063 LOG(VB_CHANSCAN, LOG_DEBUG, LOC + "Comparing transport B " +
1064 FormatTransport(tb) + QString(" size(%1)").arg(tb.m_channels.size()));
1065
1066 for (size_t k = 0; found_same && k < tb.m_channels.size(); ++k)
1067 {
1068 if (tb.m_channels[k].IsSameChannel(ta.m_channels[k], 0))
1069 {
1070 found_diff = false;
1071 }
1072 else
1073 {
1074 found_same = false;
1075 }
1076 }
1077 }
1078
1079 // Transport with the lowest signal strength is duplicate
1080 if (found_same && !found_diff)
1081 {
1082 size_t lowss = transports[i].m_signalStrength < transports[j].m_signalStrength ? i : j;
1083 ignore[lowss] = true;
1084 duplicates.push_back(transports[lowss]);
1085
1086 LOG(VB_CHANSCAN, LOG_INFO, LOC + "Duplicate transports found:");
1087 LOG(VB_CHANSCAN, LOG_INFO, LOC + "Transport A " + FormatTransport(transports[i]));
1088 LOG(VB_CHANSCAN, LOG_INFO, LOC + "Transport B " + FormatTransport(transports[j]));
1089 LOG(VB_CHANSCAN, LOG_INFO, LOC + "Discarding " + FormatTransport(transports[lowss]));
1090 }
1091 }
1092 }
1093 if (!ignore[i])
1094 {
1095 no_dups.push_back(transports[i]);
1096 }
1097 }
1098
1099 transports = no_dups;
1100}
1101
1103{
1104 bool require_av = (m_serviceRequirements & kRequireAV) == kRequireAV;
1105 bool require_a = (m_serviceRequirements & kRequireAudio) != 0;
1106
1107 for (auto & transport : transports)
1108 {
1109 ChannelInsertInfoList filtered;
1110 for (auto & channel : transport.m_channels)
1111 {
1112 if (m_ftaOnly && channel.m_isEncrypted &&
1113 channel.m_decryptionStatus != kEncDecrypted)
1114 continue;
1115
1116 if (require_a && channel.m_isDataService)
1117 continue;
1118
1119 if (require_av && channel.m_isAudioService)
1120 continue;
1121
1122 // Filter channels out that do not have a logical channel number
1123 if (m_lcnOnly && channel.m_chanNum.isEmpty())
1124 {
1125 QString msg = FormatChannel(transport, channel);
1126 LOG(VB_CHANSCAN, LOG_INFO, LOC + QString("No LCN: %1").arg(msg));
1127 continue;
1128 }
1129
1130 // Filter channels out that are not present in PAT and PMT.
1131 if (m_completeOnly &&
1132 !(channel.m_inPat &&
1133 channel.m_inPmt ))
1134 {
1135 QString msg = FormatChannel(transport, channel);
1136 LOG(VB_CHANSCAN, LOG_INFO, LOC + QString("Not in PAT/PMT: %1").arg(msg));
1137 continue;
1138 }
1139
1140 // Filter channels out that are not present in SDT and that are not ATSC
1141 if (m_completeOnly &&
1142 channel.m_atscMajorChannel == 0 &&
1143 channel.m_atscMinorChannel == 0 &&
1144 (!channel.m_inPat ||
1145 !channel.m_inPmt ||
1146 !channel.m_inSdt ||
1147 (channel.m_patTsId !=
1148 channel.m_sdtTsId)))
1149 {
1150 QString msg = FormatChannel(transport, channel);
1151 LOG(VB_CHANSCAN, LOG_INFO, LOC + QString("Not in PAT/PMT/SDT: %1").arg(msg));
1152 continue;
1153 }
1154
1155 // Filter channels out that do not have a name
1156 if (m_completeOnly && channel.m_serviceName.isEmpty())
1157 {
1158 QString msg = FormatChannel(transport, channel);
1159 LOG(VB_CHANSCAN, LOG_INFO, LOC + QString("No name: %1").arg(msg));
1160 continue;
1161 }
1162
1163 // Filter channels out only in channels.conf, i.e. not found
1164 if (channel.m_inChannelsConf &&
1165 !(channel.m_inPat ||
1166 channel.m_inPmt ||
1167 channel.m_inVct ||
1168 channel.m_inNit ||
1169 channel.m_inSdt))
1170 continue;
1171
1172 filtered.push_back(channel);
1173 }
1174 transport.m_channels = filtered;
1175 }
1176}
1177
1179{
1180 QMap<uint64_t, bool> rs;
1181
1182 // Search all channels to find relocated services
1183 for (auto & transport : transports)
1184 {
1185 for (auto & channel : transport.m_channels)
1186 {
1187 if (channel.m_oldOrigNetId > 0)
1188 {
1189 uint64_t key = ((uint64_t)channel.m_oldOrigNetId << 32) | (channel.m_oldTsId << 16) | channel.m_oldServiceId;
1190 rs[key] = true;
1191 }
1192 }
1193 }
1194
1195 // Remove all relocated services
1196 for (auto & transport : transports)
1197 {
1198 ChannelInsertInfoList filtered;
1199 for (auto & channel : transport.m_channels)
1200 {
1201 uint64_t key = ((uint64_t)channel.m_origNetId << 32) | (channel.m_sdtTsId << 16) | channel.m_serviceId;
1202 if (rs.value(key, false))
1203 {
1204 QString msg = FormatChannel(transport, channel);
1205 LOG(VB_CHANSCAN, LOG_INFO, LOC + QString("Relocated: %1").arg(msg));
1206 continue;
1207 }
1208 filtered.push_back(channel);
1209 }
1210 transport.m_channels = filtered;
1211 }
1212}
1213
1214// Process DVB Channel Numbers
1216{
1217 QMap<qlonglong, uint> map_sid_scn; // HD Simulcast channel numbers, service ID is key
1218 QMap<qlonglong, uint> map_sid_lcn; // Logical channel numbers, service ID is key
1219 QMap<uint, qlonglong> map_lcn_sid; // Logical channel numbers, channel number is key
1220
1221 LOG(VB_CHANSCAN, LOG_INFO, LOC + QString("Process DVB Channel Numbers"));
1222 for (auto & transport : transports)
1223 {
1224 for (auto & channel : transport.m_channels)
1225 {
1226 LOG(VB_CHANSCAN, LOG_DEBUG, LOC + QString("Channel onid:%1 sid:%2 lcn:%3 scn:%4")
1227 .arg(channel.m_origNetId).arg(channel.m_serviceId).arg(channel.m_logicalChannel)
1228 .arg(channel.m_simulcastChannel));
1229 qlonglong key = ((qlonglong)channel.m_origNetId<<32) | channel.m_serviceId;
1230 if (channel.m_logicalChannel > 0)
1231 {
1232 map_sid_lcn[key] = channel.m_logicalChannel;
1233 map_lcn_sid[channel.m_logicalChannel] = key;
1234 }
1235 if (channel.m_simulcastChannel > 0)
1236 {
1237 map_sid_scn[key] = channel.m_simulcastChannel;
1238 }
1239 }
1240 }
1241
1242 // Process the HD Simulcast Channel Numbers
1243 //
1244 // For each channel with a HD Simulcast Channel Number, do use that
1245 // number as the Logical Channel Number; the SD channel that now has
1246 // this LCN does get the original LCN of the HD Simulcast channel.
1247 // If this is not selected then the Logical Channel Numbers are used
1248 // without the override from the HD Simulcast channel numbers.
1249 // This usually means that channel numbers 1, 2, 3 etc are used for SD channels
1250 // while the corresponding HD channels do have higher channel numbers.
1251 // When the HD Simulcast channel numbers are enabled then channel numbers 1, 2, 3 etc are
1252 // used for the HD channels and the corresponding SD channels use the high channel numbers.
1253 if (m_doScn)
1254 {
1255 LOG(VB_CHANSCAN, LOG_INFO, LOC + QString("Process Simulcast Channel Numbers"));
1256
1257 QMap<qlonglong, uint>::iterator it;
1258 for (it = map_sid_scn.begin(); it != map_sid_scn.end(); ++it)
1259 {
1260 // Exchange LCN between the SD channel and the HD simulcast channel
1261 qlonglong key_hd = it.key(); // Key of HD channel
1262 uint scn_hd = *it; // SCN of the HD channel
1263 uint lcn_sd = scn_hd; // Old LCN of the SD channel
1264 uint lcn_hd = map_sid_lcn[key_hd]; // Old LCN of the HD channel
1265
1266 qlonglong key_sd = map_lcn_sid[lcn_sd]; // Key of the SD channel
1267
1268 map_sid_lcn[key_sd] = lcn_hd; // SD channel gets old LCN of HD channel
1269 map_sid_lcn[key_hd] = lcn_sd; // HD channel gets old LCN of SD channel
1270 map_lcn_sid[lcn_hd] = key_sd; // SD channel gets key of SD channel
1271 map_lcn_sid[lcn_sd] = key_hd; // HD channel gets key of SD channel
1272 }
1273 }
1274
1275 // Update channels with the resulting Logical Channel Numbers
1276 LOG(VB_CHANSCAN, LOG_INFO, LOC + QString("Process Logical Channel Numbers"));
1277 for (auto & transport : transports)
1278 {
1279 for (auto & channel : transport.m_channels)
1280 {
1281 if (channel.m_chanNum.isEmpty())
1282 {
1283 qlonglong key = ((qlonglong)channel.m_origNetId<<32) | channel.m_serviceId;
1284 QMap<qlonglong, uint>::const_iterator it = map_sid_lcn.constFind(key);
1285 if (it != map_sid_lcn.cend())
1286 {
1287 channel.m_chanNum = QString::number(*it + m_lcnOffset);
1288 LOG(VB_CHANSCAN, LOG_DEBUG, LOC +
1289 QString("Final channel sid:%1 channel %2")
1290 .arg(channel.m_serviceId).arg(channel.m_chanNum));
1291 }
1292 else
1293 {
1294 LOG(VB_CHANSCAN, LOG_DEBUG, LOC +
1295 QString("Final channel sid:%1 NO channel number")
1296 .arg(channel.m_serviceId));
1297 }
1298 }
1299 else
1300 {
1301 LOG(VB_CHANSCAN, LOG_DEBUG, LOC +
1302 QString("Final channel sid:%1 has already channel number %2")
1303 .arg(channel.m_serviceId).arg(channel.m_chanNum));
1304 }
1305 }
1306 }
1307}
1308
1317 uint sourceid, ScanDTVTransportList &transports) const
1318{
1319 ScanDTVTransportList not_in_scan;
1320 int found_in_same_transport = 0;
1321 int found_in_other_transport = 0;
1322 int found_nowhere = 0;
1323
1325 if (!transports.empty())
1326 tuner_type = transports[0].m_tunerType;
1327
1328 bool is_dvbs =
1329 (DTVTunerType::kTunerTypeDVBS1 == tuner_type) ||
1330 (DTVTunerType::kTunerTypeDVBS2 == tuner_type);
1331
1332 uint freq_mult = is_dvbs ? 1 : 1000;
1333
1335 query.prepare(
1336 "SELECT mplexid "
1337 "FROM dtv_multiplex "
1338 "WHERE sourceid = :SOURCEID "
1339 "GROUP BY mplexid "
1340 "ORDER BY mplexid");
1341 query.bindValue(":SOURCEID", sourceid);
1342
1343 if (!query.exec())
1344 {
1345 MythDB::DBError("GetDBTransports()", query);
1346 return not_in_scan;
1347 }
1348
1349 QMap<uint,bool> found_in_scan;
1350 while (query.next())
1351 {
1352 ScanDTVTransport db_transport;
1353 uint mplexid = query.value(0).toUInt();
1354 if (db_transport.FillFromDB(tuner_type, mplexid))
1355 {
1356 if (db_transport.m_channels.empty())
1357 {
1358 continue;
1359 }
1360 }
1361
1362 bool found_transport = false;
1363 QMap<uint,bool> found_in_database;
1364
1365 // Search for old channels in the same transport of the scan.
1366 for (size_t ist = 0; ist < transports.size(); ++ist) // All transports in scan
1367 {
1368 ScanDTVTransport &scan_transport = transports[ist]; // Transport from the scan
1369 if (scan_transport.IsEqual(tuner_type, db_transport, 500 * freq_mult, true)) // Same transport?
1370 {
1371 found_transport = true; // Yes
1372 scan_transport.m_mplex = db_transport.m_mplex; // Found multiplex
1373 for (size_t jdc = 0; jdc < db_transport.m_channels.size(); ++jdc) // All channels in database transport
1374 {
1375 if (!found_in_database[jdc]) // Channel not found yet?
1376 {
1377 ChannelInsertInfo &db_chan = db_transport.m_channels[jdc]; // Channel in database transport
1378 for (size_t ksc = 0; ksc < scan_transport.m_channels.size(); ++ksc) // All channels in scanned transport
1379 { // Channel in scanned transport
1380 if (!found_in_scan[(ist<<16)+ksc]) // Scanned channel not yet found?
1381 {
1382 ChannelInsertInfo &scan_chan = scan_transport.m_channels[ksc];
1383 if (db_chan.IsSameChannel(scan_chan, 2)) // Same transport, relaxed check
1384 {
1385 found_in_same_transport++;
1386 found_in_database[jdc] = true; // Channel from db found in scan
1387 found_in_scan[(ist<<16)+ksc] = true; // Channel from scan found in db
1388 scan_chan.m_dbMplexId = db_transport.m_mplex; // Found multiplex
1389 scan_chan.m_channelId = db_chan.m_channelId; // This is the crucial field
1390 break; // Ready with scanned transport
1391 }
1392 }
1393 }
1394 }
1395 }
1396 }
1397 }
1398
1399 // Search for old channels in all transports of the scan.
1400 // This is done for all channels that have not yet been found.
1401 // This can identify the channels that have moved to another transport.
1403 {
1404 for (size_t ist = 0; ist < transports.size(); ++ist) // All transports in scan
1405 {
1406 ScanDTVTransport &scan_transport = transports[ist]; // Scanned transport
1407 for (size_t jdc = 0; jdc < db_transport.m_channels.size(); ++jdc) // All channels in database transport
1408 {
1409 if (!found_in_database[jdc]) // Channel not found yet?
1410 {
1411 ChannelInsertInfo &db_chan = db_transport.m_channels[jdc]; // Channel in database transport
1412 for (size_t ksc = 0; ksc < scan_transport.m_channels.size(); ++ksc) // All channels in scanned transport
1413 {
1414 if (!found_in_scan[(ist<<16)+ksc]) // Scanned channel not yet found?
1415 {
1416 ChannelInsertInfo &scan_chan = scan_transport.m_channels[ksc];
1417 if (db_chan.IsSameChannel(scan_chan, 1)) // Other transport, check
1418 { // network id and service id
1419 found_in_other_transport++;
1420 found_in_database[jdc] = true; // Channel from db found in scan
1421 found_in_scan[(ist<<16)+ksc] = true; // Channel from scan found in db
1422 scan_chan.m_channelId = db_chan.m_channelId; // This is the crucial field
1423 break; // Ready with scanned transport
1424 }
1425 }
1426 }
1427 }
1428 }
1429 }
1430 }
1431
1432 // If the transport in the database is found in the scan
1433 // then all channels in that transport that are not found
1434 // in the scan are copied to the "not_in_scan" list.
1435 if (found_transport)
1436 {
1437 ScanDTVTransport tmp = db_transport;
1438 tmp.m_channels.clear();
1439
1440 for (size_t idc = 0; idc < db_transport.m_channels.size(); ++idc)
1441 {
1442 if (!found_in_database[idc])
1443 {
1444 tmp.m_channels.push_back(db_transport.m_channels[idc]);
1445 found_nowhere++;
1446 }
1447 }
1448
1449 if (!tmp.m_channels.empty())
1450 not_in_scan.push_back(tmp);
1451 }
1452 }
1453 LOG(VB_GENERAL, LOG_INFO, LOC +
1454 QString("Old channels found in same transport: %1")
1455 .arg(found_in_same_transport));
1456 LOG(VB_GENERAL, LOG_INFO, LOC +
1457 QString("Old channels found in other transport: %1")
1458 .arg(found_in_other_transport));
1459 LOG(VB_GENERAL, LOG_INFO, LOC +
1460 QString("Old channels not found (off-air): %1")
1461 .arg(found_nowhere));
1462
1463 return not_in_scan;
1464}
1465
1467{
1469 for (auto & transport : transports)
1470 {
1471 for (auto & chan : transport.m_channels)
1472 {
1473 if (((chan.m_couldBeOpencable && (chan.m_siStandard == "mpeg")) ||
1474 chan.m_isOpencable) && !chan.m_inVct)
1475 {
1476 chan.m_siStandard = "opencable";
1477 }
1478 }
1479 }
1480}
1481
1483 const ScanDTVTransportList &transports)
1484{
1486 for (const auto & transport : transports)
1487 {
1488 for (const auto & chan : transport.m_channels)
1489 {
1490 int enc {0};
1491 if (chan.m_isEncrypted)
1492 enc = (chan.m_decryptionStatus == kEncDecrypted) ? 2 : 1;
1493 if (chan.m_siStandard == "atsc") info.m_atscChannels[enc] += 1;
1494 if (chan.m_siStandard == "dvb") info.m_dvbChannels[enc] += 1;
1495 if (chan.m_siStandard == "mpeg") info.m_mpegChannels[enc] += 1;
1496 if (chan.m_siStandard == "opencable") info.m_scteChannels[enc] += 1;
1497 if (chan.m_siStandard == "ntsc") info.m_ntscChannels[enc] += 1;
1498 if (chan.m_siStandard != "ntsc")
1499 {
1500 ++info.m_progNumCnt[chan.m_serviceId];
1501 ++info.m_chanNumCnt[map_str(chan.m_chanNum)];
1502 }
1503 if (chan.m_siStandard == "atsc")
1504 {
1505 ++info.m_atscNumCnt[(chan.m_atscMajorChannel << 16) |
1506 (chan.m_atscMinorChannel)];
1507 ++info.m_atscMinCnt[chan.m_atscMinorChannel];
1508 ++info.m_atscMajCnt[chan.m_atscMajorChannel];
1509 }
1510 if (chan.m_siStandard == "ntsc")
1511 {
1512 ++info.m_atscNumCnt[(chan.m_atscMajorChannel << 16) |
1513 (chan.m_atscMinorChannel)];
1514 }
1515 }
1516 }
1517
1518 return info;
1519}
1520
1522 const ScanDTVTransportList &transports,
1524{
1526
1527 for (const auto & transport : transports)
1528 {
1529 for (const auto & chan : transport.m_channels)
1530 {
1531 stats.m_uniqueProgNum +=
1532 (info.m_progNumCnt[chan.m_serviceId] == 1) ? 1 : 0;
1533 stats.m_uniqueChanNum +=
1534 (info.m_chanNumCnt[map_str(chan.m_chanNum)] == 1) ? 1 : 0;
1535
1536 if (chan.m_siStandard == "atsc")
1537 {
1538 stats.m_uniqueAtscNum +=
1539 (info.m_atscNumCnt[(chan.m_atscMajorChannel << 16) |
1540 (chan.m_atscMinorChannel)] == 1) ? 1 : 0;
1541 stats.m_uniqueAtscMin +=
1542 (info.m_atscMinCnt[(chan.m_atscMinorChannel)] == 1) ? 1 : 0;
1543 stats.m_maxAtscMajCnt = std::max(
1544 stats.m_maxAtscMajCnt,
1545 info.m_atscMajCnt[chan.m_atscMajorChannel]);
1546 }
1547 }
1548 }
1549
1550 stats.m_uniqueTotal = (stats.m_uniqueProgNum + stats.m_uniqueAtscNum +
1551 stats.m_uniqueAtscMin + stats.m_uniqueChanNum);
1552
1553 return stats;
1554}
1555
1556
1558 const ScanDTVTransport &transport,
1559 const ChannelInsertInfo &chan,
1561{
1562 QString msg;
1563 QTextStream ssMsg(&msg);
1564
1565 ssMsg << transport.m_frequency << ":";
1566
1567 QString si_standard = (chan.m_siStandard=="opencable") ?
1568 QString("scte") : chan.m_siStandard;
1569
1570 if (si_standard == "atsc" || si_standard == "scte")
1571 {
1572 ssMsg << (QString("%1:%2:%3-%4:%5:%6=%7=%8:%9")
1573 .arg(chan.m_callSign, chan.m_chanNum)
1574 .arg(chan.m_atscMajorChannel)
1575 .arg(chan.m_atscMinorChannel)
1576 .arg(chan.m_serviceId)
1577 .arg(chan.m_vctTsId)
1578 .arg(chan.m_vctChanTsId)
1579 .arg(chan.m_patTsId)
1580 .arg(si_standard)).toLatin1().constData();
1581 }
1582 else if (si_standard == "dvb")
1583 {
1584 ssMsg << (QString("%1:%2:%3:%4:%5:%6=%7:%8")
1585 .arg(chan.m_serviceName, chan.m_chanNum)
1586 .arg(chan.m_netId).arg(chan.m_origNetId)
1587 .arg(chan.m_serviceId)
1588 .arg(chan.m_sdtTsId)
1589 .arg(chan.m_patTsId)
1590 .arg(si_standard)).toLatin1().constData();
1591 }
1592 else
1593 {
1594 ssMsg << (QString("%1:%2:%3:%4:%5")
1595 .arg(chan.m_callSign, chan.m_chanNum)
1596 .arg(chan.m_serviceId)
1597 .arg(chan.m_patTsId)
1598 .arg(si_standard)).toLatin1().constData();
1599 }
1600
1601 if (info)
1602 {
1603 ssMsg << ' ';
1604 msg = msg.leftJustified(72);
1605
1606 ssMsg << chan.m_channelId;
1607
1608 ssMsg << ":"
1609 << (QString("cnt(pnum:%1,channum:%2)")
1610 .arg(info->m_progNumCnt[chan.m_serviceId])
1611 .arg(info->m_chanNumCnt[map_str(chan.m_chanNum)])
1612 ).toLatin1().constData();
1613
1614 if (chan.m_siStandard == "atsc")
1615 {
1616 ssMsg <<
1617 (QString(":atsc_cnt(tot:%1,minor:%2)")
1618 .arg(info->m_atscNumCnt[
1619 (chan.m_atscMajorChannel << 16) |
1620 (chan.m_atscMinorChannel)])
1621 .arg(info->m_atscMinCnt[chan.m_atscMinorChannel])
1622 ).toLatin1().constData();
1623 }
1624 }
1625
1626 return msg;
1627}
1628
1641 const ScanDTVTransport &/*transport*/,
1642 const ChannelInsertInfo &chan)
1643{
1644 QString msg;
1645 QTextStream ssMsg(&msg);
1646
1647 QString si_standard = (chan.m_siStandard=="opencable") ?
1648 QString("scte") : chan.m_siStandard;
1649
1650 if (si_standard == "atsc" || si_standard == "scte")
1651 {
1652 if (si_standard == "atsc")
1653 {
1654 ssMsg << (kATSCChannelFormat
1655 .arg(chan.m_atscMajorChannel)
1656 .arg(chan.m_atscMinorChannel)).toLatin1().constData();
1657 }
1658 else if (chan.m_freqId.isEmpty())
1659 {
1660 ssMsg << (QString("%1-%2")
1661 .arg(chan.m_sourceId)
1662 .arg(chan.m_serviceId)).toLatin1().constData();
1663 }
1664 else
1665 {
1666 ssMsg << (QString("%1-%2")
1667 .arg(chan.m_freqId)
1668 .arg(chan.m_serviceId)).toLatin1().constData();
1669 }
1670
1671 if (!chan.m_callSign.isEmpty())
1672 ssMsg << (QString(" (%1)")
1673 .arg(chan.m_callSign)).toLatin1().constData();
1674 }
1675 else if (si_standard == "dvb")
1676 {
1677 ssMsg << (QString("%1 (%2 %3)")
1678 .arg(chan.m_serviceName).arg(chan.m_serviceId)
1679 .arg(chan.m_netId)).toLatin1().constData();
1680 }
1681 else if (chan.m_freqId.isEmpty())
1682 {
1683 ssMsg << (QString("%1-%2")
1684 .arg(chan.m_sourceId).arg(chan.m_serviceId))
1685 .toLatin1().constData();
1686 }
1687 else
1688 {
1689 ssMsg << (QString("%1-%2")
1690 .arg(chan.m_freqId).arg(chan.m_serviceId))
1691 .toLatin1().constData();
1692 }
1693
1694 return msg;
1695}
1696
1698 const ScanDTVTransportList &transports_in,
1700{
1701 // Sort transports in order of increasing frequency
1702 struct less_than_key
1703 {
1704 bool operator() (const ScanDTVTransport &t1, const ScanDTVTransport &t2)
1705 {
1706 return t1.m_frequency < t2.m_frequency;
1707 }
1708 };
1709 ScanDTVTransportList transports(transports_in);
1710 std::ranges::sort(transports, less_than_key());
1711
1712 QString msg;
1713
1714 for (auto & transport : transports)
1715 {
1716 auto fmt_chan = [transport, info](const QString & m, const auto & chan)
1717 { return m + FormatChannel(transport, chan, info) + "\n"; };
1718 msg = std::accumulate(transport.m_channels.cbegin(), transport.m_channels.cend(),
1719 msg, fmt_chan);
1720 }
1721
1722 return msg;
1723}
1724
1726 const ScanDTVTransport &transport)
1727{
1728 QString msg;
1729 QTextStream ssMsg(&msg);
1730 ssMsg << transport.toString();
1731 ssMsg << QString(" onid:%1").arg(transport.m_networkID);
1732 ssMsg << QString(" tsid:%1").arg(transport.m_transportID);
1733 ssMsg << QString(" ss:%1").arg(transport.m_signalStrength);
1734 return msg;
1735}
1736
1738 const ScanDTVTransportList &transports_in)
1739{
1740 // Sort transports in order of increasing frequency
1741 struct less_than_key
1742 {
1743 bool operator() (const ScanDTVTransport &t1, const ScanDTVTransport &t2)
1744 {
1745 return t1.m_frequency < t2.m_frequency;
1746 }
1747 };
1748 ScanDTVTransportList transports(transports_in);
1749 std::ranges::sort(transports, less_than_key());
1750
1751 auto fmt_trans = [](const QString& msg, const auto & transport)
1752 { return msg + FormatTransport(transport) + "\n"; };
1753 return std::accumulate(transports.cbegin(), transports.cend(),
1754 QString(), fmt_trans);
1755}
1756
1759 const ChannelImporterUniquenessStats &stats)
1760{
1761 QString msg = tr("Channels: FTA Enc Dec\n") +
1762 QString("ATSC %1 %2 %3\n")
1763 .arg(info.m_atscChannels[0],3)
1764 .arg(info.m_atscChannels[1],3)
1765 .arg(info.m_atscChannels[2],3) +
1766 QString("DVB %1 %2 %3\n")
1767 .arg(info.m_dvbChannels [0],3)
1768 .arg(info.m_dvbChannels [1],3)
1769 .arg(info.m_dvbChannels [2],3) +
1770 QString("SCTE %1 %2 %3\n")
1771 .arg(info.m_scteChannels[0],3)
1772 .arg(info.m_scteChannels[1],3)
1773 .arg(info.m_scteChannels[2],3) +
1774 QString("MPEG %1 %2 %3\n")
1775 .arg(info.m_mpegChannels[0],3)
1776 .arg(info.m_mpegChannels[1],3)
1777 .arg(info.m_mpegChannels[2],3) +
1778 QString("NTSC %1\n")
1779 .arg(info.m_ntscChannels[0],3) +
1780 tr("Unique: prog %1 atsc %2 atsc minor %3 channum %4\n")
1781 .arg(stats.m_uniqueProgNum).arg(stats.m_uniqueAtscNum)
1782 .arg(stats.m_uniqueAtscMin).arg(stats.m_uniqueChanNum) +
1783 tr("Max atsc major count: %1")
1784 .arg(stats.m_maxAtscMajCnt);
1785
1786 return msg;
1787}
1788
1791 const ChannelInsertInfo &chan, ChannelType type)
1792{
1793 switch (type)
1794 {
1796 return (chan.m_siStandard == "atsc") /* &&
1797 (info.m_atscNumCnt[(chan.m_atscMajorChannel << 16) |
1798 (chan.m_atscMinorChannel)] == 1) */;
1799
1800 case kDVBNonConflicting:
1801 return (chan.m_siStandard == "dvb") /* &&
1802 (info.m_progNumCnt[chan.m_serviceId] == 1) */;
1803
1805 return ((chan.m_siStandard == "mpeg") &&
1806 (info.m_chanNumCnt[map_str(chan.m_chanNum)] == 1));
1807
1809 return (((chan.m_siStandard == "scte") ||
1810 (chan.m_siStandard == "opencable")) &&
1811 (info.m_chanNumCnt[map_str(chan.m_chanNum)] == 1));
1812
1814 return ((chan.m_siStandard == "ntsc") &&
1815 (info.m_atscNumCnt[(chan.m_atscMajorChannel << 16) |
1816 (chan.m_atscMinorChannel)] == 1));
1817
1818 case kATSCConflicting:
1819 return ((chan.m_siStandard == "atsc") &&
1820 (info.m_atscNumCnt[(chan.m_atscMajorChannel << 16) |
1821 (chan.m_atscMinorChannel)] != 1));
1822
1823 case kDVBConflicting:
1824 return ((chan.m_siStandard == "dvb") &&
1825 (info.m_progNumCnt[chan.m_serviceId] != 1));
1826
1827 case kMPEGConflicting:
1828 return ((chan.m_siStandard == "mpeg") &&
1829 (info.m_chanNumCnt[map_str(chan.m_chanNum)] != 1));
1830
1831 case kSCTEConflicting:
1832 return (((chan.m_siStandard == "scte") ||
1833 (chan.m_siStandard == "opencable")) &&
1834 (info.m_chanNumCnt[map_str(chan.m_chanNum)] != 1));
1835
1836 case kNTSCConflicting:
1837 return ((chan.m_siStandard == "ntsc") &&
1838 (info.m_atscNumCnt[(chan.m_atscMajorChannel << 16) |
1839 (chan.m_atscMinorChannel)] != 1));
1840 }
1841 return false;
1842}
1843
1845 const ScanDTVTransportList &transports,
1847 ChannelType type, uint &new_chan, uint &old_chan)
1848{
1849 new_chan = old_chan = 0;
1850 for (const auto & transport : transports)
1851 {
1852 for (const auto& chan : transport.m_channels)
1853 {
1854 if (IsType(info, chan, type))
1855 {
1856 if (chan.m_channelId)
1857 ++old_chan;
1858 else
1859 ++new_chan;
1860 }
1861 }
1862 }
1863}
1864
1866 const ScanDTVTransportList &transports)
1867{
1868 auto add_count = [](int count, const auto & transport)
1869 { return count + transport.m_channels.size(); };
1870 return std::accumulate(transports.cbegin(), transports.cend(),
1871 0, add_count);
1872}
1873
1889 const ChannelInsertInfo &chan)
1890{
1891 static QMutex s_lastFreeLock;
1892 static QMap<uint,uint> s_lastFreeChanNumMap;
1893 QString chanNum;
1894
1895 // Suggest existing channel number if non-conflicting
1897 return chan.m_chanNum;
1898
1899 // Add a suffix to make it unique
1900 for (char suffix = 'A'; suffix <= 'Z'; ++suffix)
1901 {
1902 chanNum = chan.m_chanNum + suffix;
1903 if (!ChannelUtil::IsConflicting(chanNum, chan.m_sourceId))
1904 return chanNum;
1905 }
1906
1907 // Find unused channel number
1908 QMutexLocker locker(&s_lastFreeLock);
1909 uint last_free_chan_num = s_lastFreeChanNumMap[chan.m_sourceId];
1910 for (last_free_chan_num++; ; ++last_free_chan_num)
1911 {
1912 chanNum = QString::number(last_free_chan_num);
1913 if (!ChannelUtil::IsConflicting(chanNum, chan.m_sourceId))
1914 break;
1915 }
1916 s_lastFreeChanNumMap[chan.m_sourceId] = last_free_chan_num;
1917
1918 return chanNum;
1919}
1920
1923{
1925 if (m_useGui)
1926 {
1927 m_functorRetval = -1;
1928 while (m_functorRetval < 0)
1929 {
1930 if (m_useWeb) {
1931 m_pWeb->m_mutex.lock();
1932 m_pWeb->m_dlgMsg = msg;
1933 m_pWeb->m_dlgButtons.append(tr("Delete All"));
1934 m_pWeb->m_dlgButtons.append(tr("Set all invisible"));
1935 m_pWeb->m_dlgButtons.append(tr("Ignore All"));
1938 m_pWeb->m_mutex.unlock();
1939 continue;
1940 }
1941
1942 MythScreenStack *popupStack =
1943 GetMythMainWindow()->GetStack("popup stack");
1944 auto *deleteDialog =
1945 new MythDialogBox(msg, popupStack, "deletechannels");
1946
1947 if (deleteDialog->Create())
1948 {
1949 deleteDialog->AddButton(tr("Delete All"));
1950 deleteDialog->AddButton(tr("Set all invisible"));
1951// deleteDialog->AddButton(tr("Handle manually"));
1952 deleteDialog->AddButton(tr("Ignore All"));
1953 QObject::connect(deleteDialog, &MythDialogBox::Closed, this,
1954 [this](const QString & /*resultId*/, int result)
1955 {
1956 m_functorRetval = result;
1957 m_eventLoop.quit();
1958 });
1959 popupStack->AddScreen(deleteDialog);
1960
1961 m_eventLoop.exec();
1962 }
1963 }
1964
1965 switch (m_functorRetval)
1966 {
1967 case 0: action = kDeleteAll; break;
1968 case 1: action = kDeleteInvisibleAll; break;
1969 case 2: action = kDeleteIgnoreAll; break;
1970 }
1971 }
1972 else if (m_isInteractive)
1973 {
1974 std::cout << msg.toLatin1().constData()
1975 << '\n'
1976 << tr("Do you want to:").toLatin1().constData()
1977 << '\n'
1978 << tr("1. Delete All").toLatin1().constData()
1979 << '\n'
1980 << tr("2. Set all invisible").toLatin1().constData()
1981 << '\n'
1982// cout << "3. Handle manually" << endl;
1983 << tr("4. Ignore All").toLatin1().constData()
1984 << '\n';
1985 while (true)
1986 {
1987 std::string ret;
1988 std::cin >> ret;
1989 bool ok = false;
1990 uint val = QString(ret.c_str()).toUInt(&ok);
1991 if (ok && (val == 1 || val == 2 || val == 4))
1992 {
1993 action = (1 == val) ? kDeleteAll : action;
1994 action = (2 == val) ? kDeleteInvisibleAll : action;
1995 //action = (3 == val) ? kDeleteManual : action;
1996 action = (4 == val) ? kDeleteIgnoreAll : action;
1997 break;
1998 }
1999
2000 //cout << "Please enter either 1, 2, 3 or 4:" << endl;
2001 std::cout << tr("Please enter either 1, 2 or 4:")
2002 .toLatin1().constData() << '\n';
2003 }
2004 }
2005
2006 return action;
2007}
2008
2011{
2013 if (m_useGui)
2014 {
2015 m_functorRetval = -1;
2016 while (m_functorRetval < 0)
2017 {
2018 if (m_useWeb) {
2019 m_pWeb->m_mutex.lock();
2020 m_pWeb->m_dlgMsg = msg;
2021 m_pWeb->m_dlgButtons.append(tr("Insert All"));
2022 m_pWeb->m_dlgButtons.append(tr("Insert Manually"));
2023 m_pWeb->m_dlgButtons.append(tr("Ignore All"));
2026 m_pWeb->m_mutex.unlock();
2027 continue;
2028 }
2029
2030 MythScreenStack *popupStack =
2031 GetMythMainWindow()->GetStack("popup stack");
2032 auto *insertDialog =
2033 new MythDialogBox(msg, popupStack, "insertchannels");
2034
2035 if (insertDialog->Create())
2036 {
2037 insertDialog->AddButton(tr("Insert All"));
2038 insertDialog->AddButton(tr("Insert Manually"));
2039 insertDialog->AddButton(tr("Ignore All"));
2040 QObject::connect(insertDialog, &MythDialogBox::Closed, this,
2041 [this](const QString & /*resultId*/, int result)
2042 {
2043 m_functorRetval = result;
2044 m_eventLoop.quit();
2045 });
2046
2047 popupStack->AddScreen(insertDialog);
2048 m_eventLoop.exec();
2049 }
2050 }
2051
2052 switch (m_functorRetval)
2053 {
2054 case 0: action = kInsertAll; break;
2055 case 1: action = kInsertManual; break;
2056 case 2: action = kInsertIgnoreAll; break;
2057 }
2058 }
2059 else if (m_isInteractive)
2060 {
2061 std::cout << msg.toLatin1().constData()
2062 << '\n'
2063 << tr("Do you want to:").toLatin1().constData()
2064 << '\n'
2065 << tr("1. Insert All").toLatin1().constData()
2066 << '\n'
2067 << tr("2. Insert Manually").toLatin1().constData()
2068 << '\n'
2069 << tr("3. Ignore All").toLatin1().constData()
2070 << '\n';
2071 while (true)
2072 {
2073 std::string ret;
2074 std::cin >> ret;
2075 bool ok = false;
2076 uint val = QString(ret.c_str()).toUInt(&ok);
2077 if (ok && (1 <= val) && (val <= 3))
2078 {
2079 action = (1 == val) ? kInsertAll : action;
2080 action = (2 == val) ? kInsertManual : action;
2081 action = (3 == val) ? kInsertIgnoreAll : action;
2082 break;
2083 }
2084
2085 std::cout << tr("Please enter either 1, 2, or 3:")
2086 .toLatin1().constData() << '\n';
2087 }
2088 }
2089
2090 m_functorRetval = 0; // Reset default menu choice to first item for next menu
2091 return action;
2092}
2093
2096{
2098
2099 if (m_useGui)
2100 {
2101 m_functorRetval = -1;
2102 while (m_functorRetval < 0)
2103 {
2104 if (m_useWeb) {
2105 m_pWeb->m_mutex.lock();
2106 m_pWeb->m_dlgMsg = msg;
2107 m_pWeb->m_dlgButtons.append(tr("Update All"));
2108 m_pWeb->m_dlgButtons.append(tr("Ignore All"));
2111 m_pWeb->m_mutex.unlock();
2112 continue;
2113 }
2114
2115 MythScreenStack *popupStack =
2116 GetMythMainWindow()->GetStack("popup stack");
2117 auto *updateDialog =
2118 new MythDialogBox(msg, popupStack, "updatechannels");
2119
2120 if (updateDialog->Create())
2121 {
2122 updateDialog->AddButton(tr("Update All"));
2123 updateDialog->AddButton(tr("Ignore All"));
2124 QObject::connect(updateDialog, &MythDialogBox::Closed, this,
2125 [this](const QString& /*resultId*/, int result)
2126 {
2127 m_functorRetval = result;
2128 m_eventLoop.quit();
2129 });
2130
2131 popupStack->AddScreen(updateDialog);
2132 m_eventLoop.exec();
2133 }
2134 }
2135
2136 switch (m_functorRetval)
2137 {
2138 case 0: action = kUpdateAll; break;
2139 case 1: action = kUpdateIgnoreAll; break;
2140 }
2141 }
2142 else if (m_isInteractive)
2143 {
2144 std::cout << msg.toLatin1().constData()
2145 << '\n'
2146 << tr("Do you want to:").toLatin1().constData()
2147 << '\n'
2148 << tr("1. Update All").toLatin1().constData()
2149 << '\n'
2150 << tr("2. Update Manually").toLatin1().constData()
2151 << '\n'
2152 << tr("3. Ignore All").toLatin1().constData()
2153 << '\n';
2154 while (true)
2155 {
2156 std::string ret;
2157 std::cin >> ret;
2158 bool ok = false;
2159 uint val = QString(ret.c_str()).toUInt(&ok);
2160 if (ok && (1 <= val) && (val <= 3))
2161 {
2162 action = (1 == val) ? kUpdateAll : action;
2163 action = (2 == val) ? kUpdateManual : action;
2164 action = (3 == val) ? kUpdateIgnoreAll : action;
2165 break;
2166 }
2167
2168 std::cout << tr("Please enter either 1, 2, or 3:")
2169 .toLatin1().constData() << '\n';
2170 }
2171 }
2172 m_functorRetval = 0; // Reset default menu choice to first item for next menu
2173 return action;
2174}
2175
2177 const QString& title,
2178 const QString& message, QString &text)
2179{
2180 int dmc = m_functorRetval; // Default menu choice
2181 m_functorRetval = -1;
2182
2183 MythScreenStack *popupStack = nullptr;
2184 if (m_useWeb) {
2185 m_pWeb->m_mutex.lock();
2186 m_pWeb->m_dlgMsg = message;
2187 m_pWeb->m_dlgButtons.append(tr("OK"));
2188 m_pWeb->m_dlgButtons.append(tr("Edit"));
2189 m_pWeb->m_dlgButtons.append(tr("Cancel"));
2190 m_pWeb->m_dlgButtons.append(tr("Cancel All"));
2193 m_pWeb->m_mutex.unlock();
2194 }
2195 else
2196 {
2198 popupStack = parent->GetStack("popup stack");
2199 auto *popup = new MythDialogBox(title, message, popupStack,
2200 "manualchannelpopup");
2201
2202 if (popup->Create())
2203 {
2204 popup->AddButtonD(QCoreApplication::translate("(Common)", "OK"), 0 == dmc);
2205 popup->AddButtonD(tr("Edit"), 1 == dmc);
2206 popup->AddButtonD(QCoreApplication::translate("(Common)", "Cancel"), 2 == dmc);
2207 popup->AddButtonD(QCoreApplication::translate("(Common)", "Cancel All"), 3 == dmc);
2208 QObject::connect(popup, &MythDialogBox::Closed, this,
2209 [this](const QString & /*resultId*/, int result)
2210 {
2211 m_functorRetval = result;
2212 m_eventLoop.quit();
2213 });
2214 popupStack->AddScreen(popup);
2215 m_eventLoop.exec();
2216 }
2217 else
2218 {
2219 delete popup;
2220 popup = nullptr;
2221 }
2222 }
2223 // Choice "Edit"
2224 if (1 == m_functorRetval)
2225 {
2226
2227 if (m_useWeb) {
2228 m_pWeb->m_mutex.lock();
2229 m_pWeb->m_dlgMsg = tr("Please enter a unique channel number.");
2230 m_pWeb->m_dlgInputReq = true;
2233 text = m_pWeb->m_dlgString;
2234 m_pWeb->m_mutex.unlock();
2235 }
2236 else
2237 {
2238 auto *textEdit =
2239 new MythTextInputDialog(popupStack,
2240 tr("Please enter a unique channel number."),
2241 FilterNone, false, text);
2242 if (textEdit->Create())
2243 {
2244 QObject::connect(textEdit, &MythTextInputDialog::haveResult, this,
2245 [this,&text](QString result)
2246 {
2247 m_functorRetval = 0;
2248 text = std::move(result);
2249 });
2250 QObject::connect(textEdit, &MythTextInputDialog::Exiting, this,
2251 [this]()
2252 {
2253 m_eventLoop.quit();
2254 });
2255
2256 popupStack->AddScreen(textEdit);
2257 m_eventLoop.exec();
2258 }
2259 else
2260 {
2261 delete textEdit;
2262 }
2263 }
2264 }
2265 OkCancelType rval = kOCTCancel;
2266 switch (m_functorRetval) {
2267 case 0: rval = kOCTOk; break;
2268 // NOLINTNEXTLINE(bugprone-branch-clone)
2269 case 1: rval = kOCTCancel; break; // "Edit" is done already
2270 case 2: rval = kOCTCancel; break;
2271 case 3: rval = kOCTCancelAll; break;
2272 }
2273 return rval;
2274}
2275
2277 const QString& title,
2278 const QString& message, QString &text)
2279{
2280 int dmc = m_functorRetval; // Default menu choice
2281 m_functorRetval = -1;
2282
2283 MythScreenStack *popupStack = nullptr;
2284 if (m_useWeb) {
2285 m_pWeb->m_mutex.lock();
2286 m_pWeb->m_dlgMsg = message;
2287 m_pWeb->m_dlgButtons.append(tr("OK"));
2288 m_pWeb->m_dlgButtons.append(tr("OK All"));
2289 m_pWeb->m_dlgButtons.append(tr("Edit"));
2290 m_pWeb->m_dlgButtons.append(tr("Cancel"));
2291 m_pWeb->m_dlgButtons.append(tr("Cancel All"));
2294 m_pWeb->m_mutex.unlock();
2295 }
2296 else
2297 {
2299 popupStack = parent->GetStack("popup stack");
2300 auto *popup = new MythDialogBox(title, message, popupStack,
2301 "resolvechannelpopup");
2302
2303 if (popup->Create())
2304 {
2305 popup->AddButtonD(QCoreApplication::translate("(Common)", "OK"), 0 == dmc);
2306 popup->AddButtonD(QCoreApplication::translate("(Common)", "OK All"), 1 == dmc);
2307 popup->AddButtonD(tr("Edit"), 2 == dmc);
2308 popup->AddButtonD(QCoreApplication::translate("(Common)", "Cancel"), 3 == dmc);
2309 popup->AddButtonD(QCoreApplication::translate("(Common)", "Cancel All"), 4 == dmc);
2310 QObject::connect(popup, &MythDialogBox::Closed, this,
2311 [this](const QString & /*resultId*/, int result)
2312 {
2313 m_functorRetval = result;
2314 m_eventLoop.quit();
2315 });
2316 popupStack->AddScreen(popup);
2317 m_eventLoop.exec();
2318 }
2319 else
2320 {
2321 delete popup;
2322 popup = nullptr;
2323 }
2324 }
2325 // Choice "Edit"
2326 if (2 == m_functorRetval)
2327 {
2328 if (m_useWeb) {
2329 m_pWeb->m_mutex.lock();
2330 m_pWeb->m_dlgMsg = tr("Please enter a unique channel number.");
2331 m_pWeb->m_dlgInputReq = true;
2334 text = m_pWeb->m_dlgString;
2335 m_pWeb->m_mutex.unlock();
2336 }
2337 else
2338 {
2339 auto *textEdit =
2340 new MythTextInputDialog(popupStack,
2341 tr("Please enter a unique channel number."),
2342 FilterNone, false, text);
2343 if (textEdit->Create())
2344 {
2345 QObject::connect(textEdit, &MythTextInputDialog::haveResult, this,
2346 [this,&text](QString result)
2347 {
2348 m_functorRetval = 0;
2349 text = std::move(result);
2350 });
2351 QObject::connect(textEdit, &MythTextInputDialog::Exiting, this,
2352 [this]()
2353 {
2354 m_eventLoop.quit();
2355 });
2356
2357 popupStack->AddScreen(textEdit);
2358 m_eventLoop.exec();
2359 }
2360 else
2361 {
2362 delete textEdit;
2363 }
2364 }
2365 }
2366
2367 OkCancelType rval = kOCTCancel;
2368 switch (m_functorRetval) {
2369 case 0: rval = kOCTOk; break;
2370 case 1: rval = kOCTOkAll; break;
2371 // NOLINTNEXTLINE(bugprone-branch-clone)
2372 case 2: rval = kOCTCancel; break; // "Edit" is done already
2373 case 3: rval = kOCTCancel; break;
2374 case 4: rval = kOCTCancelAll; break;
2375 }
2376 return rval;
2377}
2378
2380 const ScanDTVTransport &transport,
2381 ChannelInsertInfo &chan)
2382{
2383 QString msg = tr("Channel %1 has channel number %2 but that is already in use.")
2384 .arg(SimpleFormatChannel(transport, chan),
2385 chan.m_chanNum);
2386
2388
2389 if (m_useGui)
2390 {
2391 while (true)
2392 {
2393 QString msg2 = msg;
2394 msg2 += "\n";
2395 msg2 += tr("Please enter a unique channel number.");
2396
2397 QString val = ComputeSuggestedChannelNum(chan);
2398 msg2 += "\n";
2399 msg2 += tr("Default value is %1.").arg(val);
2401 tr("Channel Importer"),
2402 msg2, val);
2403
2404 if (kOCTOk != ret && kOCTOkAll != ret)
2405 break; // user canceled..
2406
2407 bool ok = CheckChannelNumber(val, chan);
2408 if (ok)
2409 {
2410 chan.m_chanNum = val;
2411 break;
2412 }
2413 }
2414 }
2415 else if (m_isInteractive)
2416 {
2417 std::cout << msg.toLatin1().constData() << '\n';
2418
2419 QString cancelStr = QCoreApplication::translate("(Common)",
2420 "Cancel").toLower();
2421 QString cancelAllStr = QCoreApplication::translate("(Common)",
2422 "Cancel All").toLower();
2423 QString msg2 = tr("Please enter a non-conflicting channel number "
2424 "(or type '%1' to skip, '%2' to skip all):")
2425 .arg(cancelStr, cancelAllStr);
2426
2427 while (true)
2428 {
2429 std::cout << msg2.toLatin1().constData() << '\n';
2430 std::string sret;
2431 std::cin >> sret;
2432 QString val = QString(sret.c_str());
2433 if (val.toLower() == cancelStr)
2434 {
2435 ret = kOCTCancel;
2436 break; // user canceled..
2437 }
2438 if (val.toLower() == cancelAllStr)
2439 {
2440 ret = kOCTCancelAll;
2441 break; // user canceled..
2442 }
2443
2444 bool ok = CheckChannelNumber(val, chan);
2445 if (ok)
2446 {
2447 chan.m_chanNum = val;
2448 ret = kOCTOk;
2449 break;
2450 }
2451 }
2452 }
2453
2454 return ret;
2455}
2456
2458 const ScanDTVTransport &transport,
2459 ChannelInsertInfo &chan)
2460{
2461 QString msg = tr("You chose to manually insert channel %1.")
2462 .arg(SimpleFormatChannel(transport, chan));
2463
2465
2466 if (m_useGui)
2467 {
2468 while (true)
2469 {
2470 QString msg2 = msg;
2471 msg2 += " ";
2472 msg2 += tr("Please enter a unique channel number.");
2473
2474 QString val = ComputeSuggestedChannelNum(chan);
2475 msg2 += " ";
2476 msg2 += tr("Default value is %1").arg(val);
2478 tr("Channel Importer"),
2479 msg2, val);
2480
2481 if (kOCTOk != ret)
2482 break; // user canceled..
2483
2484 bool ok = CheckChannelNumber(val, chan);
2485 if (ok)
2486 {
2487 chan.m_chanNum = val;
2488 ret = kOCTOk;
2489 break;
2490 }
2491 }
2492 }
2493 else if (m_isInteractive)
2494 {
2495 std::cout << msg.toLatin1().constData() << '\n';
2496
2497 QString cancelStr = QCoreApplication::translate("(Common)", "Cancel").toLower();
2498 QString cancelAllStr = QCoreApplication::translate("(Common)", "Cancel All").toLower();
2499
2500 //: %1 is the translation of "Cancel", %2 of "Cancel All"
2501 QString msg2 = tr("Please enter a non-conflicting channel number "
2502 "(or type '%1' to skip, '%2' to skip all): ")
2503 .arg(cancelStr, cancelAllStr);
2504
2505 while (true)
2506 {
2507 std::cout << msg2.toLatin1().constData() << '\n';
2508 std::string sret;
2509 std::cin >> sret;
2510 QString val = QString(sret.c_str());
2511 if (val.toLower() == cancelStr)
2512 {
2513 ret = kOCTCancel;
2514 break; // user canceled..
2515 }
2516 if (val.toLower() == cancelAllStr)
2517 {
2518 ret = kOCTCancelAll;
2519 break; // user canceled..
2520 }
2521
2522 bool ok = CheckChannelNumber(val, chan);
2523 if (ok)
2524 {
2525 chan.m_chanNum = val;
2526 ret = kOCTOk;
2527 break;
2528 }
2529 }
2530 }
2531
2532 return ret;
2533}
2534
2535// ChannelImporter::CheckChannelNumber
2536//
2537// Check validity of a new channel number.
2538// The channel number is not a number but it is a string that starts with a digit.
2539// The channel number should not yet exist in this video source.
2540//
2542 const QString &num,
2543 const ChannelInsertInfo &chan)
2544{
2545 bool ok = (num.length() >= 1);
2546 ok = ok && ((num[0] >= '0') && (num[0] <= '9'));
2547 ok = ok && !ChannelUtil::IsConflicting(
2548 num, chan.m_sourceId, chan.m_channelId);
2549 return ok;
2550}
2551
2552#include "moc_channelimporter.cpp"
#define LOC
static QString map_str(QString str)
static void channum_not_empty(ChannelInsertInfo &chan)
static const QString kATSCChannelFormat
static uint getLcnOffset(int sourceid)
OkCancelType
@ kOCTOkAll
@ kOCTCancel
@ kOCTOk
@ kOCTCancelAll
ChannelVisibleType
Definition: channelinfo.h:20
@ kChannelNeverVisible
Definition: channelinfo.h:24
@ kChannelNotVisible
Definition: channelinfo.h:23
@ kChannelAlwaysVisible
Definition: channelinfo.h:21
@ kChannelVisible
Definition: channelinfo.h:22
std::vector< ChannelInsertInfo > ChannelInsertInfoList
Definition: channelinfo.h:264
ServiceRequirements
@ kRequireAudio
@ kRequireAV
static QString ComputeSuggestedChannelNum(const ChannelInsertInfo &chan)
Compute a suggested channel number that is unique in the video source.
static ChannelImporterBasicStats CollectStats(const ScanDTVTransportList &transports)
static QString FormatTransports(const ScanDTVTransportList &transports_in)
UpdateAction QueryUserUpdate(const QString &msg)
For multiple channels.
ChannelScannerWeb * m_pWeb
static QString toString(ChannelType type)
static QString FormatTransport(const ScanDTVTransport &transport)
void ChannelNumbers(ScanDTVTransportList &transports) const
static bool IsType(const ChannelImporterBasicStats &info, const ChannelInsertInfo &chan, ChannelType type)
OkCancelType ShowManualChannelPopup(const QString &title, const QString &message, QString &text)
void Process(const ScanDTVTransportList &_transports, int sourceid=-1)
static bool CheckChannelNumber(const QString &num, const ChannelInsertInfo &chan)
static void CountChannels(const ScanDTVTransportList &transports, const ChannelImporterBasicStats &info, ChannelType type, uint &new_chan, uint &old_chan)
static void RemoveDuplicates(ScanDTVTransportList &transports, ScanDTVTransportList &duplicates)
static void AddChanToCopy(ScanDTVTransport &transport_copy, const ScanDTVTransport &transport, const ChannelInsertInfo &chan)
static QString SimpleFormatChannel(const ScanDTVTransport &transport, const ChannelInsertInfo &chan)
Format channel information into a simple string.
uint DeleteChannels(ScanDTVTransportList &transports)
uint DeleteUnusedTransports(uint sourceid)
static void MergeSameFrequency(ScanDTVTransportList &transports)
void InsertChannels(const ScanDTVTransportList &transports, const ChannelImporterBasicStats &info)
static ChannelImporterUniquenessStats CollectUniquenessStats(const ScanDTVTransportList &transports, const ChannelImporterBasicStats &info)
static void FilterRelocatedServices(ScanDTVTransportList &transports)
QEventLoop m_eventLoop
OkCancelType QueryUserResolve(const ScanDTVTransport &transport, ChannelInsertInfo &chan)
For a single channel.
ScanDTVTransportList UpdateChannels(const ScanDTVTransportList &transports, const ChannelImporterBasicStats &info, UpdateAction action, ChannelType type, ScanDTVTransportList &updated, ScanDTVTransportList &skipped) const
ServiceRequirements m_serviceRequirements
DeleteAction QueryUserDelete(const QString &msg)
For multiple channels.
ScanDTVTransportList GetDBTransports(uint sourceid, ScanDTVTransportList &transports) const
Adds found channel info to transports list, returns channels in DB which were not found in scan in an...
InsertAction QueryUserInsert(const QString &msg)
For multiple channels.
static QString FormatChannels(const ScanDTVTransportList &transports, const ChannelImporterBasicStats *info=nullptr)
static QString FormatChannel(const ScanDTVTransport &transport, const ChannelInsertInfo &chan, const ChannelImporterBasicStats *info=nullptr)
ChannelImporter(bool gui, bool interactive, bool _delete, bool insert, bool save, bool fta_only, bool lcn_only, bool complete_only, bool full_channel_search, bool remove_duplicates, ServiceRequirements service_requirements, bool success=false)
static void FixUpOpenCable(ScanDTVTransportList &transports)
static int SimpleCountChannels(const ScanDTVTransportList &transports)
static QString GetSummary(const ChannelImporterBasicStats &info, const ChannelImporterUniquenessStats &stats)
OkCancelType ShowResolveChannelPopup(const QString &title, const QString &message, QString &text)
void FilterServices(ScanDTVTransportList &transports) const
bool IsSameChannel(const ChannelInsertInfo &other, int relaxed=0) const
ChannelVisibleType m_visible
Definition: channelinfo.h:229
QString m_serviceName
Definition: channelinfo.h:220
QString m_defaultAuthority
Definition: channelinfo.h:234
QString m_siStandard
Definition: channelinfo.h:243
QWaitCondition m_waitCondition
void log(const QString &msg)
static ChannelScannerWeb * getInstance()
static bool CreateIPTVTuningData(uint channel_id, const IPTVTuningData &tuning)
Definition: channelutil.h:155
static bool DeleteChannel(uint channel_id)
static bool UpdateChannel(uint db_mplexid, uint source_id, uint channel_id, const QString &callsign, const QString &service_name, const QString &chan_num, uint service_id, uint atsc_major_channel, uint atsc_minor_channel, bool use_on_air_guide, ChannelVisibleType visible, const QString &freqid=QString(), const QString &icon=QString(), QString format=QString(), const QString &xmltvid=QString(), const QString &default_authority=QString(), uint service_type=0, int recpriority=INT_MIN, int tmOffset=INT_MIN, int commMethod=INT_MIN)
static uint CreateMultiplex(int sourceid, const QString &sistandard, uint64_t frequency, const QString &modulation, int transport_id=-1, int network_id=-1)
static bool SetVisible(uint channel_id, ChannelVisibleType visible)
static bool SetChannelValue(const QString &field_name, const QString &value, uint sourceid, const QString &channum)
static uint GetChannelCount(int sourceid=-1)
static int CreateChanID(uint sourceid, const QString &chan_num)
Creates a unique channel ID for database use.
static QString GetChanNum(int chan_id)
Returns the channel-number string of the given channel.
static bool CreateChannel(uint db_mplexid, uint db_sourceid, uint new_channel_id, const QString &callsign, const QString &service_name, const QString &chan_num, uint service_id, uint atsc_major_channel, uint atsc_minor_channel, bool use_on_air_guide, ChannelVisibleType visible, const QString &freqid, const QString &icon=QString(), QString format="Default", const QString &xmltvid=QString(), const QString &default_authority=QString(), uint service_type=0, int recpriority=0, int tmOffset=0, int commMethod=-1)
static void UpdateChannelNumberFromDB(ChannelInsertInfo &chan)
static void UpdateInsertInfoFromDB(ChannelInsertInfo &chan)
static bool IsConflicting(const QString &channum, uint sourceid=0, uint excluded_chanid=0)
Definition: channelutil.h:303
QString toString() const
bool IsEqual(DTVTunerType type, const DTVMultiplex &other, uint freq_range=0, bool fuzzy=false) const
uint64_t m_frequency
Definition: dtvmultiplex.h:94
static const int kTunerTypeDVBS2
static const int kTunerTypeDVBS1
static const int kTunerTypeATSC
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:129
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:839
QVariant value(int i) const
Definition: mythdbcon.h:205
int size(void) const
Definition: mythdbcon.h:215
bool isActive(void) const
Definition: mythdbcon.h:216
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:620
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:890
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:814
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:552
bool IsBackend(void) const
is this process a backend process
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:225
Basic menu dialog, message and a list of options.
void Closed(QString, int)
MythScreenStack * GetStack(const QString &Stackname)
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
Dialog prompting the user to enter a text string.
void haveResult(QString)
bool FillFromDB(DTVTunerType type, uint mplexid) override
ChannelInsertInfoList m_channels
Definition: dtvmultiplex.h:138
static bool MarkProcessed(uint scanid)
Definition: scaninfo.cpp:202
unsigned int uint
Definition: compat.h:60
std::vector< ScanDTVTransport > ScanDTVTransportList
Definition: dtvmultiplex.h:143
@ kEncDecrypted
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
MythConfirmationDialog * ShowOkPopup(const QString &message, bool showCancel)
Non-blocking version of MythPopupBox::showOkPopup()
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythMainWindow * GetMythMainWindow(void)
@ FilterNone
MBASE_PUBLIC long long copy(QFile &dst, QFile &src, uint block_size=0)
Copies src file to dst file.
dictionary info
Definition: azlyrics.py:7
uint SaveScan(const ScanDTVTransportList &scan)
Definition: scaninfo.cpp:22