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