Skip to content

Commit 7c5d506

Browse files
committed
Add web stream extraction support for IPTV Simple addon
1 parent 7486838 commit 7c5d506

14 files changed

+861
-4
lines changed

CMakeLists.txt

+10-2
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,11 @@ set(IPTV_SOURCES src/addon.cpp
4545
src/iptvsimple/utilities/Logger.cpp
4646
src/iptvsimple/utilities/SettingsMigration.cpp
4747
src/iptvsimple/utilities/StreamUtils.cpp
48-
src/iptvsimple/utilities/WebUtils.cpp)
48+
src/iptvsimple/utilities/WebUtils.cpp
49+
src/iptvsimple/utilities/WebStreamExtractor.cpp
50+
src/iptvsimple/utilities/CurlUtils.cpp
51+
src/iptvsimple/utilities/Base64.cpp
52+
)
4953

5054
set(IPTV_HEADERS src/addon.h
5155
src/IptvSimple.h
@@ -76,7 +80,11 @@ set(IPTV_HEADERS src/addon.h
7680
src/iptvsimple/utilities/StreamUtils.h
7781
src/iptvsimple/utilities/TimeUtils.h
7882
src/iptvsimple/utilities/WebUtils.h
79-
src/iptvsimple/utilities/XMLUtils.h)
83+
src/iptvsimple/utilities/XMLUtils.h
84+
src/iptvsimple/utilities/WebStreamExtractor.h
85+
src/iptvsimple/utilities/CurlUtils.h
86+
src/iptvsimple/utilities/Base64.h
87+
)
8088

8189
addon_version(pvr.iptvsimple IPTV)
8290
add_definitions(-DIPTV_VERSION=${IPTV_VERSION})

src/IptvSimple.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ PVR_ERROR IptvSimple::GetChannelStreamProperties(const kodi::addon::PVRChannel&
239239
else
240240
streamURL = m_catchupController.ProcessStreamUrl(m_currentChannel);
241241

242+
streamURL = StreamUtils::WebStreamExtractor(streamURL, m_currentChannel);
242243
StreamUtils::SetAllStreamProperties(properties, m_currentChannel, streamURL, catchupUrl.empty(), catchupProperties, m_settings);
243244

244245
Logger::Log(LogLevel::LEVEL_INFO, "%s - Live %s URL: %s", __FUNCTION__, catchupUrl.empty() ? "Stream" : "Catchup", WebUtils::RedactUrl(streamURL).c_str());
@@ -417,6 +418,7 @@ PVR_ERROR IptvSimple::GetRecordingStreamProperties(const kodi::addon::PVRRecordi
417418

418419
if (!mediaEntry.GetMediaEntryId().empty() && !url.empty())
419420
{
421+
url = StreamUtils::WebStreamExtractor(url, mediaEntry);
420422
StreamUtils::SetAllStreamProperties(properties, mediaEntry, url, m_settings);
421423

422424
return PVR_ERROR_NO_ERROR;

src/iptvsimple/PlaylistLoader.cpp

+16-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "InstanceSettings.h"
1111
#include "utilities/FileUtils.h"
1212
#include "utilities/Logger.h"
13+
#include "utilities/WebStreamExtractor.h"
1314
#include "utilities/WebUtils.h"
1415

1516
#include <chrono>
@@ -200,6 +201,10 @@ bool PlaylistLoader::LoadPlayList()
200201
{
201202
ParseSinglePropertyIntoChannel(line, tmpChannel, EXTVLCOPT_DASH_MARKER);
202203
}
204+
else if (StringUtils::StartsWith(line, WEBPROP_MARKER)) //#WEBPROP:
205+
{
206+
ParseSinglePropertyIntoChannel(line, tmpChannel, WEBPROP_MARKER);
207+
}
203208
else if (StringUtils::StartsWith(line, M3U_GROUP_MARKER)) //#EXTGRP:
204209
{
205210
//Clear any previous Group Ids
@@ -221,6 +226,11 @@ bool PlaylistLoader::LoadPlayList()
221226
}
222227
else if (line[0] != '#')
223228
{
229+
if (line[0] == '@')
230+
{
231+
line = line.substr(1);
232+
tmpChannel.AddProperty("isWebUrl", "true");
233+
}
224234
Logger::Log(LEVEL_DEBUG, "%s - Adding channel or Media Entry '%s' with URL: '%s'", __FUNCTION__, tmpChannel.GetChannelName().c_str(), line.c_str());
225235

226236
if (m_settings->IsMediaEnabled() &&
@@ -552,7 +562,8 @@ void PlaylistLoader::ParseAndAddChannelGroups(const std::string& groupNamesListS
552562

553563
void PlaylistLoader::ParseSinglePropertyIntoChannel(const std::string& line, Channel& channel, const std::string& markerName)
554564
{
555-
const std::string value = ReadMarkerValue(line, markerName, markerName != KODIPROP_MARKER);
565+
const std::string value = ReadMarkerValue(
566+
line, markerName, (markerName != KODIPROP_MARKER && markerName != WEBPROP_MARKER));
556567
auto pos = value.find('=');
557568
if (pos != std::string::npos)
558569
{
@@ -569,6 +580,10 @@ void PlaylistLoader::ParseSinglePropertyIntoChannel(const std::string& line, Cha
569580
{
570581
addProperty &= prop == "http-user-agent" || prop == "http-referrer" || prop == "program";
571582
}
583+
else if (markerName == WEBPROP_MARKER)
584+
{
585+
addProperty &= prop == "web-regex" || prop == "web-headers";
586+
}
572587
else if (markerName == KODIPROP_MARKER && (prop == "inputstreamaddon" || prop == "inputstreamclass"))
573588
{
574589
prop = PVR_STREAM_PROPERTY_INPUTSTREAM;

src/iptvsimple/PlaylistLoader.h

+1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ namespace iptvsimple
5454
static const std::string EXTVLCOPT_DASH_MARKER = "#EXTVLCOPT--";
5555
static const std::string RADIO_MARKER = "radio=";
5656
static const std::string PLAYLIST_TYPE_MARKER = "#EXT-X-PLAYLIST-TYPE:";
57+
static const std::string WEBPROP_MARKER = "#WEBPROP:";
5758

5859
class PlaylistLoader
5960
{

src/iptvsimple/utilities/Base64.cpp

+249
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
#include "Base64.h"
2+
3+
using namespace iptvsimple;
4+
using namespace utilities;
5+
namespace
6+
{
7+
constexpr char PADDING{'='};
8+
constexpr std::string_view CHARACTERS{"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
9+
"abcdefghijklmnopqrstuvwxyz"
10+
"0123456789+/"};
11+
// clang-format off
12+
constexpr unsigned char BASE64_TABLE[] = {
13+
255,255,255,255, 255,255,255,255, 255,255,255,255, 255,255,255,255,
14+
255,255,255,255, 255,255,255,255, 255,255,255,255, 255,255,255,255,
15+
255,255,255,255, 255,255,255,255, 255,255,255, 62, 255,255,255, 63,
16+
52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255, 255, 0,255,255, /* Note PAD->0 */
17+
255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
18+
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255, 255,255,255,255,
19+
255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
20+
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255, 255,255,255,255,
21+
22+
255,255,255,255, 255,255,255,255, 255,255,255,255, 255,255,255,255,
23+
255,255,255,255, 255,255,255,255, 255,255,255,255, 255,255,255,255,
24+
255,255,255,255, 255,255,255,255, 255,255,255,255, 255,255,255,255,
25+
255,255,255,255, 255,255,255,255, 255,255,255,255, 255,255,255,255,
26+
255,255,255,255, 255,255,255,255, 255,255,255,255, 255,255,255,255,
27+
255,255,255,255, 255,255,255,255, 255,255,255,255, 255,255,255,255,
28+
255,255,255,255, 255,255,255,255, 255,255,255,255, 255,255,255,255,
29+
255,255,255,255, 255,255,255,255, 255,255,255,255, 255,255,255,255,
30+
};
31+
// clang-format on
32+
} // namespace
33+
34+
void BASE64::Encode(const uint8_t* input,
35+
const size_t length,
36+
std::string& output,
37+
const bool padding /* = true */)
38+
{
39+
if (input == nullptr || length == 0)
40+
return;
41+
42+
long l;
43+
output.clear();
44+
output.reserve(((length + 2) / 3) * 4);
45+
46+
for (size_t i{0}; i < length; i += 3)
47+
{
48+
l = (((static_cast<unsigned long>(input[i])) << 16) & 0xFFFFFF) |
49+
((((i + 1) < length) ? ((static_cast<unsigned long>(input[i + 1])) << 8) : 0) & 0xFFFF) |
50+
((((i + 2) < length) ? ((static_cast<unsigned long>(input[i + 2])) << 0) : 0) & 0x00FF);
51+
52+
output.push_back(CHARACTERS[(l >> 18) & 0x3F]);
53+
output.push_back(CHARACTERS[(l >> 12) & 0x3F]);
54+
55+
if (i + 1 < length)
56+
output.push_back(CHARACTERS[(l >> 6) & 0x3F]);
57+
if (i + 2 < length)
58+
output.push_back(CHARACTERS[(l >> 0) & 0x3F]);
59+
}
60+
61+
if (padding)
62+
{
63+
const int left = 3 - (length % 3);
64+
65+
if (length % 3)
66+
{
67+
for (int i = 0; i < left; ++i)
68+
output.push_back(PADDING);
69+
}
70+
}
71+
}
72+
73+
std::string BASE64::Encode(const uint8_t* input,
74+
const size_t length,
75+
const bool padding /* = true */)
76+
{
77+
std::string output;
78+
Encode(input, length, output, padding);
79+
return output;
80+
}
81+
82+
std::string BASE64::Encode(const std::vector<uint8_t>& input, const bool padding /* = true */)
83+
{
84+
std::string output;
85+
Encode(input.data(), input.size(), output, padding);
86+
return output;
87+
}
88+
89+
std::string BASE64::Encode(const std::vector<char>& input, const bool padding /* = true */)
90+
{
91+
std::string output;
92+
Encode(reinterpret_cast<const uint8_t*>(input.data()), input.size(), output, padding);
93+
return output;
94+
}
95+
96+
std::string BASE64::Encode(const std::string& inputStr, const bool padding /* = true */)
97+
{
98+
std::string output;
99+
Encode(reinterpret_cast<const uint8_t*>(inputStr.data()), inputStr.size(), output, padding);
100+
return output;
101+
}
102+
103+
void BASE64::Decode(const char* input, const size_t length, std::vector<uint8_t>& output)
104+
{
105+
if (!input)
106+
return;
107+
108+
output.clear();
109+
output.reserve(length - ((length + 2) / 4));
110+
111+
bool paddingStarted{false};
112+
int quadPos{0};
113+
unsigned char leftChar{0};
114+
int pads{0};
115+
116+
for (size_t i{0}; i < length; i++)
117+
{
118+
// Check for pad sequences and ignore the invalid ones.
119+
if (input[i] == PADDING)
120+
{
121+
paddingStarted = true;
122+
123+
if (quadPos >= 2 && quadPos + ++pads >= 4)
124+
{
125+
// A pad sequence means we should not parse more input.
126+
// We've already interpreted the data from the quad at this point.
127+
return;
128+
}
129+
continue;
130+
}
131+
132+
unsigned char thisChar{BASE64_TABLE[static_cast<unsigned char>(input[i])]};
133+
// Skip not allowed characters
134+
if (thisChar >= 64)
135+
continue;
136+
137+
// Characters that are not '=', in the middle of the padding, are not allowed
138+
if (paddingStarted)
139+
{
140+
Logger::Log(LEVEL_ERROR, "Invalid base64-encoded string: Incorrect padding characters");
141+
output.clear();
142+
return;
143+
}
144+
pads = 0;
145+
146+
switch (quadPos)
147+
{
148+
case 0:
149+
quadPos = 1;
150+
leftChar = thisChar;
151+
break;
152+
case 1:
153+
quadPos = 2;
154+
output.push_back((leftChar << 2) | (thisChar >> 4));
155+
leftChar = thisChar & 0x0f;
156+
break;
157+
case 2:
158+
quadPos = 3;
159+
output.push_back((leftChar << 4) | (thisChar >> 2));
160+
leftChar = thisChar & 0x03;
161+
break;
162+
case 3:
163+
quadPos = 0;
164+
output.push_back((leftChar << 6) | (thisChar));
165+
leftChar = 0;
166+
break;
167+
}
168+
}
169+
170+
if (quadPos != 0)
171+
{
172+
if (quadPos == 1)
173+
{
174+
// There is exactly one extra valid, non-padding, base64 character.
175+
// This is an invalid length, as there is no possible input that
176+
// could encoded into such a base64 string.
177+
Logger::Log(LEVEL_ERROR,
178+
"Invalid base64-encoded string: number of data characters cannot be 1 "
179+
"more than a multiple of 4");
180+
}
181+
else
182+
{
183+
Logger::Log(LEVEL_ERROR, "Invalid base64-encoded string: Incorrect padding");
184+
}
185+
output.clear();
186+
}
187+
}
188+
189+
std::vector<uint8_t> BASE64::Decode(std::string_view input)
190+
{
191+
std::vector<uint8_t> data;
192+
Decode(input.data(), input.size(), data);
193+
return data;
194+
}
195+
196+
std::string BASE64::DecodeToStr(std::string_view input)
197+
{
198+
std::vector<uint8_t> output;
199+
Decode(input.data(), input.size(), output);
200+
return {output.begin(), output.end()};
201+
}
202+
203+
bool BASE64::IsValidBase64(const std::string& input)
204+
{
205+
// Check for empty input or incorrect length
206+
if (input.empty() || input.size() % 4 != 0)
207+
return false;
208+
209+
// Use a lookup table for faster character checking
210+
bool lookup[256]{};
211+
for (char c : CHARACTERS)
212+
{
213+
lookup[static_cast<unsigned char>(c)] = true;
214+
}
215+
216+
// Iterate over the input string and check each character
217+
size_t paddingSize = 0;
218+
for (size_t i = 0; i < input.size(); ++i)
219+
{
220+
if (input[i] == '=')
221+
{
222+
paddingSize++;
223+
} // Check of characters after padding, and validity of characters
224+
else if (paddingSize > 0 || !lookup[static_cast<unsigned char>(input[i])])
225+
{
226+
return false; // Invalid character
227+
}
228+
}
229+
230+
// Max allowed padding chars
231+
if (paddingSize > 2)
232+
return false;
233+
234+
return true;
235+
}
236+
237+
bool BASE64::AddPadding(std::string& base64str)
238+
{
239+
const int mod = static_cast<int>(base64str.length() % 4);
240+
if (mod > 0)
241+
{
242+
for (int i = 4 - mod; i > 0; --i)
243+
{
244+
base64str.push_back(PADDING);
245+
}
246+
return true;
247+
}
248+
return false;
249+
}

src/iptvsimple/utilities/Base64.h

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#pragma once
2+
3+
#include "Logger.h"
4+
5+
#include <regex>
6+
#include <stdexcept>
7+
#include <vector>
8+
9+
namespace iptvsimple
10+
{
11+
namespace utilities
12+
{
13+
14+
class BASE64
15+
{
16+
public:
17+
// Encoding methods
18+
static void Encode(const uint8_t* input,
19+
const size_t length,
20+
std::string& output,
21+
const bool padding = true);
22+
static std::string Encode(const uint8_t* input, const size_t length, const bool padding = true);
23+
static std::string Encode(const std::vector<uint8_t>& input, const bool padding = true);
24+
static std::string Encode(const std::vector<char>& input, const bool padding = true);
25+
static std::string Encode(const std::string& input, const bool padding = true);
26+
27+
// Decoding methods
28+
static void Decode(const char* input, const size_t length, std::vector<uint8_t>& output);
29+
static std::vector<uint8_t> Decode(std::string_view input);
30+
static std::string DecodeToStr(std::string_view input);
31+
32+
// Validation and padding
33+
static bool IsValidBase64(const std::string& input);
34+
static bool AddPadding(std::string& base64str);
35+
};
36+
37+
} // namespace utilities
38+
} // namespace iptvsimple

0 commit comments

Comments
 (0)