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