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