MythTV  master
m3u.cpp
Go to the documentation of this file.
1 #include <QStringList>
2 #include <QUrl>
3 
4 #include "HLS/m3u.h"
5 #include "mythlogging.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 QString();
32 
33  QStringList list = line.mid(p + 1).split(',');
34  for (const auto & it : qAsConst(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 QString();
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.midRef(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.midRef(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  // cppcheck-suppress constParameter
279  bool ParseKey(int version, const QString& line, 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 #ifndef USING_LIBCRYPTO
290  Q_UNUSED(aesmsg);
291 #endif
292 
293  path.clear();
294  iv.clear();
295 
296  QString attr = ParseAttributes(line, "METHOD");
297  if (attr.isNull())
298  {
299  LOG(VB_RECORD, LOG_ERR, loc + "#EXT-X-KEY: expected METHOD=<value>");
300  return false;
301  }
302 
303  if (attr.startsWith(QLatin1String("NONE")))
304  {
305  QString uri = ParseAttributes(line, "URI");
306  if (!uri.isNull())
307  {
308  LOG(VB_RECORD, LOG_ERR, loc + "#EXT-X-KEY: URI not expected");
309  return false;
310  }
311  /* IV is only supported in version 2 and above */
312  if (version >= 2)
313  {
314  iv = ParseAttributes(line, "IV");
315  if (!iv.isNull())
316  {
317  LOG(VB_RECORD, LOG_ERR, loc + "#EXT-X-KEY: IV not expected");
318  return false;
319  }
320  }
321  }
322 #ifdef USING_LIBCRYPTO
323  else if (attr.startsWith(QLatin1String("AES-128")))
324  {
325  QString uri;
326  if (!aesmsg)
327  {
328  LOG(VB_RECORD, LOG_INFO, loc +
329  "playback of AES-128 encrypted HTTP Live media detected.");
330  aesmsg = true;
331  }
332  uri = ParseAttributes(line, "URI");
333  if (uri.isNull())
334  {
335  LOG(VB_RECORD, LOG_ERR, loc + "#EXT-X-KEY: URI not found for "
336  "encrypted HTTP Live media in AES-128");
337  return false;
338  }
339 
340  /* Url is between quotes, remove them */
341  path = DecodedURI(uri.remove(QChar(QLatin1Char('"'))));
342  iv = ParseAttributes(line, "IV");
343  }
344 #endif
345  else
346  {
347 #ifndef _MSC_VER
348  LOG(VB_RECORD, LOG_ERR, loc +
349  "invalid encryption type, only NONE "
350 #ifdef USING_LIBCRYPTO
351  "and AES-128 are supported"
352 #else
353  "is supported."
354 #endif
355  );
356 #else
357 // msvc doesn't like #ifdef in the middle of the LOG macro.
358 // Errors with '#':invalid character: possibly the result of a macro expansion
359 #endif
360  return false;
361  }
362  return true;
363  }
364 
365  bool ParseProgramDateTime(const QString& line, const QString& loc,
366  QDateTime &/*date*/)
367  {
368  /*
369  * #EXT-X-PROGRAM-DATE-TIME:<YYYY-MM-DDThh:mm:ssZ>
370  */
371  LOG(VB_RECORD, LOG_DEBUG, loc +
372  QString("tag not supported: #EXT-X-PROGRAM-DATE-TIME %1")
373  .arg(line));
374  return true;
375  }
376 
377  bool ParseAllowCache(const QString& line, const QString& loc, bool &do_cache)
378  {
379  /*
380  * The EXT-X-ALLOW-CACHE tag indicates whether the client MAY or MUST
381  * NOT cache downloaded media files for later replay. It MAY occur
382  * anywhere in the Playlist file; it MUST NOT occur more than once. The
383  * EXT-X-ALLOW-CACHE tag applies to all segments in the playlist. Its
384  * format is:
385  *
386  * #EXT-X-ALLOW-CACHE:<YES|NO>
387  */
388 
389  int pos = line.indexOf(QLatin1String(":"));
390  if (pos < 0)
391  {
392  LOG(VB_RECORD, LOG_ERR, loc +
393  QString("ParseAllowCache: missing ':' in '%1'")
394  .arg(line));
395  return false;
396  }
397  QString answer = line.mid(pos+1, 3);
398  if (answer.size() < 2)
399  {
400  LOG(VB_RECORD, LOG_ERR, loc + "#EXT-X-ALLOW-CACHE, ignoring ...");
401  return true;
402  }
403  do_cache = (!answer.startsWith(QLatin1String("NO")));
404 
405  return true;
406  }
407 
408  bool ParseDiscontinuity(const QString& line, const QString& loc)
409  {
410  /* Not handled, never seen so far */
411  LOG(VB_RECORD, LOG_DEBUG, loc + QString("#EXT-X-DISCONTINUITY %1")
412  .arg(line));
413  return true;
414  }
415 
416  bool ParseEndList(const QString& loc, bool& is_vod)
417  {
418  /*
419  * The EXT-X-ENDLIST tag indicates that no more media files will be
420  * added to the Playlist file. It MAY occur anywhere in the Playlist
421  * file; it MUST NOT occur more than once. Its format is:
422  */
423  is_vod = true;
424  LOG(VB_RECORD, LOG_INFO, loc + "video on demand (vod) mode");
425  return true;
426  }
427 
428 } // Namespace M3U
M3U::ParseProgramDateTime
bool ParseProgramDateTime(const QString &line, const QString &loc, QDateTime &)
Definition: m3u.cpp:365
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
title
QString title
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:636
arg
arg(title).arg(filename).arg(doDelete))
M3U
Definition: m3u.cpp:8
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
M3U::ParseMediaSequence
bool ParseMediaSequence(int64_t &sequence_num, const QString &line, const QString &loc)
Definition: m3u.cpp:257
mythlogging.h
M3U::ParseKey
bool ParseKey(int version, const QString &line, bool &aesmsg, const QString &loc, QString &path, QString &iv)
Definition: m3u.cpp:279
hardwareprofile.config.p
p
Definition: config.py:33
M3U::ParseAllowCache
bool ParseAllowCache(const QString &line, const QString &loc, bool &do_cache)
Definition: m3u.cpp:377
M3U::DecodedURI
QString DecodedURI(const QString &uri)
Definition: m3u.cpp:9
M3U::ParseDiscontinuity
bool ParseDiscontinuity(const QString &line, const QString &loc)
Definition: m3u.cpp:408
uint
unsigned int uint
Definition: compat.h:140
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:416
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:81