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