source: mythtv/mythtv/programs/mythfilldatabase/xmltvparser.cpp @ bcbcb356dc

github-templates
Last change on this file since bcbcb356dc was ff0067852, checked in by Gary Buhrmaster <gary.buhrmaster@…>, 4 years ago

Utilize Schedules Direct metadata if grabber makes it available

Schedules Direct provides some additional metadata that at least one
upstream XMLTV grabber now optionally makes available. Use that
metadata if it is provided by the grabber (typically the grabber will
need to be told to provide the additional information via a grabber
--configure).

In particular, with this patch, the original air date and a new
episode flag is available and can be useful to better schedule
recordings.

The (real soon now) next XMLTV release will include the new grabber,
and one can obtain the upstream master code now.

If the additional metadata information is not available (or not
configured in the grabber to be made available) the results are the
same as today.

Fixes #13655

Signed-off-by: David Engel <dengel@…>

  • Property mode set to 100644
File size: 34.1 KB
Line 
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
32XMLTVParser::XMLTVParser()
33{
34    m_currentYear = MythDate::current().date().toString("yyyy").toUInt();
35}
36
37static 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
57static 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
178    timestr = MythDate::toString(dt, MythDate::kFilename);
179}
180
181static 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
192bool XMLTVParser::parseFile(
193    const QString& filename, ChannelInfoList *chanlist,
194    QMap<QString, QList<ProgInfo> > *proglist)
195{
196    m_movieGrabberPath = MetadataDownload::GetMovieGrabber();
197    m_tvGrabberPath = MetadataDownload::GetTelevisionGrabber();
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
369                        if (ProgramInfo::kCategoryNone == pginfo->m_categoryType && string_to_myth_category_type(cat) != ProgramInfo::kCategoryNone)
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.left(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                        {
460                            EventRating rating;
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}
Note: See TracBrowser for help on using the repository browser.