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