MythTV  0.28pre
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 uniqueid, season, episode, totalepisodes;
287  int dd_progid_done = 0;
288  ProgInfo *pginfo = new ProgInfo();
289 
290  QString text = element.attribute("start", "");
291  fromXMLTVDate(text, pginfo->starttime);
292  pginfo->startts = text;
293 
294  text = element.attribute("stop", "");
295  fromXMLTVDate(text, pginfo->endtime);
296  pginfo->endts = text;
297 
298  text = element.attribute("channel", "");
299  QStringList split = text.split(" ");
300 
301  pginfo->channel = split[0];
302 
303  text = element.attribute("clumpidx", "");
304  if (!text.isEmpty())
305  {
306  split = text.split('/');
307  pginfo->clumpidx = split[0];
308  pginfo->clumpmax = split[1];
309  }
310 
311  for (QDomNode child = element.firstChild(); !child.isNull();
312  child = child.nextSibling())
313  {
314  QDomElement info = child.toElement();
315  if (!info.isNull())
316  {
317  if (info.tagName() == "title")
318  {
319  if (info.attribute("lang") == "ja_JP")
320  {
321  pginfo->title = getFirstText(info);
322  }
323  else if (info.attribute("lang") == "ja_JP@kana")
324  {
325  pginfo->title_pronounce = getFirstText(info);
326  }
327  else if (pginfo->title.isEmpty())
328  {
329  pginfo->title = getFirstText(info);
330  }
331  }
332  else if (info.tagName() == "sub-title" &&
333  pginfo->subtitle.isEmpty())
334  {
335  pginfo->subtitle = getFirstText(info);
336  }
337  else if (info.tagName() == "desc" && pginfo->description.isEmpty())
338  {
339  pginfo->description = getFirstText(info);
340  }
341  else if (info.tagName() == "category")
342  {
343  const QString cat = getFirstText(info);
344 
345  if (ProgramInfo::kCategoryNone == pginfo->categoryType &&
347  {
349  }
350  else if (pginfo->category.isEmpty())
351  {
352  pginfo->category = cat;
353  }
354 
355  if ((cat.compare(QObject::tr("movie"),Qt::CaseInsensitive) == 0) ||
356  (cat.compare(QObject::tr("film"),Qt::CaseInsensitive) == 0))
357  {
358  // Hack for tv_grab_uk_rt
360  }
361 
362  pginfo->genres.append(cat);
363  }
364  else if (info.tagName() == "date" && !pginfo->airdate)
365  {
366  // Movie production year
367  QString date = getFirstText(info);
368  pginfo->airdate = date.left(4).toUInt();
369  }
370  else if (info.tagName() == "star-rating" && pginfo->stars == 0.0)
371  {
372  QDomNodeList values = info.elementsByTagName("value");
373  QDomElement item;
374  QString stars;
375  float num, den;
376  float rating = 0.0;
377 
378  // Use the first rating to appear in the xml, this should be
379  // the most important one.
380  //
381  // Averaging is not a good idea here, any subsequent ratings
382  // are likely to represent that days recommended programmes
383  // which on a bad night could given to an average programme.
384  // In the case of uk_rt it's not unknown for a recommendation
385  // to be given to programmes which are 'so bad, you have to
386  // watch!'
387  //
388  // XMLTV uses zero based ratings and signals no rating by absence.
389  // A rating from 1 to 5 is encoded as 0/4 to 4/4.
390  // MythTV uses zero to signal no rating!
391  // The same rating is encoded as 0.2 to 1.0 with steps of 0.2, it
392  // is not encoded as 0.0 to 1.0 with steps of 0.25 because
393  // 0 signals no rating!
394  // See http://xmltv.cvs.sourceforge.net/viewvc/xmltv/xmltv/xmltv.dtd?revision=1.47&view=markup#l539
395  item = values.item(0).toElement();
396  if (!item.isNull())
397  {
398  stars = getFirstText(item);
399  num = stars.section('/', 0, 0).toFloat() + 1;
400  den = stars.section('/', 1, 1).toFloat() + 1;
401  if (0.0 < den)
402  rating = num/den;
403  }
404 
405  pginfo->stars = rating;
406  }
407  else if (info.tagName() == "rating")
408  {
409  // again, the structure of ratings seems poorly represented
410  // in the XML. no idea what we'd do with multiple values.
411  QDomNodeList values = info.elementsByTagName("value");
412  QDomElement item = values.item(0).toElement();
413  if (item.isNull())
414  continue;
416  rating.system = info.attribute("system", "");
417  rating.rating = getFirstText(item);
418  pginfo->ratings.append(rating);
419  }
420  else if (info.tagName() == "previously-shown")
421  {
422  pginfo->previouslyshown = true;
423 
424  QString prevdate = info.attribute("start");
425  if (!prevdate.isEmpty())
426  {
427  QDateTime date;
428  fromXMLTVDate(prevdate, date);
429  pginfo->originalairdate = date.date();
430  }
431  }
432  else if (info.tagName() == "credits")
433  {
434  parseCredits(info, pginfo);
435  }
436  else if (info.tagName() == "subtitles")
437  {
438  if (info.attribute("type") == "teletext")
439  pginfo->subtitleType |= SUB_NORMAL;
440  else if (info.attribute("type") == "onscreen")
441  pginfo->subtitleType |= SUB_ONSCREEN;
442  else if (info.attribute("type") == "deaf-signed")
443  pginfo->subtitleType |= SUB_SIGNED;
444  }
445  else if (info.tagName() == "audio")
446  {
447  parseAudio(info, pginfo);
448  }
449  else if (info.tagName() == "video")
450  {
451  parseVideo(info, pginfo);
452  }
453  else if (info.tagName() == "episode-num")
454  {
455  if (info.attribute("system") == "dd_progid")
456  {
457  QString episodenum(getFirstText(info));
458  // if this field includes a dot, strip it out
459  int idx = episodenum.indexOf('.');
460  if (idx != -1)
461  episodenum.remove(idx, 1);
462  pginfo->programId = episodenum;
463  dd_progid_done = 1;
464  }
465  else if (info.attribute("system") == "xmltv_ns")
466  {
467  int tmp;
468  QString episodenum(getFirstText(info));
469  episode = episodenum.section('.',1,1);
470  totalepisodes = episode.section('/',1,1).trimmed();
471  episode = episode.section('/',0,0).trimmed();
472  season = episodenum.section('.',0,0).trimmed();
473  QString part(episodenum.section('.',2,2));
474  QString partnumber(part.section('/',0,0).trimmed());
475  QString parttotal(part.section('/',1,1).trimmed());
476 
478 
479  if (!season.isEmpty())
480  {
481  tmp = season.toUInt() + 1;
482  pginfo->season = tmp;
483  season = QString::number(tmp);
484  pginfo->syndicatedepisodenumber = QString('S' + season);
485  }
486 
487  if (!episode.isEmpty())
488  {
489  tmp = episode.toUInt() + 1;
490  pginfo->episode = tmp;
491  episode = QString::number(tmp);
492  pginfo->syndicatedepisodenumber.append(QString('E' + episode));
493  }
494 
495  if (!totalepisodes.isEmpty())
496  {
497  pginfo->totalepisodes = totalepisodes.toUInt();
498  }
499 
500  uint partno = 0;
501  if (!partnumber.isEmpty())
502  {
503  bool ok;
504  partno = partnumber.toUInt(&ok) + 1;
505  partno = (ok) ? partno : 0;
506  }
507 
508  if (!parttotal.isEmpty() && partno > 0)
509  {
510  bool ok;
511  uint partto = parttotal.toUInt(&ok);
512  if (ok && partnumber <= parttotal)
513  {
514  pginfo->parttotal = partto;
515  pginfo->partnumber = partno;
516  }
517  }
518  }
519  else if (info.attribute("system") == "onscreen")
520  {
522  if (pginfo->subtitle.isEmpty())
523  {
524  pginfo->subtitle = getFirstText(info);
525  }
526  }
527  else if ((info.attribute("system") == "themoviedb.org") &&
528  (_movieGrabberPath.endsWith(QString("/tmdb3.py"))))
529  {
530  /* text is movie/<inetref> */
531  QString inetrefRaw(getFirstText(info));
532  if (inetrefRaw.startsWith(QString("movie/"))) {
533  QString inetref(QString ("tmdb3.py_") + inetrefRaw.section('/',1,1).trimmed());
534  pginfo->inetref = inetref;
535  }
536  }
537  else if ((info.attribute("system") == "thetvdb.com") &&
538  (_tvGrabberPath.endsWith(QString("/ttvdb.py"))))
539  {
540  /* text is series/<inetref> */
541  QString inetrefRaw(getFirstText(info));
542  if (inetrefRaw.startsWith(QString("series/"))) {
543  QString inetref(QString ("ttvdb.py_") + inetrefRaw.section('/',1,1).trimmed());
544  pginfo->inetref = inetref;
545  /* ProgInfo does not have a collectionref, so we don't set any */
546  }
547  }
548  }
549  }
550  }
551 
552  if (pginfo->category.isEmpty() &&
555 
556  if (!pginfo->airdate
558  pginfo->airdate = current_year;
559 
560  /* Let's build ourself a programid */
561  QString programid;
562 
564  programid = "MV";
565  else if (ProgramInfo::kCategorySeries == pginfo->categoryType)
566  programid = "EP";
567  else if (ProgramInfo::kCategorySports == pginfo->categoryType)
568  programid = "SP";
569  else
570  programid = "SH";
571 
572  if (!uniqueid.isEmpty()) // we already have a unique id ready for use
573  programid.append(uniqueid);
574  else
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  if (dd_progid_done == 0)
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