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