MythTV  master
m3u.cpp
Go to the documentation of this file.
1 #include <QStringList>
2 #include <QUrl>
3 
5 #include "HLS/m3u.h"
6 
7 namespace M3U
8 {
9  QString DecodedURI(const QString& uri)
10  {
11  QByteArray ba = uri.toLatin1();
12  QUrl url = QUrl::fromEncoded(ba);
13  return url.toString();
14  }
15 
16  QString RelativeURI(const QString& surl, const QString& spath)
17  {
18  QUrl url = QUrl(surl);
19  QUrl path = QUrl(spath);
20 
21  if (!path.isRelative())
22  return spath;
23 
24  return url.resolved(path).toString();
25  }
26 
27  QString ParseAttributes(const QString& line, const char *attr)
28  {
29  int p = line.indexOf(QLatin1String(":"));
30  if (p < 0)
31  return {};
32 
33  QStringList list = line.mid(p + 1).split(',');
34  for (const auto & it : std::as_const(list))
35  {
36  QString arg = it.trimmed();
37  if (arg.startsWith(attr))
38  {
39  int pos = arg.indexOf(QLatin1String("="));
40  if (pos < 0)
41  continue;
42  return arg.mid(pos+1);
43  }
44  }
45  return {};
46  }
47 
52  bool ParseDecimalValue(const QString& line, int &target)
53  {
54  int p = line.indexOf(QLatin1String(":"));
55  if (p < 0)
56  return false;
57  int i = p;
58  while (++i < line.size() && line[i].isNumber());
59  if (i == p + 1)
60  return false;
61  target = line.mid(p + 1, i - p - 1).toInt();
62  return true;
63  }
64 
69  bool ParseDecimalValue(const QString& line, int64_t &target)
70  {
71  int p = line.indexOf(QLatin1String(":"));
72  if (p < 0)
73  return false;
74  int i = p;
75  while (++i < line.size() && line[i].isNumber());
76  if (i == p + 1)
77  return false;
78  target = line.mid(p + 1, i - p - 1).toInt();
79  return true;
80  }
81 
82  bool ParseVersion(const QString& line, const QString& loc, int& version)
83  {
84  /*
85  * The EXT-X-VERSION tag indicates the compatibility version of the
86  * Playlist file. The Playlist file, its associated media, and its
87  * server MUST comply with all provisions of the most-recent version of
88  * this document describing the protocol version indicated by the tag
89  * value.
90  *
91  * Its format is:
92  *
93  * #EXT-X-VERSION:<n>
94  */
95 
96  if (line.isNull() || !ParseDecimalValue(line, version))
97  {
98  LOG(VB_RECORD, LOG_ERR, loc +
99  "#EXT-X-VERSION: no protocol version found, should be version 1.");
100  version = 1;
101  return false;
102  }
103 
104  if (version <= 0 || version > 3)
105  {
106  LOG(VB_RECORD, LOG_ERR, loc +
107  QString("#EXT-X-VERSION is %1, but we only understand 0 through 3")
108  .arg(version));
109  return false;
110  }
111 
112  return true;
113  }
114 
115  bool ParseStreamInformation(const QString& line,
116  const QString& url,
117  const QString& loc,
118  int& id, uint64_t& bandwidth)
119  {
120  LOG(VB_RECORD, LOG_INFO, loc +
121  QString("Parsing stream from %1").arg(url));
122 
123  /*
124  * #EXT-X-STREAM-INF:[attribute=value][,attribute=value]*
125  * <URI>
126  */
127  QString attr;
128 
129  attr = ParseAttributes(line, "PROGRAM-ID");
130  if (attr.isNull())
131  {
132  LOG(VB_RECORD, LOG_INFO, loc +
133  "#EXT-X-STREAM-INF: expected PROGRAM-ID=<value>, using -1");
134  id = -1;
135  }
136  else
137  {
138  id = attr.toInt();
139  }
140 
141  attr = ParseAttributes(line, "BANDWIDTH");
142  if (attr.isNull())
143  {
144  LOG(VB_RECORD, LOG_ERR, loc +
145  "#EXT-X-STREAM-INF: expected BANDWIDTH=<value>");
146  return false;
147  }
148  bandwidth = attr.toInt();
149 
150  if (bandwidth == 0)
151  {
152  LOG(VB_RECORD, LOG_ERR, loc +
153  "#EXT-X-STREAM-INF: bandwidth cannot be 0");
154  return false;
155  }
156 
157  LOG(VB_RECORD, LOG_INFO, loc +
158  QString("bandwidth adaptation detected (program-id=%1, bandwidth=%2")
159  .arg(id).arg(bandwidth));
160 
161  return true;
162  }
163 
164  bool ParseTargetDuration(const QString& line, const QString& loc,
165  int& duration)
166  {
167  /*
168  * #EXT-X-TARGETDURATION:<s>
169  *
170  * where s is an integer indicating the target duration in seconds.
171  */
172  duration = -1;
173 
174  if (!ParseDecimalValue(line, duration))
175  {
176  LOG(VB_RECORD, LOG_ERR, loc + "expected #EXT-X-TARGETDURATION:<s>");
177  return false;
178  }
179 
180  return true;
181  }
182 
183  bool ParseSegmentInformation(int version, const QString& line,
184  uint& duration, QString& title,
185  const QString& loc)
186  {
187  /*
188  * #EXTINF:<duration>,<title>
189  *
190  * "duration" is an integer that specifies the duration of the media
191  * file in seconds. Durations SHOULD be rounded to the nearest integer.
192  * The remainder of the line following the comma is the title of the
193  * media file, which is an optional human-readable informative title of
194  * the media segment
195  */
196  int p = line.indexOf(QLatin1String(":"));
197  if (p < 0)
198  {
199  LOG(VB_RECORD, LOG_ERR, loc +
200  QString("ParseSegmentInformation: Missing ':' in '%1'")
201  .arg(line));
202  return false;
203  }
204 
205  QStringList list = line.mid(p + 1).split(',');
206 
207  /* read duration */
208  if (list.isEmpty())
209  {
210  LOG(VB_RECORD, LOG_ERR, loc +
211  QString("ParseSegmentInformation: Missing arguments in '%1'")
212  .arg(line));
213  return false;
214  }
215 
216  QString val = list[0];
217 
218  if (version < 3)
219  {
220  bool ok = false;
221  duration = val.toInt(&ok);
222  if (!ok)
223  {
224  duration = -1;
225  LOG(VB_RECORD, LOG_ERR, loc +
226  QString("ParseSegmentInformation: invalid duration in '%1'")
227  .arg(line));
228  }
229  }
230  else
231  {
232  bool ok = false;
233  double d = val.toDouble(&ok);
234  if (!ok)
235  {
236  duration = -1;
237  LOG(VB_RECORD, LOG_ERR, loc +
238  QString("ParseSegmentInformation: invalid duration in '%1'")
239  .arg(line));
240  return false;
241  }
242  if ((d) - ((int)d) >= 0.5)
243  duration = ((int)d) + 1;
244  else
245  duration = ((int)d);
246  }
247 
248  if (list.size() >= 2)
249  {
250  title = list[1];
251  }
252 
253  /* Ignore the rest of the line */
254  return true;
255  }
256 
257  bool ParseMediaSequence(int64_t & sequence_num,
258  const QString& line, const QString& loc)
259  {
260  /*
261  * #EXT-X-MEDIA-SEQUENCE:<number>
262  *
263  * A Playlist file MUST NOT contain more than one EXT-X-MEDIA-SEQUENCE
264  * tag. If the Playlist file does not contain an EXT-X-MEDIA-SEQUENCE
265  * tag then the sequence number of the first URI in the playlist SHALL
266  * be considered to be 0.
267  */
268 
269  if (!ParseDecimalValue(line, sequence_num))
270  {
271  LOG(VB_RECORD, LOG_ERR, loc + "expected #EXT-X-MEDIA-SEQUENCE:<s>");
272  return false;
273  }
274 
275  return true;
276  }
277 
278  bool ParseKey(int version, const QString& line,
279  [[maybe_unused]] bool& aesmsg,
280  const QString& loc, QString &path, QString &iv)
281  {
282  /*
283  * #EXT-X-KEY:METHOD=<method>[,URI="<URI>"][,IV=<IV>]
284  *
285  * The METHOD attribute specifies the encryption method. Two encryption
286  * methods are defined: NONE and AES-128.
287  */
288 
289  path.clear();
290  iv.clear();
291 
292  QString attr = ParseAttributes(line, "METHOD");
293  if (attr.isNull())
294  {
295  LOG(VB_RECORD, LOG_ERR, loc + "#EXT-X-KEY: expected METHOD=<value>");
296  return false;
297  }
298 
299  if (attr.startsWith(QLatin1String("NONE")))
300  {
301  QString uri = ParseAttributes(line, "URI");
302  if (!uri.isNull())
303  {
304  LOG(VB_RECORD, LOG_ERR, loc + "#EXT-X-KEY: URI not expected");
305  return false;
306  }
307  /* IV is only supported in version 2 and above */
308  if (version >= 2)
309  {
310  iv = ParseAttributes(line, "IV");
311  if (!iv.isNull())
312  {
313  LOG(VB_RECORD, LOG_ERR, loc + "#EXT-X-KEY: IV not expected");
314  return false;
315  }
316  }
317  }
318 #ifdef USING_LIBCRYPTO
319  else if (attr.startsWith(QLatin1String("AES-128")))
320  {
321  QString uri;
322  if (!aesmsg)
323  {
324  LOG(VB_RECORD, LOG_INFO, loc +
325  "playback of AES-128 encrypted HTTP Live media detected.");
326  aesmsg = true;
327  }
328  uri = ParseAttributes(line, "URI");
329  if (uri.isNull())
330  {
331  LOG(VB_RECORD, LOG_ERR, loc + "#EXT-X-KEY: URI not found for "
332  "encrypted HTTP Live media in AES-128");
333  return false;
334  }
335 
336  /* Url is between quotes, remove them */
337  path = DecodedURI(uri.remove(QChar(QLatin1Char('"'))));
338  iv = ParseAttributes(line, "IV");
339  }
340 #endif
341  else
342  {
343 #ifndef _MSC_VER
344  LOG(VB_RECORD, LOG_ERR, loc +
345  "invalid encryption type, only NONE "
346 #ifdef USING_LIBCRYPTO
347  "and AES-128 are supported"
348 #else
349  "is supported."
350 #endif
351  );
352 #else
353 // msvc doesn't like #ifdef in the middle of the LOG macro.
354 // Errors with '#':invalid character: possibly the result of a macro expansion
355 #endif
356  return false;
357  }
358  return true;
359  }
360 
361  bool ParseProgramDateTime(const QString& line, const QString& loc,
362  QDateTime &/*date*/)
363  {
364  /*
365  * #EXT-X-PROGRAM-DATE-TIME:<YYYY-MM-DDThh:mm:ssZ>
366  */
367  LOG(VB_RECORD, LOG_DEBUG, loc +
368  QString("tag not supported: #EXT-X-PROGRAM-DATE-TIME %1")
369  .arg(line));
370  return true;
371  }
372 
373  bool ParseAllowCache(const QString& line, const QString& loc, bool &do_cache)
374  {
375  /*
376  * The EXT-X-ALLOW-CACHE tag indicates whether the client MAY or MUST
377  * NOT cache downloaded media files for later replay. It MAY occur
378  * anywhere in the Playlist file; it MUST NOT occur more than once. The
379  * EXT-X-ALLOW-CACHE tag applies to all segments in the playlist. Its
380  * format is:
381  *
382  * #EXT-X-ALLOW-CACHE:<YES|NO>
383  */
384 
385  int pos = line.indexOf(QLatin1String(":"));
386  if (pos < 0)
387  {
388  LOG(VB_RECORD, LOG_ERR, loc +
389  QString("ParseAllowCache: missing ':' in '%1'")
390  .arg(line));
391  return false;
392  }
393  QString answer = line.mid(pos+1, 3);
394  if (answer.size() < 2)
395  {
396  LOG(VB_RECORD, LOG_ERR, loc + "#EXT-X-ALLOW-CACHE, ignoring ...");
397  return true;
398  }
399  do_cache = (!answer.startsWith(QLatin1String("NO")));
400 
401  return true;
402  }
403 
404  bool ParseDiscontinuity(const QString& line, const QString& loc)
405  {
406  /* Not handled, never seen so far */
407  LOG(VB_RECORD, LOG_DEBUG, loc + QString("#EXT-X-DISCONTINUITY %1")
408  .arg(line));
409  return true;
410  }
411 
412  bool ParseEndList(const QString& loc, bool& is_vod)
413  {
414  /*
415  * The EXT-X-ENDLIST tag indicates that no more media files will be
416  * added to the Playlist file. It MAY occur anywhere in the Playlist
417  * file; it MUST NOT occur more than once. Its format is:
418  */
419  is_vod = true;
420  LOG(VB_RECORD, LOG_INFO, loc + "video on demand (vod) mode");
421  return true;
422  }
423 
424 } // Namespace M3U
M3U::ParseProgramDateTime
bool ParseProgramDateTime(const QString &line, const QString &loc, QDateTime &)
Definition: m3u.cpp:361
M3U::RelativeURI
QString RelativeURI(const QString &surl, const QString &spath)
Definition: m3u.cpp:16
M3U::ParseDecimalValue
bool ParseDecimalValue(const QString &line, int &target)
Return the decimal argument in a line of type: blah:<decimal> presence of value <decimal> is compulso...
Definition: m3u.cpp:52
M3U
Definition: m3u.cpp:7
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
M3U::ParseKey
bool ParseKey(int version, const QString &line, [[maybe_unused]] bool &aesmsg, const QString &loc, QString &path, QString &iv)
Definition: m3u.cpp:278
M3U::ParseMediaSequence
bool ParseMediaSequence(int64_t &sequence_num, const QString &line, const QString &loc)
Definition: m3u.cpp:257
mythlogging.h
hardwareprofile.config.p
p
Definition: config.py:33
M3U::ParseAllowCache
bool ParseAllowCache(const QString &line, const QString &loc, bool &do_cache)
Definition: m3u.cpp:373
M3U::DecodedURI
QString DecodedURI(const QString &uri)
Definition: m3u.cpp:9
M3U::ParseDiscontinuity
bool ParseDiscontinuity(const QString &line, const QString &loc)
Definition: m3u.cpp:404
uint
unsigned int uint
Definition: compat.h:81
M3U::ParseAttributes
QString ParseAttributes(const QString &line, const char *attr)
Definition: m3u.cpp:27
M3U::ParseVersion
bool ParseVersion(const QString &line, const QString &loc, int &version)
Definition: m3u.cpp:82
M3U::ParseStreamInformation
bool ParseStreamInformation(const QString &line, const QString &url, const QString &loc, int &id, uint64_t &bandwidth)
Definition: m3u.cpp:115
M3U::ParseSegmentInformation
bool ParseSegmentInformation(int version, const QString &line, uint &duration, QString &title, const QString &loc)
Definition: m3u.cpp:183
d
static const iso6937table * d
Definition: iso6937tables.cpp:1025
M3U::ParseEndList
bool ParseEndList(const QString &loc, bool &is_vod)
Definition: m3u.cpp:412
M3U::ParseTargetDuration
bool ParseTargetDuration(const QString &line, const QString &loc, int &duration)
Definition: m3u.cpp:164
m3u.h
nv_python_libs.bbciplayer.bbciplayer_api.version
string version
Definition: bbciplayer_api.py:77