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.isEmpty())
362  {
363  QDomNodeList values = info.elementsByTagName("value");
364  QDomElement item;
365  QString stars, num, den;
366  float rating = 0.0;
367 
368  // Use the first rating to appear in the xml, this should be
369  // the most important one.
370  //
371  // Averaging is not a good idea here, any subsequent ratings
372  // are likely to represent that days recommended programmes
373  // which on a bad night could given to an average programme.
374  // In the case of uk_rt it's not unknown for a recommendation
375  // to be given to programmes which are 'so bad, you have to
376  // watch!'
377  item = values.item(0).toElement();
378  if (!item.isNull())
379  {
380  stars = getFirstText(item);
381  num = stars.section('/', 0, 0);
382  den = stars.section('/', 1, 1);
383  if (0.0 < den.toFloat())
384  rating = num.toFloat()/den.toFloat();
385  }
386 
387  pginfo->stars.setNum(rating);
388  }
389  else if (info.tagName() == "rating")
390  {
391  // again, the structure of ratings seems poorly represented
392  // in the XML. no idea what we'd do with multiple values.
393  QDomNodeList values = info.elementsByTagName("value");
394  QDomElement item = values.item(0).toElement();
395  if (item.isNull())
396  continue;
398  rating.system = info.attribute("system", "");
399  rating.rating = getFirstText(item);
400  pginfo->ratings.append(rating);
401  }
402  else if (info.tagName() == "previously-shown")
403  {
404  pginfo->previouslyshown = true;
405 
406  QString prevdate = info.attribute("start");
407  if (!prevdate.isEmpty())
408  {
409  QDateTime date;
410  fromXMLTVDate(prevdate, date);
411  pginfo->originalairdate = date.date();
412  }
413  }
414  else if (info.tagName() == "credits")
415  {
416  parseCredits(info, pginfo);
417  }
418  else if (info.tagName() == "subtitles")
419  {
420  if (info.attribute("type") == "teletext")
421  pginfo->subtitleType |= SUB_NORMAL;
422  else if (info.attribute("type") == "onscreen")
423  pginfo->subtitleType |= SUB_ONSCREEN;
424  else if (info.attribute("type") == "deaf-signed")
425  pginfo->subtitleType |= SUB_SIGNED;
426  }
427  else if (info.tagName() == "audio")
428  {
429  parseAudio(info, pginfo);
430  }
431  else if (info.tagName() == "video")
432  {
433  parseVideo(info, pginfo);
434  }
435  else if (info.tagName() == "episode-num")
436  {
437  if (info.attribute("system") == "dd_progid")
438  {
439  QString episodenum(getFirstText(info));
440  // if this field includes a dot, strip it out
441  int idx = episodenum.indexOf('.');
442  if (idx != -1)
443  episodenum.remove(idx, 1);
444  pginfo->programId = episodenum;
445  dd_progid_done = 1;
446  }
447  else if (info.attribute("system") == "xmltv_ns")
448  {
449  int tmp;
450  QString episodenum(getFirstText(info));
451  episode = episodenum.section('.',1,1);
452  totalepisodes = episode.section('/',1,1).trimmed();
453  episode = episode.section('/',0,0).trimmed();
454  season = episodenum.section('.',0,0).trimmed();
455  QString part(episodenum.section('.',2,2));
456  QString partnumber(part.section('/',0,0).trimmed());
457  QString parttotal(part.section('/',1,1).trimmed());
458 
460 
461  if (!season.isEmpty())
462  {
463  tmp = season.toUInt() + 1;
464  pginfo->season = tmp;
465  season = QString::number(tmp);
466  pginfo->syndicatedepisodenumber = QString('S' + season);
467  }
468 
469  if (!episode.isEmpty())
470  {
471  tmp = episode.toUInt() + 1;
472  pginfo->episode = tmp;
473  episode = QString::number(tmp);
474  pginfo->syndicatedepisodenumber.append(QString('E' + episode));
475  }
476 
477  if (!totalepisodes.isEmpty())
478  {
479  pginfo->totalepisodes = totalepisodes.toUInt();
480  }
481 
482  uint partno = 0;
483  if (!partnumber.isEmpty())
484  {
485  bool ok;
486  partno = partnumber.toUInt(&ok) + 1;
487  partno = (ok) ? partno : 0;
488  }
489 
490  if (!parttotal.isEmpty() && partno > 0)
491  {
492  bool ok;
493  uint partto = parttotal.toUInt(&ok);
494  if (ok && partnumber <= parttotal)
495  {
496  pginfo->parttotal = partto;
497  pginfo->partnumber = partno;
498  }
499  }
500  }
501  else if (info.attribute("system") == "onscreen")
502  {
504  if (pginfo->subtitle.isEmpty())
505  {
506  pginfo->subtitle = getFirstText(info);
507  }
508  }
509  else if ((info.attribute("system") == "themoviedb.org") &&
510  (MetadataDownload::GetMovieGrabber().endsWith(QString("/tmdb3.py"))))
511  {
512  /* text is movie/<inetref> */
513  QString inetrefRaw(getFirstText(info));
514  if (inetrefRaw.startsWith(QString("movie/"))) {
515  QString inetref(inetrefRaw.section('/',1,1).trimmed());
516  pginfo->inetref = inetref;
517  }
518  }
519  else if ((info.attribute("system") == "thetvdb.com") &&
520  (MetadataDownload::GetTelevisionGrabber().endsWith(QString("/ttvdb.py"))))
521  {
522  /* text is series/<inetref> */
523  QString inetrefRaw(getFirstText(info));
524  if (inetrefRaw.startsWith(QString("series/"))) {
525  QString inetref(inetrefRaw.section('/',1,1).trimmed());
526  pginfo->inetref = inetref;
527  }
528  }
529  }
530  }
531  }
532 
533  if (pginfo->category.isEmpty() &&
536 
537  if (!pginfo->airdate)
538  pginfo->airdate = current_year;
539 
540  /* Let's build ourself a programid */
541  QString programid;
542 
544  programid = "MV";
545  else if (ProgramInfo::kCategorySeries == pginfo->categoryType)
546  programid = "EP";
547  else if (ProgramInfo::kCategorySports == pginfo->categoryType)
548  programid = "SP";
549  else
550  programid = "SH";
551 
552  if (!uniqueid.isEmpty()) // we already have a unique id ready for use
553  programid.append(uniqueid);
554  else
555  {
556  QString seriesid = QString::number(ELFHash(pginfo->title.toUtf8()));
557  pginfo->seriesId = seriesid;
558  programid.append(seriesid);
559 
560  if (!episode.isEmpty() && !season.isEmpty())
561  {
562  /* Append unpadded episode and season number to the seriesid (to
563  maintain consistency with historical encoding), but limit the
564  season number representation to a single base-36 character to
565  ensure unique programid generation. */
566  int season_int = season.toInt();
567  if (season_int > 35)
568  {
569  // Cannot represent season as a single base-36 character, so
570  // remove the programid and fall back to normal dup matching.
572  programid.clear();
573  }
574  else
575  {
576  programid.append(episode);
577  programid.append(QString::number(season_int, 36));
578  if (pginfo->partnumber && pginfo->parttotal)
579  {
580  programid += QString::number(pginfo->partnumber);
581  programid += QString::number(pginfo->parttotal);
582  }
583  }
584  }
585  else
586  {
587  /* No ep/season info? Well then remove the programid and rely on
588  normal dupchecking methods instead. */
590  programid.clear();
591  }
592  }
593  if (dd_progid_done == 0)
594  pginfo->programId = programid;
595 
596  return pginfo;
597 }
598 
600  QString filename, ChannelInfoList *chanlist,
601  QMap<QString, QList<ProgInfo> > *proglist)
602 {
603  QDomDocument doc;
604  QFile f;
605 
606  if (!dash_open(f, filename, QIODevice::ReadOnly))
607  {
608  LOG(VB_GENERAL, LOG_ERR,
609  QString("Error unable to open '%1' for reading.") .arg(filename));
610  return false;
611  }
612 
613  QString errorMsg = "unknown";
614  int errorLine = 0;
615  int errorColumn = 0;
616 
617  if (!doc.setContent(&f, &errorMsg, &errorLine, &errorColumn))
618  {
619  LOG(VB_GENERAL, LOG_ERR, QString("Error in %1:%2: %3")
620  .arg(errorLine).arg(errorColumn).arg(errorMsg));
621 
622  f.close();
623  return true;
624  }
625 
626  f.close();
627 
628  QDomElement docElem = doc.documentElement();
629 
630  QUrl baseUrl(docElem.attribute("source-data-url", ""));
631  //QUrl sourceUrl(docElem.attribute("source-info-url", ""));
632 
633  QString aggregatedTitle;
634  QString aggregatedDesc;
635 
636  QDomNode n = docElem.firstChild();
637  while (!n.isNull())
638  {
639  QDomElement e = n.toElement();
640  if (!e.isNull())
641  {
642  if (e.tagName() == "channel")
643  {
644  ChannelInfo *chinfo = parseChannel(e, baseUrl);
645  if (!chinfo->xmltvid.isEmpty())
646  chanlist->push_back(*chinfo);
647  delete chinfo;
648  }
649  else if (e.tagName() == "programme")
650  {
651  ProgInfo *pginfo = parseProgram(e);
652 
653  if (pginfo->startts == pginfo->endts)
654  {
655  LOG(VB_GENERAL, LOG_WARNING, QString("Invalid programme (%1), "
656  "identical start and end "
657  "times, skipping")
658  .arg(pginfo->title));
659  }
660  else
661  {
662  if (pginfo->clumpidx.isEmpty())
663  (*proglist)[pginfo->channel].push_back(*pginfo);
664  else
665  {
666  /* append all titles/descriptions from one clump */
667  if (pginfo->clumpidx.toInt() == 0)
668  {
669  aggregatedTitle.clear();
670  aggregatedDesc.clear();
671  }
672 
673  if (!pginfo->title.isEmpty())
674  {
675  if (!aggregatedTitle.isEmpty())
676  aggregatedTitle.append(" | ");
677  aggregatedTitle.append(pginfo->title);
678  }
679 
680  if (!pginfo->description.isEmpty())
681  {
682  if (!aggregatedDesc.isEmpty())
683  aggregatedDesc.append(" | ");
684  aggregatedDesc.append(pginfo->description);
685  }
686  if (pginfo->clumpidx.toInt() ==
687  pginfo->clumpmax.toInt() - 1)
688  {
689  pginfo->title = aggregatedTitle;
690  pginfo->description = aggregatedDesc;
691  (*proglist)[pginfo->channel].push_back(*pginfo);
692  }
693  }
694  }
695  delete pginfo;
696  }
697  }
698  n = n.nextSibling();
699  }
700 
701  return true;
702 }
703 
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:265
QString title_pronounce
Definition: programdata.h:267
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:135
static void parseCredits(QDomElement &element, ProgInfo *pginfo)
bool parseFile(QString filename, ChannelInfoList *chanlist, QMap< QString, QList< ProgInfo > > *proglist)
unsigned char t
Definition: ParseText.cpp:339
QString subtitle
Definition: programdata.h:162
ProgramInfo::CategoryType string_to_myth_category_type(const QString &category_type)
Definition: programinfo.cpp:99
QString programId
Definition: programdata.h:179
bool previouslyshown
Definition: programdata.h:181
static void parseAudio(QDomElement &element, ProgInfo *pginfo)
QString stars
Definition: programdata.h:266
QString rating
Definition: programdata.h:68
QString callsign
Definition: channelinfo.h:82
QString clumpmax
Definition: programdata.h:271
QString startts
Definition: programdata.h:264
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
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)
unsigned k
Definition: minilzo.cpp:2292
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:86
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:270
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:263
unsigned char g
Definition: ParseText.cpp:339