10 #include <QRegularExpression>
11 #include <QTextStream>
24 #define LOC QString("IPTVChanFetch: ")
37 uint cardid, QString inputname,
uint sourceid,
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))
44 LOG(VB_CHANNEL, LOG_INFO,
LOC + QString(
"Has ScanMonitor %1")
45 .arg(monitor?
"true":
"false"));
80 LOG(VB_CHANNEL, LOG_INFO,
LOC + QString(
"Found %1 channels")
98 if (!logoUrl.isEmpty())
103 QString iconDir =
GetConfDir() +
"/channels/";
104 QString filepath = iconDir +
filename;
105 QFile
file(filepath);
106 if (
file.open(QIODevice::WriteOnly))
108 if (
file.write(data) > 0)
111 LOG(VB_CHANNEL, LOG_DEBUG,
LOC +
112 QString(
"DownloadLogo to file %1").arg(filepath));
116 LOG(VB_CHANNEL, LOG_ERR,
LOC +
117 QString(
"DownloadLogo failed to write to file %1").arg(filepath));
123 LOG(VB_CHANNEL, LOG_ERR,
LOC +
124 QString(
"DownloadLogo failed to open file %1").arg(filepath));
129 LOG(VB_CHANNEL, LOG_ERR,
LOC +
130 QString(
"DownloadLogo failed to download %1").arg(logoUrl));
135 LOG(VB_CHANNEL, LOG_DEBUG,
LOC +
"DownloadLogo empty logoUrl");
151 LOG(VB_CHANNEL, LOG_INFO,
LOC +
"Playlist URL is empty");
152 QMutexLocker locker(&
m_lock);
158 LOG(VB_CHANNEL, LOG_INFO,
LOC + QString(
"Playlist URL: %1").arg(url));
174 QCoreApplication::translate(
"(Common)",
"Error"));
178 QMutexLocker locker(&
m_lock);
184 LOG(VB_CHANNEL, LOG_DEBUG,
LOC + QString(
"Playlist file size: %1").arg(playlist.length()));
201 LOG(VB_CHANNEL, LOG_INFO,
LOC + QString(
"Found %1 channels")
207 std::vector<fbox_chan_map_t::const_iterator> acno (
m_channels.size());
209 fbox_chan_map_t::const_iterator it =
m_channels.cbegin();
214 std::sort(acno.begin(), acno.end(),
215 [] (fbox_chan_map_t::const_iterator s1, fbox_chan_map_t::const_iterator s2) ->
bool
217 return s1.key().toInt() < s2.key().toInt();
223 for (
uint i = 0; i < acno.size(); ++i)
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;
233 if (!logoUrl.isEmpty())
242 uint programnumber = (*it).m_programNumber;
244 QString msg = tr(
"Channel #%1 : %2").arg(channum, name);
246 LOG(VB_CHANNEL, LOG_INFO, QString(
"Handling channel %1 %2")
247 .arg(channum, name));
258 channum, programnumber, 0, 0,
260 logo,
"Default", xmltvid);
268 tr(
"Updating %1").arg(msg));
271 channum, programnumber, 0, 0,
273 logo,
"Default", xmltvid);
289 QMutexLocker locker(&
m_lock);
297 uint range = 70 - minval;
306 uint range = 100 - minval;
321 if (url.startsWith(
"file", Qt::CaseInsensitive))
325 QFile
file(qurl.toLocalFile());
326 if (!
file.open(QIODevice::ReadOnly))
328 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Opening '%1'")
329 .arg(qurl.toLocalFile()) +
ENO);
333 while (!
file.atEnd())
335 QByteArray data =
file.readLine();
336 ret += QString(data);
353 LOG(VB_GENERAL, LOG_ERR,
LOC +
354 QString(
"DownloadPlaylist failed to "
355 "download from %1").arg(url));
366 QString url = rawdata.section(
"\n", numLine, numLine, QString::SectionSkipEmpty);
371 if (!url.startsWith(
"#"))
382 QString rawdata = reallyrawdata;
383 rawdata.replace(
"\r\n",
"\n");
386 QString header = rawdata.section(
"\n", 0, 0);
387 if (!header.startsWith(
"#EXTM3U"))
389 LOG(VB_GENERAL, LOG_ERR,
LOC +
390 QString(
"Invalid channel list header (%1)").arg(header));
395 tr(
"ERROR: M3U channel list is malformed"));
407 LOG(VB_CHANNEL, LOG_INFO,
LOC +
408 QString(
"Estimating there are %1 channels in playlist")
416 QString sql =
"SELECT MAX(CONVERT(channum, UNSIGNED INTEGER)) FROM channel "
417 "WHERE sourceid = :SOURCEID AND deleted IS NULL";
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));
437 LOG(VB_CHANNEL, LOG_INFO,
LOC +
438 QString(
"No channels found for this source, using default channel number: %1").arg(nextChanNum));
445 for (
uint i = 1;
true; ++i)
454 if (!channum.isEmpty())
457 int channel_number = channum.toInt(&ok);
458 if (ok && channel_number > 0)
472 if (channum.isEmpty())
476 if (!channum_db.isEmpty())
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));
482 channum = channum_db;
488 if (!channum.isEmpty())
491 int channel_number = channum.toInt(&ok);
492 if (ok && channel_number > 0)
494 if (channel_number >= nextChanNum)
496 nextChanNum = channel_number + 1;
502 if (channum.isEmpty())
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);
510 QString msg = tr(
"Encountered malformed channel");
511 if (!channum.isEmpty())
513 chanmap[channum] =
info;
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);
554 QMap<QString,QString> values;
558 QString line = rawdata.section(
"\n", lineNum, lineNum, QString::SectionSkipEmpty);
562 LOG(VB_CHANNEL, LOG_INFO,
LOC + line);
565 if (line.startsWith(
"#"))
567 if (line.startsWith(
"#EXTINF:"))
569 parse_extinf(line.mid(line.indexOf(
':')+1), channum, name, logo);
571 else if (line.startsWith(
"#EXTMYTHTV:"))
573 QString data = line.mid(line.indexOf(
':')+1);
574 QString key = data.left(data.indexOf(
'='));
578 name = data.mid(data.indexOf(
'=')+1);
579 else if (key ==
"channum")
580 channum = data.mid(data.indexOf(
'=')+1);
582 values[key] = data.mid(data.indexOf(
'=')+1);
585 else if (line.startsWith(
"#EXTVLCOPT:program="))
587 values[
"programnumber"] = line.mid(line.indexOf(
'=')+1);
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)
603 LOG(VB_CHANNEL, LOG_DEBUG,
LOC +
604 QString(
"parse_chan_info [%1]='%2'")
605 .arg(it.key(), *it));
608 name, values[
"xmltvid"], logo,
609 line, values[
"bitrate"].toUInt(),
611 values[
"fecurl0"], values[
"fecbitrate0"].toUInt(),
612 values[
"fecurl1"], values[
"fecbitrate1"].toUInt(),
613 values[
"programnumber"].toUInt());
622 static const QRegularExpression re_name { R
"([^\,+],(.*)$)" };
623 auto match = re_name.match(line);
624 if (match.hasMatch())
626 result = match.captured(1).simplified();
635 auto pos = line.indexOf(field, 0, Qt::CaseInsensitive);
639 static const QRegularExpression re { R
"(\"([^\"]*)\"(.*)$)" };
640 auto match = re.match(line);
641 if (match.hasMatch())
643 result = match.captured(1).simplified();
647 LOG(VB_CHANNEL, LOG_INFO,
LOC + QString(
"line:%1 field:%2 result:%3")
648 .arg(line, field, result));
671 if (channum.isEmpty())
683 if (!name.isEmpty() && !channum.isEmpty())
691 static const QRegularExpression re
692 { R
"(^-?\d+,(\d+)(?:\.\s|\s-\s)(.*)$)" };
693 auto match = re.match(line);
694 if (match.hasMatch())
696 channum = match.captured(1);
697 name = match.captured(2);
698 if (!channum.isEmpty() && !name.isEmpty())
707 static const QRegularExpression re
708 { R
"(^-?\d+,\[(\d+)\]\s+(.*)$)" };
709 auto match = re.match(line);
710 if (match.hasMatch())
712 channum = match.captured(1);
713 name = match.captured(2);
714 if (!channum.isEmpty() && !name.isEmpty())
723 static const QRegularExpression re
724 { R
"(^-?\d+,(.*)\s+\[(\d+)\]$)" };
725 auto match = re.match(line);
726 if (match.hasMatch())
728 channum = match.captured(2);
729 name = match.captured(1);
730 if (!channum.isEmpty() && !name.isEmpty())
739 static const QRegularExpression re
740 { R
"(^(-?\d+)\s+[^,]*,\s*(.*)$)" };
741 auto match = re.match(line);
742 if (match.hasMatch())
744 channum = match.captured(1).simplified();
745 name = match.captured(2).simplified();
747 int channel_number = channum.toInt (&ok);
748 if (ok && (channel_number > 0) && !name.isEmpty())
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);