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