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