MythTV master
rssparse.cpp
Go to the documentation of this file.
1#include "rssparse.h"
2
3#include <QFile>
4#include <QDataStream>
5#include <QDomDocument>
6#include <QDomImplementation>
7#include <QHash>
8#include <QLocale>
9#include <QUrl>
10#include <QFileInfo>
11
17
18ResultItem::ResultItem(QString title, QString sortTitle,
19 QString subtitle, QString sortSubtitle,
20 QString desc, QString URL,
21 QString thumbnail, QString mediaURL,
22 QString author, const QDateTime& date,
23 const QString& time, const QString& rating,
24 const off_t filesize, const QString& player,
25 const QStringList& playerargs, const QString& download,
26 const QStringList& downloadargs, const uint width,
27 const uint height, const QString& language,
28 const bool downloadable, const QStringList& countries,
29 const uint season, const uint episode,
30 const bool customhtml)
31 : m_title(std::move(title)), m_sorttitle(std::move(sortTitle)),
32 m_subtitle(std::move(subtitle)), m_sortsubtitle(std::move(sortSubtitle)),
33 m_desc(std::move(desc)), m_url(std::move(URL)),
34 m_thumbnail(std::move(thumbnail)), m_mediaURL(std::move(mediaURL)),
35 m_author(std::move(author))
36{
37 if (!date.isNull())
38 m_date = date;
39 else
40 m_date = QDateTime();
41 m_time = time;
43 m_filesize = filesize;
44 m_player = player;
45 m_playerargs = playerargs;
46 m_download = download;
47 m_downloadargs = downloadargs;
48 m_width = width;
49 m_height = height;
50 m_language = language;
51 m_downloadable = downloadable;
52 m_countries = countries;
53 m_season = season;
54 m_episode = episode;
55 m_customhtml = customhtml;
56
58}
59
61{
62 std::shared_ptr<MythSortHelper>sh = getMythSortHelper();
63
64 if (m_sorttitle.isEmpty() and not m_title.isEmpty())
65 m_sorttitle = sh->doTitle(m_title);
66 if (m_sortsubtitle.isEmpty() and not m_subtitle.isEmpty())
67 m_sortsubtitle = sh->doTitle(m_subtitle);
68}
69
70void ResultItem::toMap(InfoMap &metadataMap)
71{
72 metadataMap["title"] = m_title;
73 metadataMap["sorttitle"] = m_sorttitle;
74 metadataMap["subtitle"] = m_subtitle;
75 metadataMap["sortsubtitle"] = m_sortsubtitle;
76 metadataMap["description"] = m_desc;
77 metadataMap["url"] = m_url;
78 metadataMap["thumbnail"] = m_thumbnail;
79 metadataMap["mediaurl"] = m_mediaURL;
80 metadataMap["author"] = m_author;
81
82 if (m_date.isNull())
83 metadataMap["date"] = QString();
84 else
85 metadataMap["date"] = MythDate::toString(m_date, MythDate::kDateFull);
86
87 if (m_time.toInt() == 0)
88 {
89 metadataMap["length"] = QString();
90 }
91 else
92 {
93 QTime time(0,0,0,0);
94 int secs = m_time.toInt();
95 QTime fin = time.addSecs(secs);
96 QString format;
97 if (secs >= 3600)
98 format = "H:mm:ss";
99 else if (secs >= 600)
100 format = "mm:ss";
101 else if (secs >= 60)
102 format = "m:ss";
103 else
104 format = ":ss";
105 metadataMap["length"] = fin.toString(format);
106 }
107
108 if (m_rating == nullptr || m_rating.isNull())
109 metadataMap["rating"] = QString();
110 else
111 metadataMap["rating"] = m_rating;
112
113 if (m_filesize == -1)
114 metadataMap["filesize"] = QString();
115 else if (m_filesize == 0 && !m_downloadable)
116 metadataMap["filesize"] = QObject::tr("Web Only");
117 else if (m_filesize == 0 && m_downloadable)
118 metadataMap["filesize"] = QObject::tr("Downloadable");
119 else
120 metadataMap["filesize"] = QString::number(m_filesize);
121
122 QString tmpSize = QString("%1 ")
123 .arg(m_filesize / 1024.0 / 1024.0, 0,'f',2);
124 tmpSize += QObject::tr("MB", "Megabytes");
125 if (m_filesize == -1)
126 metadataMap["filesize_str"] = QString();
127 else if (m_filesize == 0 && !m_downloadable)
128 metadataMap["filesize_str"] = QObject::tr("Web Only");
129 else if (m_filesize == 0 && m_downloadable)
130 metadataMap["filesize_str"] = QObject::tr("Downloadable");
131 else
132 metadataMap["filesize"] = tmpSize;
133
134 metadataMap["player"] = m_player;
135 metadataMap["playerargs"] = m_playerargs.join(", ");
136 metadataMap["downloader"] = m_download;
137 metadataMap["downloadargs"] = m_downloadargs.join(", ");
138 if (m_width == 0)
139 metadataMap["width"] = QString();
140 else
141 metadataMap["width"] = QString::number(m_width);
142 if (m_height == 0)
143 metadataMap["height"] = QString();
144 else
145 metadataMap["height"] = QString::number(m_height);
146 if (m_width == 0 || m_height == 0)
147 metadataMap["resolution"] = QString();
148 else
149 metadataMap["resolution"] = QString("%1x%2").arg(m_width).arg(m_height);
150 metadataMap["language"] = m_language;
151 metadataMap["countries"] = m_countries.join(", ");
152
153
154 if (m_season > 0 || m_episode > 0)
155 {
156 metadataMap["season"] = StringUtil::intToPaddedString(m_season, 1);
157 metadataMap["episode"] = StringUtil::intToPaddedString(m_episode, 1);
158 metadataMap["s##e##"] = metadataMap["s00e00"] = QString("s%1e%2")
161 metadataMap["##x##"] = metadataMap["00x00"] = QString("%1x%2")
164 }
165 else
166 {
167 metadataMap["season"] = QString();
168 metadataMap["episode"] = QString();
169 metadataMap["s##e##"] = metadataMap["s00e00"] = QString();
170 metadataMap["##x##"] = metadataMap["00x00"] = QString();
171 }
172}
173
174namespace
175{
176 QList<QDomNode> GetDirectChildrenNS(const QDomElement& elem,
177 const QString& ns, const QString& name)
178 {
179 QList<QDomNode> result;
180 QDomNodeList unf = elem.elementsByTagNameNS(ns, name);
181 result.reserve(unf.size());
182 for (int i = 0, size = unf.size(); i < size; ++i)
183 if (unf.at(i).parentNode() == elem)
184 result << unf.at(i);
185 return result;
186 }
187}
188
190{
192 {
193 QString m_url;
194 QString m_rating;
196 QString m_title;
198 QString m_keywords;
203 int m_ratingMin {0};
204 int m_ratingMax {0};
205 int m_views {0};
206 int m_favs {0};
207 QString m_tags;
208 QList<MRSSThumbnail> m_thumbnails;
209 QList<MRSSCredit> m_credits;
210 QList<MRSSComment> m_comments;
211 QList<MRSSPeerLink> m_peerLinks;
212 QList<MRSSScene> m_scenes;
213
215
220 {
221 if (!child.m_url.isEmpty())
222 m_url = child.m_url;
223 if (!child.m_rating.isEmpty())
224 m_rating = child.m_rating;
225 if (!child.m_ratingScheme.isEmpty())
227 if (!child.m_title.isEmpty())
228 m_title = child.m_title;
229 if (!child.m_description.isEmpty())
231 if (!child.m_keywords.isEmpty())
232 m_keywords = child.m_keywords;
233 if (!child.m_copyrightUrl.isEmpty())
235 if (!child.m_copyrightText.isEmpty())
237 if (child.m_ratingAverage != 0)
239 if (child.m_ratingCount != 0)
241 if (child.m_ratingMin != 0)
242 m_ratingMin = child.m_ratingMin;
243 if (child.m_ratingMax != 0)
244 m_ratingMax = child.m_ratingMax;
245 if (child.m_views != 0)
246 m_views = child.m_views;
247 if (child.m_favs != 0)
248 m_favs = child.m_favs;
249 if (!child.m_tags.isEmpty())
250 m_tags = child.m_tags;
251
252 m_thumbnails += child.m_thumbnails;
253 m_credits += child.m_credits;
254 m_comments += child.m_comments;
255 m_peerLinks += child.m_peerLinks;
256 m_scenes += child.m_scenes;
257 return *this;
258 }
259 };
260
261
262public:
263 MRSSParser() = default;
264
265 QList<MRSSEntry> operator() (const QDomElement& item)
266 {
267 QList<MRSSEntry> result;
268
269 QDomNodeList groups = item.elementsByTagNameNS(Parse::kMediaRSS,
270 "group");
271
272 for (int i = 0; i < groups.size(); ++i)
273 result += CollectChildren(groups.at(i).toElement());
274
275 result += CollectChildren(item);
276
277 return result;
278 }
279
280private:
281
282 static QList<MRSSEntry> CollectChildren(const QDomElement& holder)
283 {
284 QList<MRSSEntry> result;
285 QDomNodeList entries = holder.elementsByTagNameNS(Parse::kMediaRSS,
286 "content");
287
288 result.reserve(entries.size());
289 for (int i = 0; i < entries.size(); ++i)
290 {
291 MRSSEntry entry;
292
293 QDomElement en = entries.at(i).toElement();
295
296 if (en.hasAttribute("url"))
297 entry.URL = en.attribute("url");
298 else
299 entry.URL = d.m_url;
300
301 entry.Size = en.attribute("fileSize").toInt();
302 entry.Type = en.attribute("type");
303 entry.Medium = en.attribute("medium");
304 entry.IsDefault = (en.attribute("isDefault") == "true");
305 entry.Expression = en.attribute("expression");
306 if (entry.Expression.isEmpty())
307 entry.Expression = "full";
308 entry.Bitrate = en.attribute("bitrate").toInt();
309 entry.Framerate = en.attribute("framerate").toDouble();
310 entry.SamplingRate = en.attribute("samplingrate").toDouble();
311 entry.Channels = en.attribute("channels").toInt();
312 if (!en.attribute("duration").isNull())
313 entry.Duration = en.attribute("duration").toInt();
314 else
315 entry.Duration = 0;
316 if (!en.attribute("width").isNull())
317 entry.Width = en.attribute("width").toInt();
318 else
319 entry.Width = 0;
320 if (!en.attribute("height").isNull())
321 entry.Height = en.attribute("height").toInt();
322 else
323 entry.Height = 0;
324 if (!en.attribute("lang").isNull())
325 entry.Lang = en.attribute("lang");
326 else
327 entry.Lang = QString();
328
329 if (!en.attribute("rating").isNull())
330 entry.Rating = d.m_rating;
331 else
332 entry.Rating = QString();
333 entry.RatingScheme = d.m_ratingScheme;
334 entry.Title = d.m_title;
335 entry.Description = d.m_description;
336 entry.Keywords = d.m_keywords;
337 entry.CopyrightURL = d.m_copyrightUrl;
338 entry.CopyrightText = d.m_copyrightText;
339 if (d.m_ratingAverage != 0)
340 entry.RatingAverage = d.m_ratingAverage;
341 else
342 entry.RatingAverage = 0;
343 entry.RatingCount = d.m_ratingCount;
344 entry.RatingMin = d.m_ratingMin;
345 entry.RatingMax = d.m_ratingMax;
346 entry.Views = d.m_views;
347 entry.Favs = d.m_favs;
348 entry.Tags = d.m_tags;
349 entry.Thumbnails = d.m_thumbnails;
350 entry.Credits = d.m_credits;
351 entry.Comments = d.m_comments;
352 entry.PeerLinks = d.m_peerLinks;
353 entry.Scenes = d.m_scenes;
354
355 result << entry;
356 }
357 return result;
358 }
359
360 static ArbitraryLocatedData GetArbitraryLocatedDataFor(const QDomElement& holder)
361 {
363
364 QList<QDomElement> parents;
365 QDomElement parent = holder;
366 while (!parent.isNull())
367 {
368 parents.prepend(parent);
369 parent = parent.parentNode().toElement();
370 }
371
372 for (const auto& p : std::as_const(parents))
374
375 return result;
376 }
377
378 static QString GetURL(const QDomElement& element)
379 {
380 QList<QDomNode> elems = GetDirectChildrenNS(element, Parse::kMediaRSS,
381 "player");
382 if (elems.empty())
383 return {};
384
385 return elems.at(0).toElement().attribute("url");
386 }
387
388 static QString GetTitle(const QDomElement& element)
389 {
390 QList<QDomNode> elems = GetDirectChildrenNS(element, Parse::kMediaRSS,
391 "title");
392
393 if (elems.empty())
394 return {};
395
396 QDomElement telem = elems.at(0).toElement();
397 return Parse::UnescapeHTML(telem.text());
398 }
399
400 static QString GetDescription(const QDomElement& element)
401 {
402 QList<QDomNode> elems = GetDirectChildrenNS(element, Parse::kMediaRSS,
403 "description");
404
405 if (elems.empty())
406 return {};
407
408 QDomElement telem = elems.at(0).toElement();
409 return Parse::UnescapeHTML(telem.text());
410 }
411
412 static QString GetKeywords(const QDomElement& element)
413 {
414 QList<QDomNode> elems = GetDirectChildrenNS(element, Parse::kMediaRSS,
415 "keywords");
416
417 if (elems.empty())
418 return {};
419
420 QDomElement telem = elems.at(0).toElement();
421 return telem.text();
422 }
423
424 static int GetInt(const QDomElement& elem, const QString& attrname)
425 {
426 if (elem.hasAttribute(attrname))
427 {
428 bool ok = false;
429 int result = elem.attribute(attrname).toInt(&ok);
430 if (ok)
431 return result;
432 }
433 return int();
434 }
435
436 static QList<MRSSThumbnail> GetThumbnails(const QDomElement& element)
437 {
438 QList<MRSSThumbnail> result;
439 QList<QDomNode> thumbs = GetDirectChildrenNS(element, Parse::kMediaRSS,
440 "thumbnail");
441 result.reserve(thumbs.size());
442 for (const auto& dom : std::as_const(thumbs))
443 {
444 QDomElement thumbNode = dom.toElement();
445 int widthOpt = GetInt(thumbNode, "width");
446 int width = widthOpt ? widthOpt : 0;
447 int heightOpt = GetInt(thumbNode, "height");
448 int height = heightOpt ? heightOpt : 0;
449 MRSSThumbnail thumb =
450 {
451 .URL=thumbNode.attribute("url"),
452 .Width=width,
453 .Height=height,
454 .Time=thumbNode.attribute("time")
455 };
456 result << thumb;
457 }
458 return result;
459 }
460
461 static QList<MRSSCredit> GetCredits(const QDomElement& element)
462 {
463 QList<MRSSCredit> result;
464 QList<QDomNode> credits = GetDirectChildrenNS(element, Parse::kMediaRSS,
465 "credit");
466
467 result.reserve(credits.size());
468 for (const auto& dom : std::as_const(credits))
469 {
470 QDomElement creditNode = dom.toElement();
471 if (!creditNode.hasAttribute("role"))
472 continue;
473 MRSSCredit credit =
474 {
475 .Role=creditNode.attribute("role"),
476 .Who=creditNode.text()
477 };
478 result << credit;
479 }
480 return result;
481 }
482
483 static QList<MRSSComment> GetComments(const QDomElement& element)
484 {
485 QList<MRSSComment> result;
486 QList<QDomNode> commParents = GetDirectChildrenNS(element, Parse::kMediaRSS,
487 "comments");
488
489 if (!commParents.empty())
490 {
491 QDomNodeList comments = commParents.at(0).toElement()
492 .elementsByTagNameNS(Parse::kMediaRSS,
493 "comment");
494 result.reserve(comments.size());
495 for (int i = 0; i < comments.size(); ++i)
496 {
497 MRSSComment comment =
498 {
499 .Type=QObject::tr("Comments"),
500 .Comment=comments.at(i).toElement().text()
501 };
502 result << comment;
503 }
504 }
505
506 QList<QDomNode> respParents = GetDirectChildrenNS(element, Parse::kMediaRSS,
507 "responses");
508
509 if (!respParents.empty())
510 {
511 QDomNodeList responses = respParents.at(0).toElement()
512 .elementsByTagNameNS(Parse::kMediaRSS,
513 "response");
514#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
515 result.reserve(result.capacity() + responses.size());
516#endif
517 for (int i = 0; i < responses.size(); ++i)
518 {
519 MRSSComment comment =
520 {
521 .Type=QObject::tr("Responses"),
522 .Comment=responses.at(i).toElement().text()
523 };
524 result << comment;
525 }
526 }
527
528 QList<QDomNode> backParents = GetDirectChildrenNS(element, Parse::kMediaRSS,
529 "backLinks");
530
531 if (!backParents.empty())
532 {
533 QDomNodeList backlinks = backParents.at(0).toElement()
534 .elementsByTagNameNS(Parse::kMediaRSS,
535 "backLink");
536#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
537 result.reserve(result.capacity() + backlinks.size());
538#endif
539 for (int i = 0; i < backlinks.size(); ++i)
540 {
541 MRSSComment comment =
542 {
543 .Type=QObject::tr("Backlinks"),
544 .Comment=backlinks.at(i).toElement().text()
545 };
546 result << comment;
547 }
548 }
549 return result;
550 }
551
552 static QList<MRSSPeerLink> GetPeerLinks(const QDomElement& element)
553 {
554 QList<MRSSPeerLink> result;
555 QList<QDomNode> links = GetDirectChildrenNS(element, Parse::kMediaRSS,
556 "peerLink");
557
558 result.reserve(links.size());
559 for (const auto& dom : std::as_const(links))
560 {
561 QDomElement linkNode = dom.toElement();
562 MRSSPeerLink pl =
563 {
564 .Type=linkNode.attribute("type"),
565 .Link=linkNode.attribute("href")
566 };
567 result << pl;
568 }
569 return result;
570 }
571
572 static QList<MRSSScene> GetScenes(const QDomElement& element)
573 {
574 QList<MRSSScene> result;
575 QList<QDomNode> scenesNode = GetDirectChildrenNS(element, Parse::kMediaRSS,
576 "scenes");
577
578 if (!scenesNode.empty())
579 {
580 QDomNodeList scenesNodes = scenesNode.at(0).toElement()
581 .elementsByTagNameNS(Parse::kMediaRSS, "scene");
582
583 result.reserve(scenesNodes.size());
584 for (int i = 0; i < scenesNodes.size(); ++i)
585 {
586 QDomElement sceneNode = scenesNodes.at(i).toElement();
587 MRSSScene scene =
588 {
589 .Title=sceneNode.firstChildElement("sceneTitle").text(),
590 .Description=sceneNode.firstChildElement("sceneDescription").text(),
591 .StartTime=sceneNode.firstChildElement("sceneStartTime").text(),
592 .EndTime=sceneNode.firstChildElement("sceneEndTime").text()
593 };
594 result << scene;
595 }
596 }
597 return result;
598 }
599
600 static ArbitraryLocatedData CollectArbitraryLocatedData(const QDomElement& element)
601 {
602
603 QString rating;
604 QString rscheme;
605 {
606 QList<QDomNode> elems = GetDirectChildrenNS(element, Parse::kMediaRSS,
607 "rating");
608
609 if (!elems.empty())
610 {
611 QDomElement relem = elems.at(0).toElement();
612 rating = relem.text();
613 if (relem.hasAttribute("scheme"))
614 rscheme = relem.attribute("scheme");
615 else
616 rscheme = "urn:simple";
617 }
618 }
619
620 QString curl;
621 QString ctext;
622 {
623 QList<QDomNode> elems = GetDirectChildrenNS(element, Parse::kMediaRSS,
624 "copyright");
625
626 if (!elems.empty())
627 {
628 QDomElement celem = elems.at(0).toElement();
629 ctext = celem.text();
630 if (celem.hasAttribute("url"))
631 curl = celem.attribute("url");
632 }
633 }
634
635 int raverage = 0;
636 int rcount = 0;
637 int rmin = 0;
638 int rmax = 0;
639 int views = 0;
640 int favs = 0;
641 QString tags;
642 {
643 QList<QDomNode> comms = GetDirectChildrenNS(element, Parse::kMediaRSS,
644 "community");
645 if (!comms.empty())
646 {
647 QDomElement comm = comms.at(0).toElement();
648 QDomNodeList stars = comm.elementsByTagNameNS(Parse::kMediaRSS,
649 "starRating");
650 if (stars.size())
651 {
652 QDomElement ratingDom = stars.at(0).toElement();
653 raverage = GetInt(ratingDom, "average");
654 rcount = GetInt(ratingDom, "count");
655 rmin = GetInt(ratingDom, "min");
656 rmax = GetInt(ratingDom, "max");
657 }
658
659 QDomNodeList stats = comm.elementsByTagNameNS(Parse::kMediaRSS,
660 "statistics");
661 if (stats.size())
662 {
663 QDomElement stat = stats.at(0).toElement();
664 views = GetInt(stat, "views");
665 favs = GetInt(stat, "favorites");
666 }
667
668 QDomNodeList tagsNode = comm.elementsByTagNameNS(Parse::kMediaRSS,
669 "tags");
670 if (tagsNode.size())
671 {
672 QDomElement tag = tagsNode.at(0).toElement();
673 tags = tag.text();
674 }
675 }
676 }
677
679 result.m_url = GetURL(element);
680 result.m_rating = rating;
681 result.m_ratingScheme = rscheme;
682 result.m_title = GetTitle(element);
683 result.m_description = GetDescription(element);
684 result.m_keywords = GetKeywords(element);
685 result.m_copyrightUrl = curl;
686 result.m_copyrightText = ctext;
687 result.m_ratingAverage = raverage;
688 result.m_ratingCount = rcount;
689 result.m_ratingMin = rmin;
690 result.m_ratingMax = rmax;
691 result.m_views = views;
692 result.m_favs = favs;
693 result.m_tags = tags;
694 result.m_thumbnails = GetThumbnails(element);
695 result.m_credits = GetCredits(element);
696 result.m_comments = GetComments(element);
697 result.m_peerLinks = GetPeerLinks(element);
698 result.m_scenes = GetScenes(element);
699
700 return result;
701 }
702};
703
704
705//========================================================================================
706// Search Construction, Destruction
707//========================================================================================
708
709const QString Parse::kDC = "http://purl.org/dc/elements/1.1/";
710const QString Parse::kWFW = "http://wellformedweb.org/CommentAPI/";
711const QString Parse::kAtom = "http://www.w3.org/2005/Atom";
712const QString Parse::kRDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
713const QString Parse::kSlash = "http://purl.org/rss/1.0/modules/slash/";
714const QString Parse::kEnc = "http://purl.oclc.org/net/rss_2.0/enc#";
715const QString Parse::kITunes = "http://www.itunes.com/dtds/podcast-1.0.dtd";
716const QString Parse::kGeoRSSSimple = "http://www.georss.org/georss";
717const QString Parse::kGeoRSSW3 = "http://www.w3.org/2003/01/geo/wgs84_pos#";
718const QString Parse::kMediaRSS = "http://search.yahoo.com/mrss/";
719const QString Parse::kMythRSS = "http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format";
720
721QMap<QString, int> Parse::m_timezoneOffsets {
722 { "EDT" , -4 },
723 { "EST" , -5 }
724};
725
726ResultItem::resultList Parse::parseRSS(const QDomDocument& domDoc)
727{
729
730 QString document = domDoc.toString();
731 LOG(VB_GENERAL, LOG_DEBUG, "Will Be Parsing: " + document);
732
733 QDomElement root = domDoc.documentElement();
734 QDomElement channel = root.firstChildElement("channel");
735 while (!channel.isNull())
736 {
737 QDomElement item = channel.firstChildElement("item");
738 while (!item.isNull())
739 {
740 vList.append(ParseItem(item));
741 item = item.nextSiblingElement("item");
742 }
743 channel = channel.nextSiblingElement("channel");
744 }
745
746 return vList;
747}
748
749ResultItem* Parse::ParseItem(const QDomElement& item)
750{
751 QString title("");
752 QString subtitle("");
753 QString description("");
754 QString url("");
755 QString author("");
756 QString duration("");
757 QString rating("");
758 QString thumbnail("");
759 QString mediaURL("");
760 QString player("");
761 QString language("");
762 QString download("");
763 off_t filesize = 0;
764 uint width = 0;
765 uint height = 0;
766 uint season = 0;
767 uint episode = 0;
768 QDateTime date;
769 QStringList playerargs;
770 QStringList downloadargs;
771 QStringList countries;
772 bool downloadable = true;
773 bool customhtml = false;
774
775 // Get the title of the article/video
776 title = item.firstChildElement("title").text();
777 title = UnescapeHTML(title);
778 if (title.isEmpty())
779 title = "";
780
781 // Get the subtitle of this item.
782 QDomNodeList subt = item.elementsByTagNameNS(kMythRSS, "subtitle");
783 if (subt.size())
784 {
785 subtitle = subt.at(0).toElement().text();
786 }
787
788 // Parse the description of the article/video
789 QDomElement descriptiontemp = item.firstChildElement("description");
790 if (!descriptiontemp.isNull())
791 description = descriptiontemp.text();
792 if (description.isEmpty())
793 {
794 QDomNodeList nodes = item.elementsByTagNameNS(kITunes, "summary");
795 if (nodes.size())
796 description = nodes.at(0).toElement().text();
797 }
798 // Unescape and remove HTML tags from the description.
799 if (description.isEmpty())
800 description = "";
801 else
802 description = UnescapeHTML(description);
803
804 // Get the link (web playable)
805 url = item.firstChildElement("link").text();
806
807 // Parse the item author
808 QDomElement authortemp = item.firstChildElement("author");
809 if (!authortemp.isNull())
810 author = authortemp.text();
811 if (author.isEmpty())
812 author = GetAuthor(item);
813
814 // Turn the RFC-822 pubdate into a QDateTime
815 date = RFC822TimeToQDateTime(item.firstChildElement("pubDate").text());
816 if (!date.isValid() || date.isNull())
817 date = GetDCDateTime(item);
818 if (!date.isValid() || date.isNull())
819 date = MythDate::current();
820
821 // Parse the insane iTunes duration (HH:MM:SS or H:MM:SS or MM:SS or M:SS or SS)
822 QDomNodeList dur = item.elementsByTagNameNS(kITunes, "duration");
823 if (dur.size())
824 {
825 QString itunestime = dur.at(0).toElement().text();
826 QString dateformat;
827
828 if (itunestime.size() == 8)
829 dateformat = "hh:mm:ss";
830 else if (itunestime.size() == 7)
831 dateformat = "h:mm:ss";
832 else if (itunestime.size() == 5)
833 dateformat = "mm:ss";
834 else if (itunestime.size() == 4)
835 dateformat = "m:ss";
836 else if (itunestime.size() == 2)
837 dateformat = "ss";
838 else
839 duration = "0";
840
841 if (!dateformat.isNull())
842 {
843 QTime itime = QTime::fromString(itunestime, dateformat);
844 if (itime.isValid())
845 {
846 int seconds = itime.second() + (itime.minute() * 60) + (itime.hour() * 3600);
847 duration = QString::number(seconds);
848 }
849 }
850 }
851
852 // Get the rating
853 QDomElement ratingtemp = item.firstChildElement("rating");
854 if (!ratingtemp.isNull())
855 rating = ratingtemp.text();
856
857 // Get the external player binary
858 QDomElement playertemp = item.firstChildElement("player");
859 if (!playertemp.isNull())
860 player = playertemp.text();
861
862 // Get the arguments to pass to the external player
863 QDomElement playerargstemp = item.firstChildElement("playerargs");
864 if (!playerargstemp.isNull())
865 playerargs = playerargstemp.text().split(" ");
866
867 // Get the external downloader binary/script
868 QDomElement downloadtemp = item.firstChildElement("download");
869 if (!downloadtemp.isNull())
870 download = downloadtemp.text();
871
872 // Get the arguments to pass to the external downloader
873 QDomElement downloadargstemp = item.firstChildElement("downloadargs");
874 if (!downloadargstemp.isNull())
875 downloadargs = downloadargstemp.text().split(" ");
876
877 // Get the countries in which this item is playable
878 QDomNodeList cties = item.elementsByTagNameNS(kMythRSS, "country");
879 if (cties.size())
880 {
881 int i = 0;
882 while (i < cties.size())
883 {
884 countries.append(cties.at(i).toElement().text());
885 i++;
886 }
887 }
888
889 // Get the season number of this item.
890 QDomNodeList seas = item.elementsByTagNameNS(kMythRSS, "season");
891 if (seas.size())
892 {
893 season = seas.at(0).toElement().text().toUInt();
894 }
895
896 // Get the Episode number of this item.
897 QDomNodeList ep = item.elementsByTagNameNS(kMythRSS, "episode");
898 if (ep.size())
899 {
900 episode = ep.at(0).toElement().text().toUInt();
901 }
902
903 // Does this grabber return custom HTML?
904 QDomNodeList html = item.elementsByTagNameNS(kMythRSS, "customhtml");
905 if (html.size())
906 {
907 QString htmlstring = html.at(0).toElement().text();
908 if (htmlstring.contains("true", Qt::CaseInsensitive) || htmlstring == "1" ||
909 htmlstring.contains("yes", Qt::CaseInsensitive))
910 customhtml = true;
911 }
912
913 QList<MRSSEntry> enclosures = GetMediaRSS(item);
914
915 if (!enclosures.empty())
916 {
917 MRSSEntry media = enclosures.takeAt(0);
918
919 QList<MRSSThumbnail> thumbs = media.Thumbnails;
920 if (!thumbs.empty())
921 {
922 MRSSThumbnail thumb = thumbs.takeAt(0);
923 thumbnail = thumb.URL;
924 }
925
926 mediaURL = media.URL;
927
928 width = media.Width;
929 height = media.Height;
930 language = media.Lang;
931
932 if (duration.isEmpty())
933 duration = QString::number(media.Duration);
934
935 if (filesize == 0)
936 filesize = media.Size;
937
938 if (rating.isEmpty())
939 rating = QString::number(media.RatingAverage);
940 }
941 if (mediaURL.isEmpty())
942 {
943 QList<Enclosure> stdEnc = GetEnclosures(item);
944
945 if (!stdEnc.empty())
946 {
947 Enclosure e = stdEnc.takeAt(0);
948
949 mediaURL = e.URL;
950
951 if (filesize == 0)
952 filesize = e.Length;
953 }
954 }
955
956 if (mediaURL.isNull() || mediaURL == url)
957 downloadable = false;
958
959 std::shared_ptr<MythSortHelper>sh = getMythSortHelper();
960 return(new ResultItem(title, sh->doTitle(title),
961 subtitle, sh->doTitle(subtitle), description,
962 url, thumbnail, mediaURL, author, date, duration,
963 rating, filesize, player, playerargs,
964 download, downloadargs, width, height,
965 language, downloadable, countries, season,
966 episode, customhtml));
967}
968
969QString Parse::GetLink(const QDomElement& parent)
970{
971 QString result;
972 QDomElement link = parent.firstChildElement("link");
973 while(!link.isNull())
974 {
975 if (!link.hasAttribute("rel") || link.attribute("rel") == "alternate")
976 {
977 if (!link.hasAttribute("href"))
978 result = link.text();
979 else
980 result = link.attribute("href");
981 break;
982 }
983 link = link.nextSiblingElement("link");
984 }
985 return result;
986}
987
988QString Parse::GetAuthor(const QDomElement& parent)
989{
990 QString result("");
991 QDomNodeList nodes = parent.elementsByTagNameNS(kITunes,
992 "author");
993 if (nodes.size())
994 {
995 result = nodes.at(0).toElement().text();
996 return result;
997 }
998
999 nodes = parent.elementsByTagNameNS(kDC,
1000 "creator");
1001 if (nodes.size())
1002 {
1003 result = nodes.at(0).toElement().text();
1004 return result;
1005 }
1006
1007 return result;
1008}
1009
1010QString Parse::GetCommentsRSS(const QDomElement& parent)
1011{
1012 QString result;
1013 QDomNodeList nodes = parent.elementsByTagNameNS(kWFW,
1014 "commentRss");
1015 if (nodes.size())
1016 result = nodes.at(0).toElement().text();
1017 return result;
1018}
1019
1020QString Parse::GetCommentsLink(const QDomElement& parent)
1021{
1022 QString result;
1023 QDomNodeList nodes = parent.elementsByTagNameNS("", "comments");
1024 if (nodes.size())
1025 result = nodes.at(0).toElement().text();
1026 return result;
1027}
1028
1029QDateTime Parse::GetDCDateTime(const QDomElement& parent)
1030{
1031 QDomNodeList dates = parent.elementsByTagNameNS(kDC, "date");
1032 if (!dates.size())
1033 return {};
1034 return FromRFC3339(dates.at(0).toElement().text());
1035}
1036
1037QDateTime Parse::RFC822TimeToQDateTime(const QString& t)
1038{
1039 static const QRegularExpression kNonDigitRE { R"(\D)" };
1040
1041 if (t.size() < 20)
1042 return {};
1043
1044 QString time = t.simplified();
1045 short int hoursShift = 0;
1046 short int minutesShift = 0;
1047
1048 QStringList tmp = time.split(' ');
1049 if (tmp.isEmpty())
1050 return {};
1051 if (tmp.at(0).contains(kNonDigitRE))
1052 tmp.removeFirst();
1053 if (tmp.size() != 5)
1054 return {};
1055 QString tmpTimezone = tmp.takeAt(tmp.size() -1);
1056 if (tmpTimezone.size() == 5)
1057 {
1058 bool ok = false;
1059 int tz = tmpTimezone.toInt(&ok);
1060 if(ok)
1061 {
1062 hoursShift = tz / 100;
1063 minutesShift = tz % 100;
1064 }
1065 }
1066 else
1067 {
1068 hoursShift = m_timezoneOffsets.value(tmpTimezone, 0);
1069 }
1070
1071 if (tmp.at(0).size() == 1)
1072 tmp[0].prepend("0");
1073 tmp [1].truncate(3);
1074
1075 time = tmp.join(" ");
1076
1077 QDateTime result;
1078 if (tmp.at(2).size() == 4)
1079 result = QLocale::c().toDateTime(time, "dd MMM yyyy hh:mm:ss");
1080 else
1081 result = QLocale::c().toDateTime(time, "dd MMM yy hh:mm:ss");
1082 if (result.isNull() || !result.isValid())
1083 return {};
1084 result = result.addSecs((hoursShift * 3600 * (-1)) + (minutesShift * 60 * (-1)));
1085#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
1086 result.setTimeSpec(Qt::UTC);
1087#else
1088 result.setTimeZone(QTimeZone(QTimeZone::UTC));
1089#endif
1090 return result;
1091}
1092
1093QDateTime Parse::FromRFC3339(const QString& t)
1094{
1095 if (t.size() < 19)
1096 return {};
1097 QDateTime result = MythDate::fromString(t.left(19).toUpper());
1098 static const QRegularExpression fractionalSeconds { R"(\.(\d+))" };
1099 auto match = fractionalSeconds.match(t);
1100 if (match.hasMatch())
1101 {
1102 bool ok = false;
1103 int fractional = match.capturedView(1).toInt(&ok);
1104 if (ok)
1105 {
1106 if (fractional < 100)
1107 fractional *= 10;
1108 if (fractional <10)
1109 fractional *= 100;
1110 result = result.addMSecs(fractional);
1111 }
1112 }
1113 static const QRegularExpression timeZone { R"((\+|\-)(\d\d):(\d\d)$)" };
1114 match = timeZone.match(t);
1115 if (match.hasMatch())
1116 {
1117 short int multiplier = -1;
1118 if (match.captured(1) == "-")
1119 multiplier = 1;
1120 int hoursShift = match.capturedView(2).toInt();
1121 int minutesShift = match.capturedView(3).toInt();
1122 result = result.addSecs((hoursShift * 3600 * multiplier) + (minutesShift * 60 * multiplier));
1123 }
1124#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
1125 result.setTimeSpec(Qt::UTC);
1126#else
1127 result.setTimeZone(QTimeZone(QTimeZone::UTC));
1128#endif
1129 return result;
1130}
1131
1132QList<Enclosure> Parse::GetEnclosures(const QDomElement& entry)
1133{
1134 QList<Enclosure> result;
1135 QDomNodeList links = entry.elementsByTagName("enclosure");
1136 result.reserve(links.size());
1137 for (int i = 0; i < links.size(); ++i)
1138 {
1139 QDomElement link = links.at(i).toElement();
1140
1141 Enclosure e =
1142 {
1143 .URL=link.attribute("url"),
1144 .Type=link.attribute("type"),
1145 .Length=link.attribute("length", "-1").toLongLong(),
1146 .Lang=link.attribute("hreflang")
1147 };
1148
1149 result << e;
1150 }
1151 return result;
1152}
1153
1154QList<MRSSEntry> Parse::GetMediaRSS(const QDomElement& item)
1155{
1156 return MRSSParser() (item);
1157}
1158
1159QString Parse::UnescapeHTML(const QString& escaped)
1160{
1161 QString result = escaped;
1162 result.replace("&amp;", "&");
1163 result.replace("&lt;", "<");
1164 result.replace("&gt;", ">");
1165 result.replace("&apos;", "\'");
1166 result.replace("&rsquo;", "\'");
1167 result.replace("&#x2019;", "\'");
1168 result.replace("&quot;", "\"");
1169 result.replace("&#8230;",QChar(8230));
1170 result.replace("&#233;",QChar(233));
1171 result.replace("&mdash;", QChar(8212));
1172 result.replace("&nbsp;", " ");
1173 result.replace("&#160;", QChar(160));
1174 result.replace("&#225;", QChar(225));
1175 result.replace("&#8216;", QChar(8216));
1176 result.replace("&#8217;", QChar(8217));
1177 result.replace("&#039;", "\'");
1178 result.replace("&ndash;", QChar(8211));
1179 result.replace("&auml;", QChar(0x00e4));
1180 result.replace("&ouml;", QChar(0x00f6));
1181 result.replace("&uuml;", QChar(0x00fc));
1182 result.replace("&Auml;", QChar(0x00c4));
1183 result.replace("&Ouml;", QChar(0x00d6));
1184 result.replace("&Uuml;", QChar(0x00dc));
1185 result.replace("&szlig;", QChar(0x00df));
1186 result.replace("&euro;", "€");
1187 result.replace("&#8230;", "...");
1188 result.replace("&#x00AE;", QChar(0x00ae));
1189 result.replace("&#x201C;", QChar(0x201c));
1190 result.replace("&#x201D;", QChar(0x201d));
1191 result.replace("<p>", "\n");
1192
1193 static const QRegularExpression kStripHtmlRE {"<.*?>"};
1194 result.remove(kStripHtmlRE);
1195
1196 return result;
1197}
1198
1199#include "moc_rssparse.cpp"
static QString GetURL(const QDomElement &element)
Definition: rssparse.cpp:378
static ArbitraryLocatedData GetArbitraryLocatedDataFor(const QDomElement &holder)
Definition: rssparse.cpp:360
static QString GetKeywords(const QDomElement &element)
Definition: rssparse.cpp:412
static QList< MRSSThumbnail > GetThumbnails(const QDomElement &element)
Definition: rssparse.cpp:436
static QString GetTitle(const QDomElement &element)
Definition: rssparse.cpp:388
static ArbitraryLocatedData CollectArbitraryLocatedData(const QDomElement &element)
Definition: rssparse.cpp:600
static QList< MRSSCredit > GetCredits(const QDomElement &element)
Definition: rssparse.cpp:461
QList< MRSSEntry > operator()(const QDomElement &item)
Definition: rssparse.cpp:265
static int GetInt(const QDomElement &elem, const QString &attrname)
Definition: rssparse.cpp:424
MRSSParser()=default
static QString GetDescription(const QDomElement &element)
Definition: rssparse.cpp:400
static QList< MRSSPeerLink > GetPeerLinks(const QDomElement &element)
Definition: rssparse.cpp:552
static QList< MRSSEntry > CollectChildren(const QDomElement &holder)
Definition: rssparse.cpp:282
static QList< MRSSComment > GetComments(const QDomElement &element)
Definition: rssparse.cpp:483
static QList< MRSSScene > GetScenes(const QDomElement &element)
Definition: rssparse.cpp:572
static const QString kAtom
Definition: rssparse.h:218
static const QString kITunes
Definition: rssparse.h:222
static const QString kGeoRSSW3
Definition: rssparse.h:224
static const QString kDC
Definition: rssparse.h:216
static QString UnescapeHTML(const QString &escaped)
Definition: rssparse.cpp:1159
static QString GetAuthor(const QDomElement &parent)
Definition: rssparse.cpp:988
static const QString kGeoRSSSimple
Definition: rssparse.h:223
static QList< Enclosure > GetEnclosures(const QDomElement &entry)
Definition: rssparse.cpp:1132
static const QString kWFW
Definition: rssparse.h:217
static const QString kSlash
Definition: rssparse.h:220
static const QString kMediaRSS
Definition: rssparse.h:225
static QString GetCommentsRSS(const QDomElement &parent)
Definition: rssparse.cpp:1010
static ResultItem * ParseItem(const QDomElement &item)
Definition: rssparse.cpp:749
static QString GetCommentsLink(const QDomElement &parent)
Definition: rssparse.cpp:1020
static QDateTime FromRFC3339(const QString &t)
Definition: rssparse.cpp:1093
static ResultItem::resultList parseRSS(const QDomDocument &domDoc)
Definition: rssparse.cpp:726
static QMap< QString, int > m_timezoneOffsets
Definition: rssparse.h:213
friend class MRSSParser
Definition: rssparse.h:191
static QString GetLink(const QDomElement &parent)
Definition: rssparse.cpp:969
static const QString kRDF
Definition: rssparse.h:219
static QDateTime RFC822TimeToQDateTime(const QString &parent)
Definition: rssparse.cpp:1037
static QList< MRSSEntry > GetMediaRSS(const QDomElement &item)
Definition: rssparse.cpp:1154
static QDateTime GetDCDateTime(const QDomElement &parent)
Definition: rssparse.cpp:1029
static const QString kMythRSS
Definition: rssparse.h:226
static const QString kEnc
Definition: rssparse.h:221
ResultItem()=default
uint m_season
Definition: rssparse.h:183
QString m_language
Definition: rssparse.h:180
QString m_title
Definition: rssparse.h:161
QStringList m_playerargs
Definition: rssparse.h:175
QStringList m_downloadargs
Definition: rssparse.h:177
void toMap(InfoMap &metadataMap)
Definition: rssparse.cpp:70
QString m_thumbnail
Definition: rssparse.h:167
QString m_sortsubtitle
Definition: rssparse.h:164
uint m_height
Definition: rssparse.h:179
uint m_width
Definition: rssparse.h:178
void ensureSortFields(void)
Definition: rssparse.cpp:60
uint m_episode
Definition: rssparse.h:184
QString m_mediaURL
Definition: rssparse.h:168
bool m_customhtml
Definition: rssparse.h:185
QString m_desc
Definition: rssparse.h:165
off_t m_filesize
Definition: rssparse.h:173
QString m_rating
Definition: rssparse.h:172
QString m_time
Definition: rssparse.h:171
QStringList m_countries
Definition: rssparse.h:182
QString m_url
Definition: rssparse.h:166
QDateTime m_date
Definition: rssparse.h:170
bool m_downloadable
Definition: rssparse.h:181
QString m_subtitle
Definition: rssparse.h:163
QString m_player
Definition: rssparse.h:174
QString m_author
Definition: rssparse.h:169
QString m_sorttitle
Definition: rssparse.h:162
QList< ResultItem * > resultList
Definition: rssparse.h:114
QString m_download
Definition: rssparse.h:176
unsigned int uint
Definition: compat.h:60
static const iso6937table * d
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
std::shared_ptr< MythSortHelper > getMythSortHelper(void)
Get a pointer to the MythSortHelper singleton.
QHash< QString, QString > InfoMap
Definition: mythtypes.h:15
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
Definition: mythdate.cpp:93
@ kDateFull
Default local time.
Definition: mythdate.h:20
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
Definition: mythdate.cpp:39
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:15
QString intToPaddedString(int n, int width=2)
Creates a zero padded string representation of an integer.
Definition: stringutil.h:32
QList< QDomNode > GetDirectChildrenNS(const QDomElement &elem, const QString &ns, const QString &name)
Definition: rssparse.cpp:176
def rating(profile, smoonURL, gate)
Definition: scan.py:36
string dateformat
Definition: mythburn.py:168
Describes an enclosure associated with an item.
Definition: rssparse.h:30
QString URL
Definition: rssparse.h:31
qint64 Length
Definition: rssparse.h:33
QString Type
Definition: rssparse.h:53
QString Role
Definition: rssparse.h:47
int Channels
Definition: rssparse.h:82
int RatingAverage
Definition: rssparse.h:95
double Framerate
Definition: rssparse.h:80
QString Medium
Definition: rssparse.h:76
QString Description
Definition: rssparse.h:91
QString Title
Definition: rssparse.h:90
int Bitrate
Definition: rssparse.h:79
int RatingCount
Definition: rssparse.h:96
QString CopyrightText
Definition: rssparse.h:94
QList< MRSSThumbnail > Thumbnails
Definition: rssparse.h:102
QString Expression
Definition: rssparse.h:78
QList< MRSSPeerLink > PeerLinks
Definition: rssparse.h:105
QString RatingScheme
Definition: rssparse.h:89
QList< MRSSComment > Comments
Definition: rssparse.h:104
QString Tags
Definition: rssparse.h:101
int Favs
Definition: rssparse.h:100
QList< MRSSScene > Scenes
Definition: rssparse.h:106
int Width
Definition: rssparse.h:84
QList< MRSSCredit > Credits
Definition: rssparse.h:103
int Height
Definition: rssparse.h:85
QString CopyrightURL
Definition: rssparse.h:93
bool IsDefault
Definition: rssparse.h:77
int RatingMin
Definition: rssparse.h:97
QString URL
Definition: rssparse.h:73
int Views
Definition: rssparse.h:99
double SamplingRate
Definition: rssparse.h:81
int RatingMax
Definition: rssparse.h:98
QString Type
Definition: rssparse.h:75
int Duration
Definition: rssparse.h:83
qint64 Size
Definition: rssparse.h:74
QString Rating
Definition: rssparse.h:88
QString Keywords
Definition: rssparse.h:92
QString Lang
Definition: rssparse.h:86
QList< MRSSCredit > m_credits
Definition: rssparse.cpp:209
QList< MRSSPeerLink > m_peerLinks
Definition: rssparse.cpp:211
QList< MRSSThumbnail > m_thumbnails
Definition: rssparse.cpp:208
ArbitraryLocatedData & operator+=(const ArbitraryLocatedData &child)
Updates *this's fields according to the child.
Definition: rssparse.cpp:219
QList< MRSSScene > m_scenes
Definition: rssparse.cpp:212
QList< MRSSComment > m_comments
Definition: rssparse.cpp:210
QString Title
Definition: rssparse.h:65
QString URL
Definition: rssparse.h:39