Skip to content

Commit

Permalink
IPTV: add support for playlists from https://github.com/iptv-org/iptv/
Browse files Browse the repository at this point in the history
* the header only has to start with ##EXTM3U
* use the next available channel number if one doesn't exist in the playlist
* add extra tests for these playlists
  • Loading branch information
Paul Harrison committed Oct 12, 2019
1 parent 98b79f6 commit a474d52
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 9 deletions.
76 changes: 67 additions & 9 deletions mythtv/libs/libmythtv/channelscan/iptvchannelfetcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@
static bool parse_chan_info(const QString &rawdata,
IPTVChannelInfo &info,
QString &channum,
int &nextChanNum,
uint &lineNum);

static bool parse_extinf(const QString &line,
QString &channum,
QString &name);
QString &name,
int &nextChanNum);

IPTVChannelFetcher::IPTVChannelFetcher(
uint cardid, const QString &inputname, uint sourceid,
Expand Down Expand Up @@ -289,13 +291,14 @@ fbox_chan_map_t IPTVChannelFetcher::ParsePlaylist(
const QString &reallyrawdata, IPTVChannelFetcher *fetcher)
{
fbox_chan_map_t chanmap;
int nextChanNum = 1;

QString rawdata = reallyrawdata;
rawdata.replace("\r\n", "\n");

// Verify header is ok
QString header = rawdata.section("\n", 0, 0);
if (header != "#EXTM3U")
if (!header.startsWith("#EXTM3U"))
{
LOG(VB_GENERAL, LOG_ERR, LOC +
QString("Invalid channel list header (%1)").arg(header));
Expand All @@ -320,14 +323,42 @@ fbox_chan_map_t IPTVChannelFetcher::ParsePlaylist(
.arg(num_channels));
}

// get the next available channel number for the source (only used if we can't find one in the playlist)
if (fetcher)
{
MSqlQuery query(MSqlQuery::InitCon());
QString sql = "select MAX(CONVERT(channum, UNSIGNED INTEGER)) from channel where sourceid = :SOURCEID;";

query.prepare(sql);
query.bindValue(":SOURCEID", fetcher->m_sourceid);

if (!query.exec())
{
MythDB::DBError("Get next max channel number", query);
}
else
{
if (query.first())
{
nextChanNum = query.value(0).toInt() + 1;
LOG(VB_GENERAL, LOG_INFO, LOC + QString("Next available channel number from DB is: %1").arg(nextChanNum));
}
else
{
nextChanNum = 1;
LOG(VB_GENERAL, LOG_INFO, LOC + QString("No channels found for this source, using default channel number: %1").arg(nextChanNum));
}
}
}

// Parse each channel
uint lineNum = 1;
for (uint i = 1; true; ++i)
{
IPTVChannelInfo info;
QString channum;

if (!parse_chan_info(rawdata, info, channum, lineNum))
if (!parse_chan_info(rawdata, info, channum, nextChanNum, lineNum))
break;

QString msg = tr("Encountered malformed channel");
Expand Down Expand Up @@ -357,6 +388,7 @@ fbox_chan_map_t IPTVChannelFetcher::ParsePlaylist(
static bool parse_chan_info(const QString &rawdata,
IPTVChannelInfo &info,
QString &channum,
int &nextChanNum,
uint &lineNum)
{
// #EXTINF:0,2 - France 2 <-- duration,channum - channame
Expand Down Expand Up @@ -386,7 +418,7 @@ static bool parse_chan_info(const QString &rawdata,
{
if (line.startsWith("#EXTINF:"))
{
parse_extinf(line.mid(line.indexOf(':')+1), channum, name);
parse_extinf(line.mid(line.indexOf(':')+1), channum, name, nextChanNum);
}
else if (line.startsWith("#EXTMYTHTV:"))
{
Expand Down Expand Up @@ -425,7 +457,8 @@ static bool parse_chan_info(const QString &rawdata,

static bool parse_extinf(const QString &line,
QString &channum,
QString &name)
QString &name,
int &nextChanNum)
{
// Parse extension portion, Freebox or SAT>IP format
QRegExp chanNumName1("^-?\\d+,(\\d+)(?:\\.\\s|\\s-\\s)(.*)$");
Expand Down Expand Up @@ -482,10 +515,35 @@ static bool parse_extinf(const QString &line,
}
}

// line is supposed to contain the "0,2 - France 2" part
QString msg = LOC +
QString("Invalid header in channel list line \n\t\t\tEXTINF:%1")
.arg(line);
// Parse extension portion, https://github.com/iptv-org/iptv/blob/master/channels/ style
// EG. #EXTINF:-1 tvg-id="" tvg-name="" tvg-logo="https://i.imgur.com/VejnhiB.png" group-title="News",BBC News
QRegExp chanNumName6("(^-?\\d+)\\s+[^,]*[^,]*,(.*)$");
pos = chanNumName6.indexIn(line);
if (pos != -1)
{
channum = chanNumName6.cap(1).simplified();
name = chanNumName6.cap(2).simplified();

bool ok;
int channel_number = channum.toInt(&ok);
if (ok && channel_number > 0)
{
if (channel_number >= nextChanNum)
nextChanNum = channel_number + 1;
return true;
}
else
{
// no valid channel number found use the default next one
LOG(VB_GENERAL, LOG_ERR, QString("No channel number found, using next available: %1 for channel: %2").arg(nextChanNum).arg(name));
channum = QString::number(nextChanNum);
nextChanNum++;
return true;
}
}

// not one of the formats we support
QString msg = LOC + QString("Invalid header in channel list line \n\t\t\tEXTINF:%1").arg(line);
LOG(VB_GENERAL, LOG_ERR, msg);
return false;
}
Expand Down
28 changes: 28 additions & 0 deletions mythtv/libs/libmythtv/test/test_iptvrecorder/test_iptvrecorder.h
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,20 @@ class TestIPTVRecorder: public QObject
"#EXTINF:0002 tvg-id=\"daserste.de\" group-title=\"DE Hauptsender\" tvg-logo=\"609281.png\", [COLOR gold]Das Erste[/COLOR]\n"
"http://api.iptv.ink/pl.m3u8?ch=71565725\n");

/*
* https://github.com/iptv-org/iptv/blob/master/channels/ playlist no channel number
*/
QString rawdataIPTVOrg ("#EXTM3U x-tvg-url=\"http://195.154.221.171/epg/guideuk.xml.gz\"\n"
"#EXTINF:-1 tvg-id=\"\" tvg-name=\"\" tvg-logo=\"https://i.imgur.com/kDB44LV.jpg\" group-title=\"Music\",60 North\n"
"https://eu-de-edge-01.zetcast.net/ShetlandWebcams/Studio1ABR/playlist.m3u8\n");

/*
* https://github.com/iptv-org/iptv/blob/master/channels/ playlist with channel number in duration
*/
QString rawdataIPTVOrg2 ("#EXTM3U x-tvg-url=\"http://195.154.221.171/epg/guideuk.xml.gz\"\n"
"#EXTINF:123 tvg-id=\"\" tvg-name=\"\" tvg-logo=\"https://i.imgur.com/VejnhiB.png\" group-title=\"News\",BBC News\n"
"https://streamingserver001.viewtvgroup.com/kapanglivetv-uksat-bbcnews/tracks-v1a1/mono.m3u8\n");

fbox_chan_map_t chanmap;

/* test plain old MPEG-2 TS over multicast playlist */
Expand Down Expand Up @@ -217,6 +231,20 @@ class TestIPTVRecorder: public QObject
QVERIFY (!chanmap["0002"].m_tuning.IsValid ());
QCOMPARE (chanmap["0002"].m_tuning.GetProtocol(), IPTVTuningData::inValid);
QCOMPARE (chanmap["0002"].m_name, QString ("[COLOR gold]Das Erste[/COLOR]"));

/* test playlist from iptv-org/iptv with no channel number (will default to 1*/
chanmap = IPTVChannelFetcher::ParsePlaylist (rawdataIPTVOrg, nullptr);
QVERIFY (!chanmap["1"].IsValid ());
QVERIFY (!chanmap["1"].m_tuning.IsValid ());
QCOMPARE (chanmap["1"].m_tuning.GetProtocol(), IPTVTuningData::inValid);
QCOMPARE (chanmap["1"].m_name, QString ("60 North"));

/* test playlist from iptv-org/iptv with channel number */
chanmap = IPTVChannelFetcher::ParsePlaylist (rawdataIPTVOrg2, nullptr);
QVERIFY (!chanmap["123"].IsValid ());
QVERIFY (!chanmap["123"].m_tuning.IsValid ());
QCOMPARE (chanmap["123"].m_tuning.GetProtocol(), IPTVTuningData::inValid);
QCOMPARE (chanmap["123"].m_name, QString ("BBC News"));
}

/**
Expand Down

0 comments on commit a474d52

Please sign in to comment.