MythTV  master
xmltvparser.cpp
Go to the documentation of this file.
1 #include "xmltvparser.h"
2 
3 // Qt headers
4 #include <QFile>
5 #include <QStringList>
6 #include <QDateTime>
7 #include <QDomDocument>
8 #include <QUrl>
9 
10 // C++ headers
11 #include <iostream>
12 #include <cstdlib>
13 
14 // libmyth headers
15 #include "exitcodes.h"
16 #include "mythcorecontext.h"
17 #include "mythdate.h"
18 
19 // libmythtv headers
20 #include "programinfo.h"
21 #include "programdata.h"
22 #include "dvbdescriptors.h"
23 #include "channelinfo.h"
24 
25 // libmythmetadata headers
26 #include "metadatadownload.h"
27 
28 // filldata headers
29 #include "channeldata.h"
30 #include "fillutil.h"
31 
32 XMLTVParser::XMLTVParser() : current_year(0)
33 {
34  current_year = MythDate::current().date().toString("yyyy").toUInt();
35 }
36 
38 {
41 }
42 
43 static uint ELFHash(const QByteArray &ba)
44 {
45  const uchar *k = (const uchar *)ba.data();
46  uint h = 0;
47  uint g;
48 
49  if (k)
50  {
51  while (*k)
52  {
53  h = (h << 4) + *k++;
54  if ((g = (h & 0xf0000000)) != 0)
55  h ^= g >> 24;
56  h &= ~g;
57  }
58  }
59 
60  return h;
61 }
62 
63 static QString getFirstText(QDomElement element)
64 {
65  for (QDomNode dname = element.firstChild(); !dname.isNull();
66  dname = dname.nextSibling())
67  {
68  QDomText t = dname.toText();
69  if (!t.isNull())
70  return t.data();
71  }
72  return QString();
73 }
74 
75 ChannelInfo *XMLTVParser::parseChannel(QDomElement &element, QUrl &baseUrl)
76 {
77  ChannelInfo *chaninfo = new ChannelInfo;
78 
79  QString xmltvid = element.attribute("id", "");
80 
81  chaninfo->xmltvid = xmltvid;
82  chaninfo->tvformat = "Default";
83 
84  for (QDomNode child = element.firstChild(); !child.isNull();
85  child = child.nextSibling())
86  {
87  QDomElement info = child.toElement();
88  if (!info.isNull())
89  {
90  if (info.tagName() == "icon")
91  {
92  QString path = info.attribute("src", "");
93  if (!path.isEmpty() && !path.contains("://"))
94  {
95  QString base = baseUrl.toString(QUrl::StripTrailingSlash);
96  chaninfo->icon = base +
97  ((path.startsWith("/")) ? path : QString("/") + path);
98  }
99  else if (!path.isEmpty())
100  {
101  QUrl url(path);
102  if (url.isValid())
103  chaninfo->icon = url.toString();
104  }
105  }
106  else if (info.tagName() == "display-name")
107  {
108  if (chaninfo->name.isEmpty())
109  {
110  chaninfo->name = info.text();
111  }
112  else if (chaninfo->callsign.isEmpty())
113  {
114  chaninfo->callsign = info.text();
115  }
116  else if (chaninfo->channum.isEmpty())
117  {
118  chaninfo->channum = info.text();
119  }
120  }
121  }
122  }
123 
124  chaninfo->freqid = chaninfo->channum;
125  return chaninfo;
126 }
127 
128 static void fromXMLTVDate(QString &timestr, QDateTime &dt)
129 {
130  // The XMLTV spec requires dates to either be in UTC/GMT or to specify a
131  // valid timezone. We are sticking to the spec and require all grabbers
132  // to comply.
133 
134  if (timestr.isEmpty())
135  {
136  LOG(VB_XMLTV, LOG_ERR, "Found empty Date/Time in XMLTV data, ignoring");
137  return;
138  }
139 
140  QStringList split = timestr.split(" ", QString::SkipEmptyParts);
141  QString ts = split[0];
142  QDate tmpDate;
143  QTime tmpTime;
144  QString tzoffset;
145 
146  // Process the TZ offset (if any)
147  if (split.size() > 1)
148  {
149  tzoffset = split[1];
150  // These shouldn't be required and they aren't ISO 8601 but the
151  // xmltv spec mentions these and just these so handle them just in
152  // case
153  if (tzoffset == "GMT" || tzoffset == "UTC")
154  tzoffset = "+0000";
155  else if (tzoffset == "BST")
156  tzoffset = "+0100";
157  }
158  else
159  {
160  // We will accept a datetime with a trailing Z as being explicit
161  if (ts.endsWith('Z'))
162  {
163  tzoffset = "+0000";
164  ts.truncate(ts.length()-1);
165  }
166  else
167  {
168  tzoffset = "+0000";
169  static bool warned_once_on_implicit_utc = false;
170  if (!warned_once_on_implicit_utc)
171  {
172  LOG(VB_XMLTV, LOG_WARNING, "No explicit time zone found, "
173  "guessing implicit UTC! Please consider enhancing "
174  "the guide source to provide explicit UTC or local "
175  "time instead.");
176  warned_once_on_implicit_utc = true;
177  }
178  }
179  }
180 
181  // Process the date part
182  QString tsDate = ts.left(8);
183  if (tsDate.length() == 8)
184  tmpDate = QDate::fromString(tsDate, "yyyyMMdd");
185  else if (tsDate.length() == 6)
186  tmpDate = QDate::fromString(tsDate, "yyyyMM");
187  else if (tsDate.length() == 4)
188  tmpDate = QDate::fromString(tsDate, "yyyy");
189  if (!tmpDate.isValid())
190  {
191  LOG(VB_XMLTV, LOG_ERR,
192  QString("Invalid datetime (date) in XMLTV data, ignoring: %1")
193  .arg(timestr));
194  return;
195  }
196 
197  // Process the time part (if any)
198  if (ts.length() > 8)
199  {
200  QString tsTime = ts.mid(8);
201  if (tsTime.length() == 6)
202  {
203  if (tsTime == "235960")
204  tsTime = "235959";
205  tmpTime = QTime::fromString(tsTime, "HHmmss");
206  }
207  else if (tsTime.length() == 4)
208  tmpTime = QTime::fromString(tsTime, "HHmm");
209  else if (tsTime.length() == 2)
210  tmpTime = QTime::fromString(tsTime, "HH");
211  if (!tmpTime.isValid())
212  {
213  // Time part exists, but is (somehow) invalid
214  LOG(VB_XMLTV, LOG_ERR,
215  QString("Invalid datetime (time) in XMLTV data, ignoring: %1")
216  .arg(timestr));
217  return;
218  }
219  }
220 
221  QDateTime tmpDT = QDateTime(tmpDate, tmpTime, Qt::UTC);
222  if (!tmpDT.isValid())
223  {
224  LOG(VB_XMLTV, LOG_ERR,
225  QString("Invalid datetime (combination of date/time) "
226  "in XMLTV data, ignoring: %1").arg(timestr));
227  return;
228  }
229 
230  // While this seems like a hack, it's better than what was done before
231  QString isoDateString = tmpDT.toString(Qt::ISODate);
232  if (isoDateString.endsWith('Z')) // Should always be Z, but ...
233  isoDateString.truncate(isoDateString.length()-1);
234  isoDateString += tzoffset;
235  dt = QDateTime::fromString(isoDateString, Qt::ISODate).toUTC();
236 
237  if (!dt.isValid())
238  {
239  LOG(VB_XMLTV, LOG_ERR,
240  QString("Invalid datetime (zone offset) in XMLTV data, "
241  "ignoring: %1").arg(timestr));
242  return;
243  }
244 
246 }
247 
248 static void parseCredits(QDomElement &element, ProgInfo *pginfo)
249 {
250  for (QDomNode child = element.firstChild(); !child.isNull();
251  child = child.nextSibling())
252  {
253  QDomElement info = child.toElement();
254  if (!info.isNull())
255  pginfo->AddPerson(info.tagName(), getFirstText(info));
256  }
257 }
258 
259 static void parseVideo(QDomElement &element, ProgInfo *pginfo)
260 {
261  for (QDomNode child = element.firstChild(); !child.isNull();
262  child = child.nextSibling())
263  {
264  QDomElement info = child.toElement();
265  if (!info.isNull())
266  {
267  if (info.tagName() == "quality")
268  {
269  if (getFirstText(info) == "HDTV")
270  pginfo->videoProps |= VID_HDTV;
271  }
272  else if (info.tagName() == "aspect")
273  {
274  if (getFirstText(info) == "16:9")
275  pginfo->videoProps |= VID_WIDESCREEN;
276  }
277  }
278  }
279 }
280 
281 static void parseAudio(QDomElement &element, ProgInfo *pginfo)
282 {
283  for (QDomNode child = element.firstChild(); !child.isNull();
284  child = child.nextSibling())
285  {
286  QDomElement info = child.toElement();
287  if (!info.isNull())
288  {
289  if (info.tagName() == "stereo")
290  {
291  if (getFirstText(info) == "mono")
292  {
293  pginfo->audioProps |= AUD_MONO;
294  }
295  else if (getFirstText(info) == "stereo")
296  {
297  pginfo->audioProps |= AUD_STEREO;
298  }
299  else if (getFirstText(info) == "dolby" ||
300  getFirstText(info) == "dolby digital")
301  {
302  pginfo->audioProps |= AUD_DOLBY;
303  }
304  else if (getFirstText(info) == "surround")
305  {
306  pginfo->audioProps |= AUD_SURROUND;
307  }
308  }
309  }
310  }
311 }
312 
313 ProgInfo *XMLTVParser::parseProgram(QDomElement &element)
314 {
315  QString programid, season, episode, totalepisodes;
316  ProgInfo *pginfo = new ProgInfo();
317 
318  QString text = element.attribute("start", "");
319  fromXMLTVDate(text, pginfo->starttime);
320  pginfo->startts = text;
321 
322  text = element.attribute("stop", "");
323  fromXMLTVDate(text, pginfo->endtime);
324  pginfo->endts = text;
325 
326  text = element.attribute("channel", "");
327  QStringList split = text.split(" ");
328 
329  pginfo->channel = split[0];
330 
331  text = element.attribute("clumpidx", "");
332  if (!text.isEmpty())
333  {
334  split = text.split('/');
335  pginfo->clumpidx = split[0];
336  pginfo->clumpmax = split[1];
337  }
338 
339  for (QDomNode child = element.firstChild(); !child.isNull();
340  child = child.nextSibling())
341  {
342  QDomElement info = child.toElement();
343  if (!info.isNull())
344  {
345  if (info.tagName() == "title")
346  {
347  if (info.attribute("lang") == "ja_JP")
348  {
349  pginfo->title = getFirstText(info);
350  }
351  else if (info.attribute("lang") == "ja_JP@kana")
352  {
353  pginfo->title_pronounce = getFirstText(info);
354  }
355  else if (pginfo->title.isEmpty())
356  {
357  pginfo->title = getFirstText(info);
358  }
359  }
360  else if (info.tagName() == "sub-title" &&
361  pginfo->subtitle.isEmpty())
362  {
363  pginfo->subtitle = getFirstText(info);
364  }
365  else if (info.tagName() == "desc" && pginfo->description.isEmpty())
366  {
367  pginfo->description = getFirstText(info);
368  }
369  else if (info.tagName() == "category")
370  {
371  const QString cat = getFirstText(info);
372 
373  if (ProgramInfo::kCategoryNone == pginfo->categoryType &&
375  {
377  }
378  else if (pginfo->category.isEmpty())
379  {
380  pginfo->category = cat;
381  }
382 
383  if ((cat.compare(QObject::tr("movie"),Qt::CaseInsensitive) == 0) ||
384  (cat.compare(QObject::tr("film"),Qt::CaseInsensitive) == 0))
385  {
386  // Hack for tv_grab_uk_rt
388  }
389 
390  pginfo->genres.append(cat);
391  }
392  else if (info.tagName() == "date" && !pginfo->airdate)
393  {
394  // Movie production year
395  QString date = getFirstText(info);
396  pginfo->airdate = date.left(4).toUInt();
397  }
398  else if (info.tagName() == "star-rating" && pginfo->stars == 0.0)
399  {
400  QDomNodeList values = info.elementsByTagName("value");
401  QDomElement item;
402  QString stars;
403  float num, den;
404  float rating = 0.0;
405 
406  // Use the first rating to appear in the xml, this should be
407  // the most important one.
408  //
409  // Averaging is not a good idea here, any subsequent ratings
410  // are likely to represent that days recommended programmes
411  // which on a bad night could given to an average programme.
412  // In the case of uk_rt it's not unknown for a recommendation
413  // to be given to programmes which are 'so bad, you have to
414  // watch!'
415  //
416  // XMLTV uses zero based ratings and signals no rating by absence.
417  // A rating from 1 to 5 is encoded as 0/4 to 4/4.
418  // MythTV uses zero to signal no rating!
419  // The same rating is encoded as 0.2 to 1.0 with steps of 0.2, it
420  // is not encoded as 0.0 to 1.0 with steps of 0.25 because
421  // 0 signals no rating!
422  // See http://xmltv.cvs.sourceforge.net/viewvc/xmltv/xmltv/xmltv.dtd?revision=1.47&view=markup#l539
423  item = values.item(0).toElement();
424  if (!item.isNull())
425  {
426  stars = getFirstText(item);
427  num = stars.section('/', 0, 0).toFloat() + 1;
428  den = stars.section('/', 1, 1).toFloat() + 1;
429  if (0.0 < den)
430  rating = num/den;
431  }
432 
433  pginfo->stars = rating;
434  }
435  else if (info.tagName() == "rating")
436  {
437  // again, the structure of ratings seems poorly represented
438  // in the XML. no idea what we'd do with multiple values.
439  QDomNodeList values = info.elementsByTagName("value");
440  QDomElement item = values.item(0).toElement();
441  if (item.isNull())
442  continue;
444  rating.system = info.attribute("system", "");
445  rating.rating = getFirstText(item);
446  pginfo->ratings.append(rating);
447  }
448  else if (info.tagName() == "previously-shown")
449  {
450  pginfo->previouslyshown = true;
451 
452  QString prevdate = info.attribute("start");
453  if (!prevdate.isEmpty())
454  {
455  QDateTime date;
456  fromXMLTVDate(prevdate, date);
457  pginfo->originalairdate = date.date();
458  }
459  }
460  else if (info.tagName() == "credits")
461  {
462  parseCredits(info, pginfo);
463  }
464  else if (info.tagName() == "subtitles")
465  {
466  if (info.attribute("type") == "teletext")
467  pginfo->subtitleType |= SUB_NORMAL;
468  else if (info.attribute("type") == "onscreen")
469  pginfo->subtitleType |= SUB_ONSCREEN;
470  else if (info.attribute("type") == "deaf-signed")
471  pginfo->subtitleType |= SUB_SIGNED;
472  }
473  else if (info.tagName() == "audio")
474  {
475  parseAudio(info, pginfo);
476  }
477  else if (info.tagName() == "video")
478  {
479  parseVideo(info, pginfo);
480  }
481  else if (info.tagName() == "episode-num")
482  {
483  if (info.attribute("system") == "dd_progid")
484  {
485  QString episodenum(getFirstText(info));
486  // if this field includes a dot, strip it out
487  int idx = episodenum.indexOf('.');
488  if (idx != -1)
489  episodenum.remove(idx, 1);
490  programid = episodenum;
491  /* Only EPisodes and SHows are part of a series for SD */
492  if (programid.startsWith(QString("EP")) ||
493  programid.startsWith(QString("SH")))
494  pginfo->seriesId = QString("EP") + programid.mid(2,8);
495  }
496  else if (info.attribute("system") == "xmltv_ns")
497  {
498  int tmp;
499  QString episodenum(getFirstText(info));
500  episode = episodenum.section('.',1,1);
501  totalepisodes = episode.section('/',1,1).trimmed();
502  episode = episode.section('/',0,0).trimmed();
503  season = episodenum.section('.',0,0).trimmed();
504  season = season.section('/',0,0).trimmed();
505  QString part(episodenum.section('.',2,2));
506  QString partnumber(part.section('/',0,0).trimmed());
507  QString parttotal(part.section('/',1,1).trimmed());
508 
510 
511  if (!season.isEmpty())
512  {
513  tmp = season.toUInt() + 1;
514  pginfo->season = tmp;
515  season = QString::number(tmp);
516  pginfo->syndicatedepisodenumber = QString('S' + season);
517  }
518 
519  if (!episode.isEmpty())
520  {
521  tmp = episode.toUInt() + 1;
522  pginfo->episode = tmp;
523  episode = QString::number(tmp);
524  pginfo->syndicatedepisodenumber.append(QString('E' + episode));
525  }
526 
527  if (!totalepisodes.isEmpty())
528  {
529  pginfo->totalepisodes = totalepisodes.toUInt();
530  }
531 
532  uint partno = 0;
533  if (!partnumber.isEmpty())
534  {
535  bool ok;
536  partno = partnumber.toUInt(&ok) + 1;
537  partno = (ok) ? partno : 0;
538  }
539 
540  if (!parttotal.isEmpty() && partno > 0)
541  {
542  bool ok;
543  uint partto = parttotal.toUInt(&ok);
544  if (ok && partnumber <= parttotal)
545  {
546  pginfo->parttotal = partto;
547  pginfo->partnumber = partno;
548  }
549  }
550  }
551  else if (info.attribute("system") == "onscreen")
552  {
554  if (pginfo->subtitle.isEmpty())
555  {
556  pginfo->subtitle = getFirstText(info);
557  }
558  }
559  else if ((info.attribute("system") == "themoviedb.org") &&
560  (_movieGrabberPath.endsWith(QString("/tmdb3.py"))))
561  {
562  /* text is movie/<inetref> */
563  QString inetrefRaw(getFirstText(info));
564  if (inetrefRaw.startsWith(QString("movie/"))) {
565  QString inetref(QString ("tmdb3.py_") + inetrefRaw.section('/',1,1).trimmed());
566  pginfo->inetref = inetref;
567  }
568  }
569  else if ((info.attribute("system") == "thetvdb.com") &&
570  (_tvGrabberPath.endsWith(QString("/ttvdb.py"))))
571  {
572  /* text is series/<inetref> */
573  QString inetrefRaw(getFirstText(info));
574  if (inetrefRaw.startsWith(QString("series/"))) {
575  QString inetref(QString ("ttvdb.py_") + inetrefRaw.section('/',1,1).trimmed());
576  pginfo->inetref = inetref;
577  /* ProgInfo does not have a collectionref, so we don't set any */
578  }
579  }
580  }
581  }
582  }
583 
584  if (pginfo->category.isEmpty() &&
587 
588  if (!pginfo->airdate
590  pginfo->airdate = current_year;
591 
592  if (programid.isEmpty())
593  {
594 
595  /* Let's build ourself a programid */
596 
598  programid = "MV";
599  else if (ProgramInfo::kCategorySeries == pginfo->categoryType)
600  programid = "EP";
601  else if (ProgramInfo::kCategorySports == pginfo->categoryType)
602  programid = "SP";
603  else
604  programid = "SH";
605 
606  QString seriesid = QString::number(ELFHash(pginfo->title.toUtf8()));
607  pginfo->seriesId = seriesid;
608  programid.append(seriesid);
609 
610  if (!episode.isEmpty() && !season.isEmpty())
611  {
612  /* Append unpadded episode and season number to the seriesid (to
613  maintain consistency with historical encoding), but limit the
614  season number representation to a single base-36 character to
615  ensure unique programid generation. */
616  int season_int = season.toInt();
617  if (season_int > 35)
618  {
619  // Cannot represent season as a single base-36 character, so
620  // remove the programid and fall back to normal dup matching.
622  programid.clear();
623  }
624  else
625  {
626  programid.append(episode);
627  programid.append(QString::number(season_int, 36));
628  if (pginfo->partnumber && pginfo->parttotal)
629  {
630  programid += QString::number(pginfo->partnumber);
631  programid += QString::number(pginfo->parttotal);
632  }
633  }
634  }
635  else
636  {
637  /* No ep/season info? Well then remove the programid and rely on
638  normal dupchecking methods instead. */
640  programid.clear();
641  }
642  }
643 
644  pginfo->programId = programid;
645 
646  return pginfo;
647 }
648 
650  QString filename, ChannelInfoList *chanlist,
651  QMap<QString, QList<ProgInfo> > *proglist)
652 {
653  QDomDocument doc;
654  QFile f;
655 
656  if (!dash_open(f, filename, QIODevice::ReadOnly))
657  {
658  LOG(VB_GENERAL, LOG_ERR,
659  QString("Error unable to open '%1' for reading.") .arg(filename));
660  return false;
661  }
662 
663  QString errorMsg = "unknown";
664  int errorLine = 0;
665  int errorColumn = 0;
666 
667  if (!doc.setContent(&f, &errorMsg, &errorLine, &errorColumn))
668  {
669  LOG(VB_GENERAL, LOG_ERR, QString("Error in %1:%2: %3")
670  .arg(errorLine).arg(errorColumn).arg(errorMsg));
671 
672  f.close();
673  return true;
674  }
675 
676  f.close();
677 
678  QDomElement docElem = doc.documentElement();
679 
680  QUrl baseUrl(docElem.attribute("source-data-url", ""));
681  //QUrl sourceUrl(docElem.attribute("source-info-url", ""));
682 
683  QString aggregatedTitle;
684  QString aggregatedDesc;
685 
686  QDomNode n = docElem.firstChild();
687  while (!n.isNull())
688  {
689  QDomElement e = n.toElement();
690  if (!e.isNull())
691  {
692  if (e.tagName() == "channel")
693  {
694  ChannelInfo *chinfo = parseChannel(e, baseUrl);
695  if (!chinfo->xmltvid.isEmpty())
696  chanlist->push_back(*chinfo);
697  delete chinfo;
698  }
699  else if (e.tagName() == "programme")
700  {
701  ProgInfo *pginfo = parseProgram(e);
702 
703  if (!(pginfo->starttime.isValid()))
704  {
705  LOG(VB_GENERAL, LOG_WARNING, QString("Invalid programme (%1), "
706  "invalid start time, "
707  "skipping")
708  .arg(pginfo->title));
709  }
710  else if (pginfo->channel.isEmpty())
711  {
712  LOG(VB_GENERAL, LOG_WARNING, QString("Invalid programme (%1), "
713  "missing channel, "
714  "skipping")
715  .arg(pginfo->title));
716  }
717  else if (pginfo->startts == pginfo->endts)
718  {
719  LOG(VB_GENERAL, LOG_WARNING, QString("Invalid programme (%1), "
720  "identical start and end "
721  "times, skipping")
722  .arg(pginfo->title));
723  }
724  else
725  {
726  if (pginfo->clumpidx.isEmpty())
727  (*proglist)[pginfo->channel].push_back(*pginfo);
728  else
729  {
730  /* append all titles/descriptions from one clump */
731  if (pginfo->clumpidx.toInt() == 0)
732  {
733  aggregatedTitle.clear();
734  aggregatedDesc.clear();
735  }
736 
737  if (!pginfo->title.isEmpty())
738  {
739  if (!aggregatedTitle.isEmpty())
740  aggregatedTitle.append(" | ");
741  aggregatedTitle.append(pginfo->title);
742  }
743 
744  if (!pginfo->description.isEmpty())
745  {
746  if (!aggregatedDesc.isEmpty())
747  aggregatedDesc.append(" | ");
748  aggregatedDesc.append(pginfo->description);
749  }
750  if (pginfo->clumpidx.toInt() ==
751  pginfo->clumpmax.toInt() - 1)
752  {
753  pginfo->title = aggregatedTitle;
754  pginfo->description = aggregatedDesc;
755  (*proglist)[pginfo->channel].push_back(*pginfo);
756  }
757  }
758  }
759  delete pginfo;
760  }
761  }
762  n = n.nextSibling();
763  }
764 
765  return true;
766 }
unsigned char audioProps
Definition: programdata.h:176
QString system
Definition: programdata.h:69
bool dash_open(QFile &file, const QString &filename, int m, FILE *handle)
Definition: fillutil.cpp:11
uint episode
Definition: programdata.h:188
static void parseVideo(QDomElement &element, ProgInfo *pginfo)
QString endts
Definition: programdata.h:268
QString title_pronounce
Definition: programdata.h:269
QString channum
Definition: channelinfo.h:78
unsigned int current_year
Definition: xmltvparser.h:28
QString tvformat
Definition: channelinfo.h:96
QString freqid
Definition: channelinfo.h:79
uint season
Definition: programdata.h:187
QString _tvGrabberPath
Definition: xmltvparser.h:30
QList< EventRating > ratings
Definition: programdata.h:185
unsigned char subtitleType
Definition: programdata.h:175
uint16_t parttotal
Definition: programdata.h:173
Default UTC, "yyyyMMddhhmmss".
Definition: mythdate.h:15
const char * filename
Definition: ioapi.h:135
unsigned int uint
Definition: compat.h:136
static void parseCredits(QDomElement &element, ProgInfo *pginfo)
bool parseFile(QString filename, ChannelInfoList *chanlist, QMap< QString, QList< ProgInfo > > *proglist)
QString subtitle
Definition: programdata.h:164
void lateInit()
Definition: xmltvparser.cpp:37
ProgramInfo::CategoryType string_to_myth_category_type(const QString &category_type)
QString programId
Definition: programdata.h:181
bool previouslyshown
Definition: programdata.h:183
float stars
Definition: programdata.h:178
const QString season
Definition: eitfixup.cpp:21
static void parseAudio(QDomElement &element, ProgInfo *pginfo)
QString rating
Definition: programdata.h:70
QString callsign
Definition: channelinfo.h:82
def rating(profile, smoonURL, gate)
Definition: scan.py:25
QString clumpmax
Definition: programdata.h:273
QString _movieGrabberPath
Definition: xmltvparser.h:29
QString startts
Definition: programdata.h:267
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
unsigned char t
Definition: ParseText.cpp:339
QString title
Definition: programdata.h:163
QString description
Definition: programdata.h:165
QStringList genres
Definition: programdata.h:186
QString seriesId
Definition: programdata.h:180
ProgInfo * parseProgram(QDomElement &element)
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
Definition: mythdate.cpp:72
static QString GetTelevisionGrabber()
static uint ELFHash(const QByteArray &ba)
Definition: xmltvparser.cpp:43
QString syndicatedepisodenumber
Definition: programdata.h:174
QString myth_category_type_to_string(ProgramInfo::CategoryType category_type)
vector< ChannelInfo > ChannelInfoList
Definition: channelinfo.h:121
QString category
Definition: programdata.h:166
uint16_t airdate
movie year / production year
Definition: programdata.h:169
QDateTime starttime
Definition: programdata.h:167
ChannelInfo * parseChannel(QDomElement &element, QUrl &baseUrl)
Definition: xmltvparser.cpp:75
QString clumpidx
Definition: programdata.h:272
uint totalepisodes
Definition: programdata.h:189
static void fromXMLTVDate(QString &timestr, QDateTime &dt)
QString icon
Definition: channelinfo.h:84
uint16_t partnumber
Definition: programdata.h:172
QDate originalairdate
origial broadcast date
Definition: programdata.h:170
QString name
Definition: channelinfo.h:83
unsigned char videoProps
Definition: programdata.h:177
QString xmltvid
Definition: channelinfo.h:88
ProgramInfo::CategoryType categoryType
Definition: programdata.h:179
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
Definition: mythdate.cpp:34
QDateTime endtime
Definition: programdata.h:168
static QString GetMovieGrabber()
Default UTC.
Definition: mythdate.h:14
void AddPerson(DBPerson::Role, const QString &name)
QString inetref
Definition: programdata.h:182
static QString getFirstText(QDomElement element)
Definition: xmltvparser.cpp:63
QString channel
Definition: programdata.h:266
unsigned char g
Definition: ParseText.cpp:339