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(" ");
141  QString ts = split[0];
142  QDateTime tmpDT;
143  tmpDT.setTimeSpec(Qt::LocalTime);
144 
145  // UTC/GMT, just strip
146  if (ts.endsWith('Z'))
147  ts.truncate(ts.length()-1);
148 
149  if (ts.length() == 14)
150  {
151  tmpDT = QDateTime::fromString(ts, "yyyyMMddHHmmss");
152  }
153  else if (ts.length() == 12)
154  {
155  tmpDT = QDateTime::fromString(ts, "yyyyMMddHHmm");
156  }
157  else if (ts.length() == 8)
158  {
159  tmpDT = QDateTime::fromString(ts, "yyyyMMdd");
160  }
161  else if (ts.length() == 6)
162  {
163  tmpDT = QDateTime::fromString(ts, "yyyyMM");
164  }
165  else if (ts.length() == 4)
166  {
167  tmpDT = QDateTime::fromString(ts, "yyyy");
168  }
169 
170  if (!tmpDT.isValid())
171  {
172  LOG(VB_GENERAL, LOG_ERR,
173  QString("Ignoring unknown timestamp format: %1")
174  .arg(ts));
175  return;
176  }
177 
178  if (split.size() > 1)
179  {
180  QString tmp = split[1].trimmed();
181 
182  // These shouldn't be required and they aren't ISO 8601 but the
183  // xmltv spec mentions these and just these so handle them just in
184  // case
185  if (tmp == "GMT" || tmp == "UTC")
186  tmp = "+0000";
187  else if (tmp == "BST")
188  tmp = "+0100";
189 
190  // While this seems like a hack, it's better than what was done before
191  QString isoDateString = QString("%1 %2").arg(tmpDT.toString(Qt::ISODate))
192  .arg(tmp);
193  // Work around Qt bug where zero offset dates are flagged as LocalTime
194  tmpDT = QDateTime::fromString(isoDateString, Qt::ISODate);
195  if (tmpDT.timeSpec() == Qt::LocalTime)
196  tmpDT.setTimeSpec(Qt::UTC);
197  dt = tmpDT.toUTC();
198  }
199 
200  if (!dt.isValid())
201  {
202  static bool warned_once_on_implicit_utc = false;
203  if (!warned_once_on_implicit_utc)
204  {
205  LOG(VB_XMLTV, LOG_ERR, "No explicit time zone found, "
206  "guessing implicit UTC! Please consider enhancing "
207  "the guide source to provice explicit UTC or local "
208  "time instead.");
209  warned_once_on_implicit_utc = true;
210  }
211  dt = tmpDT;
212  }
213 
214  dt.setTimeSpec(Qt::UTC);
215 
217 }
218 
219 static void parseCredits(QDomElement &element, ProgInfo *pginfo)
220 {
221  for (QDomNode child = element.firstChild(); !child.isNull();
222  child = child.nextSibling())
223  {
224  QDomElement info = child.toElement();
225  if (!info.isNull())
226  pginfo->AddPerson(info.tagName(), getFirstText(info));
227  }
228 }
229 
230 static void parseVideo(QDomElement &element, ProgInfo *pginfo)
231 {
232  for (QDomNode child = element.firstChild(); !child.isNull();
233  child = child.nextSibling())
234  {
235  QDomElement info = child.toElement();
236  if (!info.isNull())
237  {
238  if (info.tagName() == "quality")
239  {
240  if (getFirstText(info) == "HDTV")
241  pginfo->videoProps |= VID_HDTV;
242  }
243  else if (info.tagName() == "aspect")
244  {
245  if (getFirstText(info) == "16:9")
246  pginfo->videoProps |= VID_WIDESCREEN;
247  }
248  }
249  }
250 }
251 
252 static void parseAudio(QDomElement &element, ProgInfo *pginfo)
253 {
254  for (QDomNode child = element.firstChild(); !child.isNull();
255  child = child.nextSibling())
256  {
257  QDomElement info = child.toElement();
258  if (!info.isNull())
259  {
260  if (info.tagName() == "stereo")
261  {
262  if (getFirstText(info) == "mono")
263  {
264  pginfo->audioProps |= AUD_MONO;
265  }
266  else if (getFirstText(info) == "stereo")
267  {
268  pginfo->audioProps |= AUD_STEREO;
269  }
270  else if (getFirstText(info) == "dolby" ||
271  getFirstText(info) == "dolby digital")
272  {
273  pginfo->audioProps |= AUD_DOLBY;
274  }
275  else if (getFirstText(info) == "surround")
276  {
277  pginfo->audioProps |= AUD_SURROUND;
278  }
279  }
280  }
281  }
282 }
283 
284 ProgInfo *XMLTVParser::parseProgram(QDomElement &element)
285 {
286  QString programid, season, episode, totalepisodes;
287  ProgInfo *pginfo = new ProgInfo();
288 
289  QString text = element.attribute("start", "");
290  fromXMLTVDate(text, pginfo->starttime);
291  pginfo->startts = text;
292 
293  text = element.attribute("stop", "");
294  fromXMLTVDate(text, pginfo->endtime);
295  pginfo->endts = text;
296 
297  text = element.attribute("channel", "");
298  QStringList split = text.split(" ");
299 
300  pginfo->channel = split[0];
301 
302  text = element.attribute("clumpidx", "");
303  if (!text.isEmpty())
304  {
305  split = text.split('/');
306  pginfo->clumpidx = split[0];
307  pginfo->clumpmax = split[1];
308  }
309 
310  for (QDomNode child = element.firstChild(); !child.isNull();
311  child = child.nextSibling())
312  {
313  QDomElement info = child.toElement();
314  if (!info.isNull())
315  {
316  if (info.tagName() == "title")
317  {
318  if (info.attribute("lang") == "ja_JP")
319  {
320  pginfo->title = getFirstText(info);
321  }
322  else if (info.attribute("lang") == "ja_JP@kana")
323  {
324  pginfo->title_pronounce = getFirstText(info);
325  }
326  else if (pginfo->title.isEmpty())
327  {
328  pginfo->title = getFirstText(info);
329  }
330  }
331  else if (info.tagName() == "sub-title" &&
332  pginfo->subtitle.isEmpty())
333  {
334  pginfo->subtitle = getFirstText(info);
335  }
336  else if (info.tagName() == "desc" && pginfo->description.isEmpty())
337  {
338  pginfo->description = getFirstText(info);
339  }
340  else if (info.tagName() == "category")
341  {
342  const QString cat = getFirstText(info);
343 
344  if (ProgramInfo::kCategoryNone == pginfo->categoryType &&
346  {
348  }
349  else if (pginfo->category.isEmpty())
350  {
351  pginfo->category = cat;
352  }
353 
354  if ((cat.compare(QObject::tr("movie"),Qt::CaseInsensitive) == 0) ||
355  (cat.compare(QObject::tr("film"),Qt::CaseInsensitive) == 0))
356  {
357  // Hack for tv_grab_uk_rt
359  }
360 
361  pginfo->genres.append(cat);
362  }
363  else if (info.tagName() == "date" && !pginfo->airdate)
364  {
365  // Movie production year
366  QString date = getFirstText(info);
367  pginfo->airdate = date.left(4).toUInt();
368  }
369  else if (info.tagName() == "star-rating" && pginfo->stars == 0.0)
370  {
371  QDomNodeList values = info.elementsByTagName("value");
372  QDomElement item;
373  QString stars;
374  float num, den;
375  float rating = 0.0;
376 
377  // Use the first rating to appear in the xml, this should be
378  // the most important one.
379  //
380  // Averaging is not a good idea here, any subsequent ratings
381  // are likely to represent that days recommended programmes
382  // which on a bad night could given to an average programme.
383  // In the case of uk_rt it's not unknown for a recommendation
384  // to be given to programmes which are 'so bad, you have to
385  // watch!'
386  //
387  // XMLTV uses zero based ratings and signals no rating by absence.
388  // A rating from 1 to 5 is encoded as 0/4 to 4/4.
389  // MythTV uses zero to signal no rating!
390  // The same rating is encoded as 0.2 to 1.0 with steps of 0.2, it
391  // is not encoded as 0.0 to 1.0 with steps of 0.25 because
392  // 0 signals no rating!
393  // See http://xmltv.cvs.sourceforge.net/viewvc/xmltv/xmltv/xmltv.dtd?revision=1.47&view=markup#l539
394  item = values.item(0).toElement();
395  if (!item.isNull())
396  {
397  stars = getFirstText(item);
398  num = stars.section('/', 0, 0).toFloat() + 1;
399  den = stars.section('/', 1, 1).toFloat() + 1;
400  if (0.0 < den)
401  rating = num/den;
402  }
403 
404  pginfo->stars = rating;
405  }
406  else if (info.tagName() == "rating")
407  {
408  // again, the structure of ratings seems poorly represented
409  // in the XML. no idea what we'd do with multiple values.
410  QDomNodeList values = info.elementsByTagName("value");
411  QDomElement item = values.item(0).toElement();
412  if (item.isNull())
413  continue;
415  rating.system = info.attribute("system", "");
416  rating.rating = getFirstText(item);
417  pginfo->ratings.append(rating);
418  }
419  else if (info.tagName() == "previously-shown")
420  {
421  pginfo->previouslyshown = true;
422 
423  QString prevdate = info.attribute("start");
424  if (!prevdate.isEmpty())
425  {
426  QDateTime date;
427  fromXMLTVDate(prevdate, date);
428  pginfo->originalairdate = date.date();
429  }
430  }
431  else if (info.tagName() == "credits")
432  {
433  parseCredits(info, pginfo);
434  }
435  else if (info.tagName() == "subtitles")
436  {
437  if (info.attribute("type") == "teletext")
438  pginfo->subtitleType |= SUB_NORMAL;
439  else if (info.attribute("type") == "onscreen")
440  pginfo->subtitleType |= SUB_ONSCREEN;
441  else if (info.attribute("type") == "deaf-signed")
442  pginfo->subtitleType |= SUB_SIGNED;
443  }
444  else if (info.tagName() == "audio")
445  {
446  parseAudio(info, pginfo);
447  }
448  else if (info.tagName() == "video")
449  {
450  parseVideo(info, pginfo);
451  }
452  else if (info.tagName() == "episode-num")
453  {
454  if (info.attribute("system") == "dd_progid")
455  {
456  QString episodenum(getFirstText(info));
457  // if this field includes a dot, strip it out
458  int idx = episodenum.indexOf('.');
459  if (idx != -1)
460  episodenum.remove(idx, 1);
461  programid = episodenum;
462  /* Only EPisodes and SHows are part of a series for SD */
463  if (programid.startsWith(QString("EP")) ||
464  programid.startsWith(QString("SH")))
465  pginfo->seriesId = QString("EP") + programid.mid(2,8);
466  }
467  else if (info.attribute("system") == "xmltv_ns")
468  {
469  int tmp;
470  QString episodenum(getFirstText(info));
471  episode = episodenum.section('.',1,1);
472  totalepisodes = episode.section('/',1,1).trimmed();
473  episode = episode.section('/',0,0).trimmed();
474  season = episodenum.section('.',0,0).trimmed();
475  QString part(episodenum.section('.',2,2));
476  QString partnumber(part.section('/',0,0).trimmed());
477  QString parttotal(part.section('/',1,1).trimmed());
478 
480 
481  if (!season.isEmpty())
482  {
483  tmp = season.toUInt() + 1;
484  pginfo->season = tmp;
485  season = QString::number(tmp);
486  pginfo->syndicatedepisodenumber = QString('S' + season);
487  }
488 
489  if (!episode.isEmpty())
490  {
491  tmp = episode.toUInt() + 1;
492  pginfo->episode = tmp;
493  episode = QString::number(tmp);
494  pginfo->syndicatedepisodenumber.append(QString('E' + episode));
495  }
496 
497  if (!totalepisodes.isEmpty())
498  {
499  pginfo->totalepisodes = totalepisodes.toUInt();
500  }
501 
502  uint partno = 0;
503  if (!partnumber.isEmpty())
504  {
505  bool ok;
506  partno = partnumber.toUInt(&ok) + 1;
507  partno = (ok) ? partno : 0;
508  }
509 
510  if (!parttotal.isEmpty() && partno > 0)
511  {
512  bool ok;
513  uint partto = parttotal.toUInt(&ok);
514  if (ok && partnumber <= parttotal)
515  {
516  pginfo->parttotal = partto;
517  pginfo->partnumber = partno;
518  }
519  }
520  }
521  else if (info.attribute("system") == "onscreen")
522  {
524  if (pginfo->subtitle.isEmpty())
525  {
526  pginfo->subtitle = getFirstText(info);
527  }
528  }
529  else if ((info.attribute("system") == "themoviedb.org") &&
530  (_movieGrabberPath.endsWith(QString("/tmdb3.py"))))
531  {
532  /* text is movie/<inetref> */
533  QString inetrefRaw(getFirstText(info));
534  if (inetrefRaw.startsWith(QString("movie/"))) {
535  QString inetref(QString ("tmdb3.py_") + inetrefRaw.section('/',1,1).trimmed());
536  pginfo->inetref = inetref;
537  }
538  }
539  else if ((info.attribute("system") == "thetvdb.com") &&
540  (_tvGrabberPath.endsWith(QString("/ttvdb.py"))))
541  {
542  /* text is series/<inetref> */
543  QString inetrefRaw(getFirstText(info));
544  if (inetrefRaw.startsWith(QString("series/"))) {
545  QString inetref(QString ("ttvdb.py_") + inetrefRaw.section('/',1,1).trimmed());
546  pginfo->inetref = inetref;
547  /* ProgInfo does not have a collectionref, so we don't set any */
548  }
549  }
550  }
551  }
552  }
553 
554  if (pginfo->category.isEmpty() &&
557 
558  if (!pginfo->airdate
560  pginfo->airdate = current_year;
561 
562  if (programid.isEmpty())
563  {
564 
565  /* Let's build ourself a programid */
566 
568  programid = "MV";
569  else if (ProgramInfo::kCategorySeries == pginfo->categoryType)
570  programid = "EP";
571  else if (ProgramInfo::kCategorySports == pginfo->categoryType)
572  programid = "SP";
573  else
574  programid = "SH";
575 
576  QString seriesid = QString::number(ELFHash(pginfo->title.toUtf8()));
577  pginfo->seriesId = seriesid;
578  programid.append(seriesid);
579 
580  if (!episode.isEmpty() && !season.isEmpty())
581  {
582  /* Append unpadded episode and season number to the seriesid (to
583  maintain consistency with historical encoding), but limit the
584  season number representation to a single base-36 character to
585  ensure unique programid generation. */
586  int season_int = season.toInt();
587  if (season_int > 35)
588  {
589  // Cannot represent season as a single base-36 character, so
590  // remove the programid and fall back to normal dup matching.
592  programid.clear();
593  }
594  else
595  {
596  programid.append(episode);
597  programid.append(QString::number(season_int, 36));
598  if (pginfo->partnumber && pginfo->parttotal)
599  {
600  programid += QString::number(pginfo->partnumber);
601  programid += QString::number(pginfo->parttotal);
602  }
603  }
604  }
605  else
606  {
607  /* No ep/season info? Well then remove the programid and rely on
608  normal dupchecking methods instead. */
610  programid.clear();
611  }
612  }
613 
614  pginfo->programId = programid;
615 
616  return pginfo;
617 }
618 
620  QString filename, ChannelInfoList *chanlist,
621  QMap<QString, QList<ProgInfo> > *proglist)
622 {
623  QDomDocument doc;
624  QFile f;
625 
626  if (!dash_open(f, filename, QIODevice::ReadOnly))
627  {
628  LOG(VB_GENERAL, LOG_ERR,
629  QString("Error unable to open '%1' for reading.") .arg(filename));
630  return false;
631  }
632 
633  QString errorMsg = "unknown";
634  int errorLine = 0;
635  int errorColumn = 0;
636 
637  if (!doc.setContent(&f, &errorMsg, &errorLine, &errorColumn))
638  {
639  LOG(VB_GENERAL, LOG_ERR, QString("Error in %1:%2: %3")
640  .arg(errorLine).arg(errorColumn).arg(errorMsg));
641 
642  f.close();
643  return true;
644  }
645 
646  f.close();
647 
648  QDomElement docElem = doc.documentElement();
649 
650  QUrl baseUrl(docElem.attribute("source-data-url", ""));
651  //QUrl sourceUrl(docElem.attribute("source-info-url", ""));
652 
653  QString aggregatedTitle;
654  QString aggregatedDesc;
655 
656  QDomNode n = docElem.firstChild();
657  while (!n.isNull())
658  {
659  QDomElement e = n.toElement();
660  if (!e.isNull())
661  {
662  if (e.tagName() == "channel")
663  {
664  ChannelInfo *chinfo = parseChannel(e, baseUrl);
665  if (!chinfo->xmltvid.isEmpty())
666  chanlist->push_back(*chinfo);
667  delete chinfo;
668  }
669  else if (e.tagName() == "programme")
670  {
671  ProgInfo *pginfo = parseProgram(e);
672 
673  if (pginfo->startts == pginfo->endts)
674  {
675  LOG(VB_GENERAL, LOG_WARNING, QString("Invalid programme (%1), "
676  "identical start and end "
677  "times, skipping")
678  .arg(pginfo->title));
679  }
680  else
681  {
682  if (pginfo->clumpidx.isEmpty())
683  (*proglist)[pginfo->channel].push_back(*pginfo);
684  else
685  {
686  /* append all titles/descriptions from one clump */
687  if (pginfo->clumpidx.toInt() == 0)
688  {
689  aggregatedTitle.clear();
690  aggregatedDesc.clear();
691  }
692 
693  if (!pginfo->title.isEmpty())
694  {
695  if (!aggregatedTitle.isEmpty())
696  aggregatedTitle.append(" | ");
697  aggregatedTitle.append(pginfo->title);
698  }
699 
700  if (!pginfo->description.isEmpty())
701  {
702  if (!aggregatedDesc.isEmpty())
703  aggregatedDesc.append(" | ");
704  aggregatedDesc.append(pginfo->description);
705  }
706  if (pginfo->clumpidx.toInt() ==
707  pginfo->clumpmax.toInt() - 1)
708  {
709  pginfo->title = aggregatedTitle;
710  pginfo->description = aggregatedDesc;
711  (*proglist)[pginfo->channel].push_back(*pginfo);
712  }
713  }
714  }
715  delete pginfo;
716  }
717  }
718  n = n.nextSibling();
719  }
720 
721  return true;
722 }
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