MythTV master
iptvchannelfetcher.cpp
Go to the documentation of this file.
1// -*- Mode: c++ -*-
2
3// Std C headers
4#include <algorithm>
5#include <cmath>
6#include <unistd.h>
7#include <utility>
8
9// Qt headers
10#include <QFile>
11#include <QRegularExpression>
12#include <QTextStream>
13
14// MythTV headers
17#include "libmythbase/mythdb.h"
19
20#include "cardutil.h"
21#include "channelutil.h"
22#include "iptvchannelfetcher.h"
23#include "scanmonitor.h"
24
25#define LOC QString("IPTVChanFetch: ")
26
27static bool parse_chan_info(const QString &rawdata,
29 QString &channum,
30 uint &lineNum);
31
32static bool parse_extinf(const QString &line,
33 QString &channum,
34 QString &name,
35 QString &logo);
36
38 uint cardid, QString inputname, uint sourceid,
39 bool is_mpts, ScanMonitor *monitor) :
40 m_scanMonitor(monitor),
41 m_cardId(cardid), m_inputName(std::move(inputname)),
42 m_sourceId(sourceid), m_isMpts(is_mpts),
43 m_thread(new MThread("IPTVChannelFetcher", this))
44{
45 LOG(VB_CHANNEL, LOG_INFO, LOC + QString("Has ScanMonitor %1")
46 .arg(monitor?"true":"false"));
47}
48
50{
51 Stop();
52 delete m_thread;
53 m_thread = nullptr;
54}
55
60{
61 m_lock.lock();
62
63 while (m_threadRunning)
64 {
65 m_stopNow = true;
66 m_lock.unlock();
67 m_thread->wait(5ms);
68 m_lock.lock();
69 }
70
71 m_lock.unlock();
72
73 m_thread->wait();
74}
75
77{
78 while (!m_thread->isFinished())
79 m_thread->wait(500ms);
80
81 LOG(VB_CHANNEL, LOG_INFO, LOC + QString("Found %1 channels")
82 .arg(m_channels.size()));
83 return m_channels;
84}
85
88{
89 Stop();
90 m_stopNow = false;
91 m_thread->start();
92}
93
94// Download the IPTV channel logo and write to configured location.
95static bool download_logo(const QString& logoUrl, const QString& filename)
96{
97 bool ret = false;
98
99 if (!logoUrl.isEmpty())
100 {
101 QByteArray data;
102 if (GetMythDownloadManager()->download(logoUrl, &data))
103 {
104 QString iconDir = GetConfDir() + "/channels/";
105 QString filepath = iconDir + filename;
106 QFile file(filepath);
107 if (file.open(QIODevice::WriteOnly))
108 {
109 if (file.write(data) > 0)
110 {
111 ret = true;
112 LOG(VB_CHANNEL, LOG_DEBUG, LOC +
113 QString("DownloadLogo to file %1").arg(filepath));
114 }
115 else
116 {
117 LOG(VB_CHANNEL, LOG_ERR, LOC +
118 QString("DownloadLogo failed to write to file %1").arg(filepath));
119 }
120 file.close();
121 }
122 else
123 {
124 LOG(VB_CHANNEL, LOG_ERR, LOC +
125 QString("DownloadLogo failed to open file %1").arg(filepath));
126 }
127 }
128 else
129 {
130 LOG(VB_CHANNEL, LOG_ERR, LOC +
131 QString("DownloadLogo failed to download %1").arg(logoUrl));
132 }
133 }
134 else
135 {
136 LOG(VB_CHANNEL, LOG_DEBUG, LOC + "DownloadLogo empty logoUrl");
137 }
138 return ret;
139}
140
142{
143 m_lock.lock();
144 m_threadRunning = true;
145 m_lock.unlock();
146
147 // Step 1/4 : Get info from DB
148 QString url = CardUtil::GetVideoDevice(m_cardId);
149
150 if (m_stopNow || url.isEmpty())
151 {
152 LOG(VB_CHANNEL, LOG_INFO, LOC + "Playlist URL is empty");
153 QMutexLocker locker(&m_lock);
154 m_threadRunning = false;
155 m_stopNow = true;
156 return;
157 }
158
159 LOG(VB_CHANNEL, LOG_INFO, LOC + QString("Playlist URL: %1").arg(url));
160
161 // Step 2/4 : Download
162 if (m_scanMonitor)
163 {
164 m_scanMonitor->ScanAppendTextToLog(tr("Downloading Playlist"));
166 }
167
168 QString playlist = DownloadPlaylist(url);
169
170 if (m_stopNow || playlist.isEmpty())
171 {
172 if (playlist.isNull() && m_scanMonitor)
173 {
175 QCoreApplication::translate("(Common)", "Error"));
177 m_scanMonitor->ScanErrored(tr("Downloading Playlist Failed"));
178 }
179 QMutexLocker locker(&m_lock);
180 m_threadRunning = false;
181 m_stopNow = true;
182 return;
183 }
184
185 LOG(VB_CHANNEL, LOG_DEBUG, LOC + QString("Playlist file size: %1").arg(playlist.length()));
186
187 // Step 3/4 : Process
188 if (m_scanMonitor)
189 {
190 m_scanMonitor->ScanAppendTextToLog(tr("Processing Playlist"));
192 }
193
194 m_channels.clear();
195 m_channels = ParsePlaylist(playlist, this);
196
197 // Step 4/4 : Finish up
198 if (m_scanMonitor)
199 m_scanMonitor->ScanAppendTextToLog(tr("Adding Channels"));
201
202 LOG(VB_CHANNEL, LOG_INFO, LOC + QString("Found %1 channels")
203 .arg(m_channels.size()));
204
205 if (!m_isMpts)
206 {
207 // Sort channels in ascending channel number order
208 std::vector<fbox_chan_map_t::const_iterator> acno (m_channels.size());
209 {
210 fbox_chan_map_t::const_iterator it = m_channels.cbegin();
211 for (uint i = 0; it != m_channels.cend(); ++it, ++i)
212 {
213 acno[i] = it;
214 }
215 std::ranges::sort(acno,
216 [] (fbox_chan_map_t::const_iterator s1, fbox_chan_map_t::const_iterator s2) -> bool
217 {
218 return s1.key().toInt() < s2.key().toInt();
219 }
220 );
221 }
222
223 // Insert channels in ascending channel number order
224 for (uint i = 0; i < acno.size(); ++i)
225 {
226 fbox_chan_map_t::const_iterator it = acno[i];
227 const QString& channum = it.key();
228 QString name = (*it).m_name;
229 QString xmltvid = (*it).m_xmltvid.isEmpty() ? "" : (*it).m_xmltvid;
230 QString logoUrl = (*it).m_logo;
231 QString logo;
232
233 // Download channel icon (logo) when there is an logo URL in the EXTINF
234 if (!logoUrl.isEmpty())
235 {
236 QString filename = QString("IPTV_%1_%2_logo").arg(m_sourceId).arg(channum);
237 if (download_logo(logoUrl, filename))
238 {
239 logo = filename;
240 }
241 }
242
243 uint programnumber = (*it).m_programNumber;
244 //: %1 is the channel number, %2 is the channel name
245 QString msg = tr("Channel #%1 : %2").arg(channum, name);
246
247 LOG(VB_CHANNEL, LOG_INFO, QString("Handling channel %1 %2")
248 .arg(channum, name));
249
250 int chanid = ChannelUtil::GetChanID(m_sourceId, channum);
251 if (chanid <= 0)
252 {
253 if (m_scanMonitor)
254 {
255 m_scanMonitor->ScanAppendTextToLog(tr("Adding %1").arg(msg));
256 }
257 chanid = ChannelUtil::CreateChanID(m_sourceId, channum);
258 ChannelUtil::CreateChannel(0, m_sourceId, chanid, name, name,
259 channum, programnumber, 0, 0,
260 false, kChannelVisible, QString(),
261 logo, "Default", xmltvid);
262 ChannelUtil::CreateIPTVTuningData(chanid, (*it).m_tuning);
263 }
264 else
265 {
266 if (m_scanMonitor)
267 {
269 tr("Updating %1").arg(msg));
270 }
271 ChannelUtil::UpdateChannel(0, m_sourceId, chanid, name, name,
272 channum, programnumber, 0, 0,
273 false, kChannelVisible, QString(),
274 logo, "Default", xmltvid);
275 ChannelUtil::UpdateIPTVTuningData(chanid, (*it).m_tuning);
276 }
277
279 }
280
281 if (m_scanMonitor)
282 {
287 }
288 }
289
290 QMutexLocker locker(&m_lock);
291 m_threadRunning = false;
292 m_stopNow = true;
293}
294
296{
297 uint minval = 35;
298 uint range = 70 - minval;
299 uint pct = minval + (uint) truncf((((float)val) / m_chanCnt) * range);
300 if (m_scanMonitor)
302}
303
305{
306 uint minval = 70;
307 uint range = 100 - minval;
308 uint pct = minval + (uint) truncf((((float)val) / m_chanCnt) * range);
309 if (m_scanMonitor)
311}
312
313void IPTVChannelFetcher::SetMessage(const QString &status)
314{
315 if (m_scanMonitor)
317}
318
319// This function is always called from a thread context.
320QString IPTVChannelFetcher::DownloadPlaylist(const QString &url)
321{
322 if (url.startsWith("file", Qt::CaseInsensitive))
323 {
324 QString ret;
325 QUrl qurl(url);
326 QFile file(qurl.toLocalFile());
327 if (!file.open(QIODevice::ReadOnly))
328 {
329 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Opening '%1'")
330 .arg(qurl.toLocalFile()) + ENO);
331 return ret;
332 }
333
334 while (!file.atEnd())
335 {
336 QByteArray data = file.readLine();
337 ret += QString(data);
338 }
339
340 file.close();
341 return ret;
342 }
343
344 // Use MythDownloadManager for http URLs
345 QByteArray data;
346 QString ret;
347
348 if (GetMythDownloadManager()->download(url, &data))
349 {
350 ret = QString(data);
351 }
352 else
353 {
354 LOG(VB_GENERAL, LOG_ERR, LOC +
355 QString("DownloadPlaylist failed to "
356 "download from %1").arg(url));
357 }
358 return ret;
359}
360
361static uint estimate_number_of_channels(const QString &rawdata)
362{
363 uint result = 0;
364 uint numLine = 1;
365 while (true)
366 {
367 QString url = rawdata.section("\n", numLine, numLine, QString::SectionSkipEmpty);
368 if (url.isEmpty())
369 return result;
370
371 ++numLine;
372 if (!url.startsWith("#")) // ignore comments
373 ++result;
374 }
375}
376
378 const QString &reallyrawdata, IPTVChannelFetcher *fetcher)
379{
380 fbox_chan_map_t chanmap;
381 int nextChanNum = 1;
382
383 QString rawdata = reallyrawdata;
384 rawdata.replace("\r\n", "\n");
385
386 // Verify header is ok
387 QString header = rawdata.section("\n", 0, 0);
388 if (!header.startsWith("#EXTM3U"))
389 {
390 LOG(VB_GENERAL, LOG_ERR, LOC +
391 QString("Invalid channel list header (%1)").arg(header));
392
393 if (fetcher)
394 {
395 fetcher->SetMessage(
396 tr("ERROR: M3U channel list is malformed"));
397 }
398
399 return chanmap;
400 }
401
402 // Estimate number of channels
403 if (fetcher)
404 {
405 uint num_channels = estimate_number_of_channels(rawdata);
406 fetcher->SetTotalNumChannels(num_channels);
407
408 LOG(VB_CHANNEL, LOG_INFO, LOC +
409 QString("Estimating there are %1 channels in playlist")
410 .arg(num_channels));
411 }
412
413 // Get the next available channel number for the source (only used if we can't find one in the playlist)
414 if (fetcher)
415 {
417 QString sql = "SELECT MAX(CONVERT(channum, UNSIGNED INTEGER)) FROM channel "
418 "WHERE sourceid = :SOURCEID AND deleted IS NULL";
419
420 query.prepare(sql);
421 query.bindValue(":SOURCEID", fetcher->m_sourceId);
422
423 if (!query.exec())
424 {
425 MythDB::DBError("Get next max channel number", query);
426 }
427 else
428 {
429 if (query.first())
430 {
431 nextChanNum = query.value(0).toInt() + 1;
432 LOG(VB_CHANNEL, LOG_INFO, LOC +
433 QString("Next available channel number from DB is: %1").arg(nextChanNum));
434 }
435 else
436 {
437 nextChanNum = 1;
438 LOG(VB_CHANNEL, LOG_INFO, LOC +
439 QString("No channels found for this source, using default channel number: %1").arg(nextChanNum));
440 }
441 }
442 }
443
444 // Parse each channel
445 uint lineNum = 1;
446 for (uint i = 1; true; ++i)
447 {
449 QString channum;
450
451 if (!parse_chan_info(rawdata, info, channum, lineNum))
452 break;
453
454 // Check if parsed channel number is valid
455 if (!channum.isEmpty())
456 {
457 bool ok = false;
458 int channel_number = channum.toInt(&ok);
459 if (ok && channel_number > 0)
460 {
461 // Positive integer channel number found
462 }
463 else
464 {
465 // Ignore invalid or negative channel numbers
466 channum.clear();
467 }
468 }
469
470 // No channel number found, try to find channel in database and use that channel number
471 if (fetcher)
472 {
473 if (channum.isEmpty())
474 {
475 uint sourceId = fetcher->m_sourceId;
476 QString channum_db = ChannelUtil::GetChannelNumber(sourceId, info.m_name);
477 if (!channum_db.isEmpty())
478 {
479 LOG(VB_RECORD, LOG_INFO, LOC +
480 QString("Found channel in database, channel number: %1 for channel: %2")
481 .arg(channum_db, info.m_name));
482
483 channum = channum_db;
484 }
485 }
486 }
487
488 // Update highest channel number found so far
489 if (!channum.isEmpty())
490 {
491 bool ok = false;
492 int channel_number = channum.toInt(&ok);
493 if (ok && channel_number > 0)
494 {
495 if (channel_number >= nextChanNum)
496 {
497 nextChanNum = channel_number + 1;
498 }
499 }
500 }
501
502 // No channel number found, use the default next one
503 if (channum.isEmpty())
504 {
505 LOG(VB_CHANNEL, LOG_INFO, QString("No channel number found, using next available: %1 for channel: %2")
506 .arg(nextChanNum).arg(info.m_name));
507 channum = QString::number(nextChanNum);
508 nextChanNum++;
509 }
510
511 QString msg = tr("Encountered malformed channel");
512 if (!channum.isEmpty())
513 {
514 chanmap[channum] = info;
515
516 msg = QString("Parsing Channel #%1: '%2' %3")
517 .arg(channum, info.m_name,
518 info.m_tuning.GetDataURL().toString());
519 LOG(VB_CHANNEL, LOG_INFO, LOC + msg);
520
521 msg.clear(); // don't tell fetcher
522 }
523
524 if (fetcher)
525 {
526 if (!msg.isEmpty())
527 fetcher->SetMessage(msg);
528 fetcher->SetNumChannelsParsed(i);
529 }
530 }
531
532 return chanmap;
533}
534
535static bool parse_chan_info(const QString &rawdata,
537 QString &channum,
538 uint &lineNum)
539{
540 // #EXTINF:0,2 - France 2 <-- duration,channum - channame
541 // #EXTMYTHTV:xmltvid=C2.telepoche.com <-- optional line (myth specific)
542 // #EXTMYTHTV:bitrate=BITRATE <-- optional line (myth specific)
543 // #EXTMYTHTV:fectype=FECTYPE <-- optional line (myth specific)
544 // The FECTYPE can be rfc2733, rfc5109, or smpte2022
545 // #EXTMYTHTV:fecurl0=URL <-- optional line (myth specific)
546 // #EXTMYTHTV:fecurl1=URL <-- optional line (myth specific)
547 // #EXTMYTHTV:fecbitrate0=BITRATE <-- optional line (myth specific)
548 // #EXTMYTHTV:fecbitrate1=BITRATE <-- optional line (myth specific)
549 // #EXTVLCOPT:program=program_number <-- optional line (used by MythTV and VLC)
550 // #... <-- ignored comments
551 // rtsp://maiptv.iptv.fr/iptvtv/201 <-- url
552
553 QString name;
554 QString logo;
555 QMap<QString,QString> values;
556
557 while (true)
558 {
559 QString line = rawdata.section("\n", lineNum, lineNum, QString::SectionSkipEmpty);
560 if (line.isEmpty())
561 return false;
562
563 LOG(VB_CHANNEL, LOG_INFO, LOC + line);
564
565 ++lineNum;
566 if (line.startsWith("#"))
567 {
568 if (line.startsWith("#EXTINF:"))
569 {
570 parse_extinf(line.mid(line.indexOf(':')+1), channum, name, logo);
571 }
572 else if (line.startsWith("#EXTMYTHTV:"))
573 {
574 QString data = line.mid(line.indexOf(':')+1);
575 QString key = data.left(data.indexOf('='));
576 if (!key.isEmpty())
577 {
578 if (key == "name")
579 name = data.mid(data.indexOf('=')+1);
580 else if (key == "channum")
581 channum = data.mid(data.indexOf('=')+1);
582 else
583 values[key] = data.mid(data.indexOf('=')+1);
584 }
585 }
586 else if (line.startsWith("#EXTVLCOPT:program="))
587 {
588 values["programnumber"] = line.mid(line.indexOf('=')+1);
589 }
590 continue;
591 }
592
593 if (name.isEmpty())
594 return false;
595
596 LOG(VB_CHANNEL, LOG_DEBUG, LOC +
597 QString("parse_chan_info name='%2'").arg(name));
598 LOG(VB_CHANNEL, LOG_DEBUG, LOC +
599 QString("parse_chan_info channum='%2'").arg(channum));
600 LOG(VB_CHANNEL, LOG_DEBUG, LOC +
601 QString("parse_chan_info logo='%2'").arg(logo));
602 for (auto it = values.cbegin(); it != values.cend(); ++it)
603 {
604 LOG(VB_CHANNEL, LOG_DEBUG, LOC +
605 QString("parse_chan_info [%1]='%2'")
606 .arg(it.key(), *it));
607 }
609 name, values["xmltvid"], logo,
610 line, values["bitrate"].toUInt(),
611 values["fectype"],
612 values["fecurl0"], values["fecbitrate0"].toUInt(),
613 values["fecurl1"], values["fecbitrate1"].toUInt(),
614 values["programnumber"].toUInt());
615 return true;
616 }
617}
618
619// Search for channel name in last part of the EXTINF line
620static QString parse_extinf_name_trailing(const QString& line)
621{
622 QString result;
623 static const QRegularExpression re_name { R"(.*,(.*)$)" };
624 auto match = re_name.match(line);
625 if (match.hasMatch())
626 {
627 result = match.captured(1).simplified();
628 }
629 return result;
630}
631
632// Search for field value, e.g. field="value", in EXTINF line
633static QString parse_extinf_field(QString line, const QString& field)
634{
635 QString result;
636 auto pos = line.indexOf(field, 0, Qt::CaseInsensitive);
637 if (pos > 0)
638 {
639 line.remove(0, pos);
640 static const QRegularExpression re { R"(\"([^\"]*)\"(.*)$)" };
641 auto match = re.match(line);
642 if (match.hasMatch())
643 {
644 result = match.captured(1).simplified();
645 }
646 }
647
648 LOG(VB_CHANNEL, LOG_INFO, LOC + QString("line:%1 field:%2 result:%3")
649 .arg(line, field, result));
650
651 return result;
652}
653
654// Search for channel number, channel name and channel logo in EXTINF line
655static bool parse_extinf(const QString &line,
656 QString &channum,
657 QString &name,
658 QString &logo)
659{
660
661 // Parse EXTINF line with TVG fields, Zatto style
662 // EG. #EXTINF:0001 tvg-id="ITV1London.uk" tvg-chno="90001" group-title="General Interest" tvg-logo="https://images.zattic.com/logos/ee3c9d2ac083eb2154b5/black/210x120.png", ITV 1 HD
663
664 // Parse EXTINF line, HDHomeRun style
665 // EG. #EXTINF:-1 channel-id="22" channel-number="22" tvg-name="Omroep Brabant",22 Omroep Brabant
666 // #EXTINF:-1 channel-id="2.1" channel-number="2.1" tvg-name="CBS2-HD",2.1 CBS2-HD
667
668 // Parse EXTINF line, https://github.com/iptv-org/iptv/blob/master/channels/ style
669 // EG. #EXTINF:-1 tvg-id="" tvg-name="" tvg-logo="https://i.imgur.com/VejnhiB.png" group-title="News",BBC News
670 {
671 channum = parse_extinf_field(line, "tvg-chno");
672 if (channum.isEmpty())
673 {
674 channum = parse_extinf_field(line, "channel-number");
675 }
676 logo = parse_extinf_field(line, "tvg-logo");
677 name = parse_extinf_field(line, "tvg-name");
678 if (name.isEmpty())
679 {
680 name = parse_extinf_name_trailing(line);
681 }
682
683 // If we only have the name then it might be another format
684 if (!name.isEmpty() && !channum.isEmpty())
685 {
686 return true;
687 }
688 }
689
690 // Freebox or SAT>IP format
691 {
692 static const QRegularExpression re
693 { R"(^-?\d+,(\d+)(?:\.\s|\s-\s)(.*)$)" };
694 auto match = re.match(line);
695 if (match.hasMatch())
696 {
697 channum = match.captured(1);
698 name = match.captured(2);
699 if (!channum.isEmpty() && !name.isEmpty())
700 {
701 return true;
702 }
703 }
704 }
705
706 // Moviestar TV number then name
707 {
708 static const QRegularExpression re
709 { R"(^-?\d+,\[(\d+)\]\s+(.*)$)" };
710 auto match = re.match(line);
711 if (match.hasMatch())
712 {
713 channum = match.captured(1);
714 name = match.captured(2);
715 if (!channum.isEmpty() && !name.isEmpty())
716 {
717 return true;
718 }
719 }
720 }
721
722 // Moviestar TV name then number
723 {
724 static const QRegularExpression re
725 { R"(^-?\d+,(.*)\s+\[(\d+)\]$)" };
726 auto match = re.match(line);
727 if (match.hasMatch())
728 {
729 channum = match.captured(2);
730 name = match.captured(1);
731 if (!channum.isEmpty() && !name.isEmpty())
732 {
733 return true;
734 }
735 }
736 }
737
738 // Parse russion iptv plugin style
739 {
740 static const QRegularExpression re
741 { R"(^(-?\d+)\s+[^,]*,\s*(.*)$)" };
742 auto match = re.match(line);
743 if (match.hasMatch())
744 {
745 channum = match.captured(1).simplified();
746 name = match.captured(2).simplified();
747 bool ok = false;
748 int channel_number = channum.toInt (&ok);
749 if (ok && (channel_number > 0) && !name.isEmpty())
750 {
751 return true;
752 }
753 }
754 }
755
756 // Almost a catchall: just get the name from the end of the line
757 // EG. #EXTINF:-1,Channel Title
758 name = parse_extinf_name_trailing(line);
759 if (!name.isEmpty())
760 {
761 return true;
762 }
763
764 // Not one of the formats we support
765 QString msg = LOC + QString("Invalid header in channel list line \n\t\t\tEXTINF:%1").arg(line);
766 LOG(VB_GENERAL, LOG_ERR, msg);
767 return false;
768}
769
770/* vim: set expandtab tabstop=4 shiftwidth=4: */
@ kChannelVisible
Definition: channelinfo.h:22
static QString GetVideoDevice(uint inputid)
Definition: cardutil.h:296
static int GetChanID(int db_mplexid, int service_transport_id, int major_channel, int minor_channel, int program_number)
static QString GetChannelNumber(uint sourceid, const QString &channel_name)
static bool CreateIPTVTuningData(uint channel_id, const IPTVTuningData &tuning)
Definition: channelutil.h:155
static bool UpdateIPTVTuningData(uint channel_id, const IPTVTuningData &tuning)
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 int CreateChanID(uint sourceid, const QString &chan_num)
Creates a unique channel ID for database use.
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)
void SetMessage(const QString &status)
static QString DownloadPlaylist(const QString &url)
void SetNumChannelsInserted(uint val)
void Stop(void)
Stops the scanning thread running.
fbox_chan_map_t m_channels
void run(void) override
fbox_chan_map_t GetChannels(void)
void SetTotalNumChannels(uint val)
static MTV_PUBLIC fbox_chan_map_t ParsePlaylist(const QString &rawdata, IPTVChannelFetcher *fetcher=nullptr)
ScanMonitor * m_scanMonitor
IPTVChannelFetcher(uint cardid, QString inputname, uint sourceid, bool is_mpts, ScanMonitor *monitor=nullptr)
void SetNumChannelsParsed(uint val)
void Scan(void)
Scans the given frequency list.
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:128
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:838
bool first(void)
Wrap QSqlQuery::first() so we can display the query results.
Definition: mythdbcon.cpp:823
QVariant value(int i) const
Definition: mythdbcon.h:204
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:619
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:889
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:551
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:49
bool isFinished(void) const
Definition: mthread.cpp:242
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:267
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:284
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:225
void ScanPercentComplete(int pct)
void ScanComplete(void)
Definition: scanmonitor.cpp:98
void ScanAppendTextToLog(const QString &status)
void ScanErrored(const QString &error)
unsigned int uint
Definition: compat.h:60
#define LOC
static uint estimate_number_of_channels(const QString &rawdata)
static bool parse_chan_info(const QString &rawdata, IPTVChannelInfo &info, QString &channum, uint &lineNum)
static bool parse_extinf(const QString &line, QString &channum, QString &name, QString &logo)
static QString parse_extinf_name_trailing(const QString &line)
static QString parse_extinf_field(QString line, const QString &field)
static bool download_logo(const QString &logoUrl, const QString &filename)
QMap< QString, IPTVChannelInfo > fbox_chan_map_t
QString GetConfDir(void)
Definition: mythdirs.cpp:285
MythDownloadManager * GetMythDownloadManager(void)
Gets the pointer to the MythDownloadManager singleton.
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:74
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
dictionary info
Definition: azlyrics.py:7