1 /*
2  *  Copyright (C) 2005-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #include "VideoInfoTag.h"
10 
11 #include "ServiceBroker.h"
12 #include "TextureDatabase.h"
13 #include "guilib/LocalizeStrings.h"
14 #include "settings/AdvancedSettings.h"
15 #include "settings/SettingsComponent.h"
16 #include "utils/Archive.h"
17 #include "utils/StringUtils.h"
18 #include "utils/Variant.h"
19 #include "utils/XMLUtils.h"
20 #include "utils/log.h"
21 
22 #include <algorithm>
23 #include <sstream>
24 #include <string>
25 #include <vector>
26 
Reset()27 void CVideoInfoTag::Reset()
28 {
29   m_director.clear();
30   m_writingCredits.clear();
31   m_genre.clear();
32   m_country.clear();
33   m_strTagLine.clear();
34   m_strPlotOutline.clear();
35   m_strPlot.clear();
36   m_strPictureURL.Clear();
37   m_strTitle.clear();
38   m_strShowTitle.clear();
39   m_strOriginalTitle.clear();
40   m_strSortTitle.clear();
41   m_cast.clear();
42   m_set.title.clear();
43   m_set.id = -1;
44   m_set.overview.clear();
45   m_tags.clear();
46   m_strFile.clear();
47   m_strPath.clear();
48   m_strMPAARating.clear();
49   m_strFileNameAndPath.clear();
50   m_premiered.Reset();
51   m_bHasPremiered = false;
52   m_strStatus.clear();
53   m_strProductionCode.clear();
54   m_firstAired.Reset();
55   m_studio.clear();
56   m_strAlbum.clear();
57   m_artist.clear();
58   m_strTrailer.clear();
59   m_iTop250 = 0;
60   m_year = -1;
61   m_iSeason = -1;
62   m_iEpisode = -1;
63   m_iIdUniqueID = -1;
64   m_uniqueIDs.clear();
65   m_strDefaultUniqueID = "unknown";
66   m_iSpecialSortSeason = -1;
67   m_iSpecialSortEpisode = -1;
68   m_strDefaultRating = "default";
69   m_iIdRating = -1;
70   m_ratings.clear();
71   m_iUserRating = 0;
72   m_iDbId = -1;
73   m_iFileId = -1;
74   m_iBookmarkId = -1;
75   m_iTrack = -1;
76   m_fanart.m_xml.clear();
77   m_duration = 0;
78   m_lastPlayed.Reset();
79   m_showLink.clear();
80   m_namedSeasons.clear();
81   m_streamDetails.Reset();
82   m_playCount = PLAYCOUNT_NOT_SET;
83   m_EpBookmark.Reset();
84   m_EpBookmark.type = CBookmark::EPISODE;
85   m_basePath.clear();
86   m_parentPathID = -1;
87   m_resumePoint.Reset();
88   m_resumePoint.type = CBookmark::RESUME;
89   m_iIdShow = -1;
90   m_iIdSeason = -1;
91   m_dateAdded.Reset();
92   m_type.clear();
93   m_relevance = -1;
94   m_parsedDetails = 0;
95   m_coverArt.clear();
96 }
97 
Save(TiXmlNode * node,const std::string & tag,bool savePathInfo,const TiXmlElement * additionalNode)98 bool CVideoInfoTag::Save(TiXmlNode *node, const std::string &tag, bool savePathInfo, const TiXmlElement *additionalNode)
99 {
100   if (!node) return false;
101 
102   // we start with a <tag> tag
103   TiXmlElement movieElement(tag.c_str());
104   TiXmlNode *movie = node->InsertEndChild(movieElement);
105 
106   if (!movie) return false;
107 
108   XMLUtils::SetString(movie, "title", m_strTitle);
109   if (!m_strOriginalTitle.empty())
110     XMLUtils::SetString(movie, "originaltitle", m_strOriginalTitle);
111   if (!m_strShowTitle.empty())
112     XMLUtils::SetString(movie, "showtitle", m_strShowTitle);
113   if (!m_strSortTitle.empty())
114     XMLUtils::SetString(movie, "sorttitle", m_strSortTitle);
115   if (!m_ratings.empty())
116   {
117     TiXmlElement ratings("ratings");
118     for (const auto& it : m_ratings)
119     {
120       TiXmlElement rating("rating");
121       rating.SetAttribute("name", it.first.c_str());
122       XMLUtils::SetFloat(&rating, "value", it.second.rating);
123       XMLUtils::SetInt(&rating, "votes", it.second.votes);
124       rating.SetAttribute("max", 10);
125       if (it.first == m_strDefaultRating)
126         rating.SetAttribute("default", "true");
127       ratings.InsertEndChild(rating);
128     }
129     movie->InsertEndChild(ratings);
130   }
131   XMLUtils::SetInt(movie, "userrating", m_iUserRating);
132 
133   if (m_EpBookmark.timeInSeconds > 0)
134   {
135     TiXmlElement epbookmark("episodebookmark");
136     XMLUtils::SetDouble(&epbookmark, "position", m_EpBookmark.timeInSeconds);
137     if (!m_EpBookmark.playerState.empty())
138     {
139       TiXmlElement playerstate("playerstate");
140       CXBMCTinyXML doc;
141       doc.Parse(m_EpBookmark.playerState);
142       playerstate.InsertEndChild(*doc.RootElement());
143       epbookmark.InsertEndChild(playerstate);
144     }
145     movie->InsertEndChild(epbookmark);
146   }
147 
148   XMLUtils::SetInt(movie, "top250", m_iTop250);
149   if (tag == "episodedetails" || tag == "tvshow")
150   {
151     XMLUtils::SetInt(movie, "season", m_iSeason);
152     XMLUtils::SetInt(movie, "episode", m_iEpisode);
153     XMLUtils::SetInt(movie, "displayseason",m_iSpecialSortSeason);
154     XMLUtils::SetInt(movie, "displayepisode",m_iSpecialSortEpisode);
155   }
156   if (tag == "musicvideo")
157   {
158     XMLUtils::SetInt(movie, "track", m_iTrack);
159     XMLUtils::SetString(movie, "album", m_strAlbum);
160   }
161   XMLUtils::SetString(movie, "outline", m_strPlotOutline);
162   XMLUtils::SetString(movie, "plot", m_strPlot);
163   XMLUtils::SetString(movie, "tagline", m_strTagLine);
164   XMLUtils::SetInt(movie, "runtime", GetDuration() / 60);
165   if (m_strPictureURL.HasData())
166   {
167     CXBMCTinyXML doc;
168     doc.Parse(m_strPictureURL.GetData());
169     const TiXmlNode* thumb = doc.FirstChild("thumb");
170     while (thumb)
171     {
172       movie->InsertEndChild(*thumb);
173       thumb = thumb->NextSibling("thumb");
174     }
175   }
176   if (m_fanart.m_xml.size())
177   {
178     CXBMCTinyXML doc;
179     doc.Parse(m_fanart.m_xml);
180     movie->InsertEndChild(*doc.RootElement());
181   }
182   XMLUtils::SetString(movie, "mpaa", m_strMPAARating);
183   XMLUtils::SetInt(movie, "playcount", GetPlayCount());
184   XMLUtils::SetDate(movie, "lastplayed", m_lastPlayed);
185   if (savePathInfo)
186   {
187     XMLUtils::SetString(movie, "file", m_strFile);
188     XMLUtils::SetString(movie, "path", m_strPath);
189     XMLUtils::SetString(movie, "filenameandpath", m_strFileNameAndPath);
190     XMLUtils::SetString(movie, "basepath", m_basePath);
191   }
192   if (!m_strEpisodeGuide.empty())
193   {
194     CXBMCTinyXML doc;
195     doc.Parse(m_strEpisodeGuide);
196     if (doc.RootElement())
197       movie->InsertEndChild(*doc.RootElement());
198     else
199       XMLUtils::SetString(movie, "episodeguide", m_strEpisodeGuide);
200   }
201 
202   XMLUtils::SetString(movie, "id", GetUniqueID());
203   for (const auto& uniqueid : m_uniqueIDs)
204   {
205     TiXmlElement uniqueID("uniqueid");
206     uniqueID.SetAttribute("type", uniqueid.first);
207     if (uniqueid.first == m_strDefaultUniqueID)
208       uniqueID.SetAttribute("default", "true");
209     TiXmlText value(uniqueid.second);
210     uniqueID.InsertEndChild(value);
211 
212     movie->InsertEndChild(uniqueID);
213   }
214   XMLUtils::SetStringArray(movie, "genre", m_genre);
215   XMLUtils::SetStringArray(movie, "country", m_country);
216   if (!m_set.title.empty())
217   {
218     TiXmlElement set("set");
219     XMLUtils::SetString(&set, "name", m_set.title);
220     if (!m_set.overview.empty())
221       XMLUtils::SetString(&set, "overview", m_set.overview);
222     movie->InsertEndChild(set);
223   }
224   XMLUtils::SetStringArray(movie, "tag", m_tags);
225   XMLUtils::SetStringArray(movie, "credits", m_writingCredits);
226   XMLUtils::SetStringArray(movie, "director", m_director);
227   if (HasPremiered())
228     XMLUtils::SetDate(movie, "premiered", m_premiered);
229   if (HasYear())
230     XMLUtils::SetInt(movie, "year", GetYear());
231   XMLUtils::SetString(movie, "status", m_strStatus);
232   XMLUtils::SetString(movie, "code", m_strProductionCode);
233   XMLUtils::SetDate(movie, "aired", m_firstAired);
234   XMLUtils::SetStringArray(movie, "studio", m_studio);
235   XMLUtils::SetString(movie, "trailer", m_strTrailer);
236 
237   if (m_streamDetails.HasItems())
238   {
239     // it goes fileinfo/streamdetails/[video|audio|subtitle]
240     TiXmlElement fileinfo("fileinfo");
241     TiXmlElement streamdetails("streamdetails");
242     for (int iStream=1; iStream<=m_streamDetails.GetVideoStreamCount(); iStream++)
243     {
244       TiXmlElement stream("video");
245       XMLUtils::SetString(&stream, "codec", m_streamDetails.GetVideoCodec(iStream));
246       XMLUtils::SetFloat(&stream, "aspect", m_streamDetails.GetVideoAspect(iStream));
247       XMLUtils::SetInt(&stream, "width", m_streamDetails.GetVideoWidth(iStream));
248       XMLUtils::SetInt(&stream, "height", m_streamDetails.GetVideoHeight(iStream));
249       XMLUtils::SetInt(&stream, "durationinseconds", m_streamDetails.GetVideoDuration(iStream));
250       XMLUtils::SetString(&stream, "stereomode", m_streamDetails.GetStereoMode(iStream));
251       streamdetails.InsertEndChild(stream);
252     }
253     for (int iStream=1; iStream<=m_streamDetails.GetAudioStreamCount(); iStream++)
254     {
255       TiXmlElement stream("audio");
256       XMLUtils::SetString(&stream, "codec", m_streamDetails.GetAudioCodec(iStream));
257       XMLUtils::SetString(&stream, "language", m_streamDetails.GetAudioLanguage(iStream));
258       XMLUtils::SetInt(&stream, "channels", m_streamDetails.GetAudioChannels(iStream));
259       streamdetails.InsertEndChild(stream);
260     }
261     for (int iStream=1; iStream<=m_streamDetails.GetSubtitleStreamCount(); iStream++)
262     {
263       TiXmlElement stream("subtitle");
264       XMLUtils::SetString(&stream, "language", m_streamDetails.GetSubtitleLanguage(iStream));
265       streamdetails.InsertEndChild(stream);
266     }
267     fileinfo.InsertEndChild(streamdetails);
268     movie->InsertEndChild(fileinfo);
269   }  /* if has stream details */
270 
271   // cast
272   for (iCast it = m_cast.begin(); it != m_cast.end(); ++it)
273   {
274     // add a <actor> tag
275     TiXmlElement cast("actor");
276     TiXmlNode *node = movie->InsertEndChild(cast);
277     XMLUtils::SetString(node, "name", it->strName);
278     XMLUtils::SetString(node, "role", it->strRole);
279     XMLUtils::SetInt(node, "order", it->order);
280     XMLUtils::SetString(node, "thumb", it->thumbUrl.GetFirstUrlByType().m_url);
281   }
282   XMLUtils::SetStringArray(movie, "artist", m_artist);
283   XMLUtils::SetStringArray(movie, "showlink", m_showLink);
284 
285   for (const auto& namedSeason : m_namedSeasons)
286   {
287     TiXmlElement season("namedseason");
288     season.SetAttribute("number", namedSeason.first);
289     TiXmlText value(namedSeason.second);
290     season.InsertEndChild(value);
291     movie->InsertEndChild(season);
292   }
293 
294   TiXmlElement resume("resume");
295   XMLUtils::SetDouble(&resume, "position", m_resumePoint.timeInSeconds);
296   XMLUtils::SetDouble(&resume, "total", m_resumePoint.totalTimeInSeconds);
297   if (!m_resumePoint.playerState.empty())
298   {
299     TiXmlElement playerstate("playerstate");
300     CXBMCTinyXML doc;
301     doc.Parse(m_resumePoint.playerState);
302     playerstate.InsertEndChild(*doc.RootElement());
303     resume.InsertEndChild(playerstate);
304   }
305   movie->InsertEndChild(resume);
306 
307   XMLUtils::SetDateTime(movie, "dateadded", m_dateAdded);
308 
309   if (additionalNode)
310     movie->InsertEndChild(*additionalNode);
311 
312   return true;
313 }
314 
Load(const TiXmlElement * element,bool append,bool prioritise)315 bool CVideoInfoTag::Load(const TiXmlElement *element, bool append, bool prioritise)
316 {
317   if (!element)
318     return false;
319   if (!append)
320     Reset();
321   ParseNative(element, prioritise);
322   return true;
323 }
324 
Merge(CVideoInfoTag & other)325 void CVideoInfoTag::Merge(CVideoInfoTag& other)
326 {
327   if (!other.m_director.empty())
328     m_director = other.m_director;
329   if (!other.m_writingCredits.empty())
330     m_writingCredits = other.m_writingCredits;
331   if (!other.m_genre.empty())
332     m_genre = other.m_genre;
333   if (!other.m_country.empty())
334     m_country = other.m_country;
335   if (!other.m_strTagLine.empty())
336     m_strTagLine = other.m_strTagLine;
337   if (!other.m_strPlotOutline.empty())
338     m_strPlotOutline = other.m_strPlotOutline;
339   if (!other.m_strPlot.empty())
340     m_strPlot = other.m_strPlot;
341   if (other.m_strPictureURL.HasData())
342     m_strPictureURL = other.m_strPictureURL;
343   if (!other.m_strTitle.empty())
344     m_strTitle = other.m_strTitle;
345   if (!other.m_strShowTitle.empty())
346     m_strShowTitle = other.m_strShowTitle;
347   if (!other.m_strOriginalTitle.empty())
348     m_strOriginalTitle = other.m_strOriginalTitle;
349   if (!other.m_strSortTitle.empty())
350     m_strSortTitle = other.m_strSortTitle;
351   if (other.m_cast.size())
352     m_cast = other.m_cast;
353 
354   if (!other.m_set.title.empty())
355     m_set.title = other.m_set.title;
356   if (other.m_set.id)
357     m_set.id = other.m_set.id;
358   if (!other.m_set.overview.empty())
359     m_set.overview = other.m_set.overview;
360   if (!other.m_tags.empty())
361     m_tags = other.m_tags;
362 
363   if (!other.m_strFile.empty())
364     m_strFile = other.m_strFile;
365   if (!other.m_strPath.empty())
366     m_strPath = other.m_strPath;
367 
368   if (!other.m_strMPAARating.empty())
369     m_strMPAARating = other.m_strMPAARating;
370   if (!other.m_strFileNameAndPath.empty())
371       m_strFileNameAndPath = other.m_strFileNameAndPath;
372 
373   if (other.m_premiered.IsValid())
374     SetPremiered(other.GetPremiered());
375 
376   if (!other.m_strStatus.empty())
377     m_strStatus = other.m_strStatus;
378   if (!other.m_strProductionCode.empty())
379     m_strProductionCode = other.m_strProductionCode;
380 
381   if (other.m_firstAired.IsValid())
382     m_firstAired = other.m_firstAired;
383   if (!other.m_studio.empty())
384     m_studio = other.m_studio;
385   if (!other.m_strAlbum.empty())
386     m_strAlbum = other.m_strAlbum;
387   if (!other.m_artist.empty())
388     m_artist = other.m_artist;
389   if (!other.m_strTrailer.empty())
390     m_strTrailer = other.m_strTrailer;
391   if (other.m_iTop250)
392     m_iTop250 = other.m_iTop250;
393   if (other.m_iSeason != -1)
394     m_iSeason = other.m_iSeason;
395   if (other.m_iEpisode != -1)
396     m_iEpisode = other.m_iEpisode;
397 
398   if (other.m_iIdUniqueID != -1)
399     m_iIdUniqueID = other.m_iIdUniqueID;
400   if (other.m_uniqueIDs.size())
401   {
402     m_uniqueIDs = other.m_uniqueIDs;
403     m_strDefaultUniqueID = other.m_strDefaultUniqueID;
404   };
405   if (other.m_iSpecialSortSeason != -1)
406     m_iSpecialSortSeason = other.m_iSpecialSortSeason;
407   if (other.m_iSpecialSortEpisode != -1)
408     m_iSpecialSortEpisode = other.m_iSpecialSortEpisode;
409 
410   if (!other.m_ratings.empty())
411   {
412     m_ratings = other.m_ratings;
413     m_strDefaultRating = other.m_strDefaultRating;
414   };
415   if (other.m_iIdRating != -1)
416     m_iIdRating = other.m_iIdRating;
417   if (other.m_iUserRating)
418     m_iUserRating = other.m_iUserRating;
419 
420   if (other.m_iDbId != -1)
421     m_iDbId = other.m_iDbId;
422   if (other.m_iFileId != -1)
423     m_iFileId = other.m_iFileId;
424   if (other.m_iBookmarkId != -1)
425     m_iBookmarkId = other.m_iBookmarkId;
426   if (other.m_iTrack != -1)
427     m_iTrack = other.m_iTrack;
428 
429   if (other.m_fanart.GetNumFanarts())
430     m_fanart = other.m_fanart;
431 
432   if (other.m_duration)
433     m_duration = other.m_duration;
434   if (other.m_lastPlayed.IsValid())
435     m_lastPlayed = other.m_lastPlayed;
436 
437   if (!other.m_showLink.empty())
438     m_showLink = other.m_showLink;
439   if (other.m_namedSeasons.size())
440     m_namedSeasons = other.m_namedSeasons;
441   if (other.m_streamDetails.HasItems())
442     m_streamDetails = other.m_streamDetails;
443   if (other.IsPlayCountSet())
444     SetPlayCount(other.GetPlayCount());
445 
446   if (other.m_EpBookmark.IsSet())
447     m_EpBookmark = other.m_EpBookmark;
448 
449   if (!other.m_basePath.empty())
450     m_basePath = other.m_basePath;
451   if (other.m_parentPathID != -1)
452     m_parentPathID = other.m_parentPathID;
453   if (other.GetResumePoint().IsSet())
454     SetResumePoint(other.GetResumePoint());
455   if (other.m_iIdShow != -1)
456     m_iIdShow = other.m_iIdShow;
457   if (other.m_iIdSeason != -1)
458     m_iIdSeason = other.m_iIdSeason;
459 
460   if (other.m_dateAdded.IsValid())
461     m_dateAdded = other.m_dateAdded;
462 
463   if (!other.m_type.empty())
464     m_type = other.m_type;
465 
466   if (other.m_relevance != -1)
467     m_relevance = other.m_relevance;
468   if (other.m_parsedDetails)
469     m_parsedDetails = other.m_parsedDetails;
470   if (other.m_coverArt.size())
471     m_coverArt = other.m_coverArt;
472   if (other.m_year != -1)
473     m_year = other.m_year;
474 }
475 
Archive(CArchive & ar)476 void CVideoInfoTag::Archive(CArchive& ar)
477 {
478   if (ar.IsStoring())
479   {
480     ar << m_director;
481     ar << m_writingCredits;
482     ar << m_genre;
483     ar << m_country;
484     ar << m_strTagLine;
485     ar << m_strPlotOutline;
486     ar << m_strPlot;
487     ar << m_strPictureURL.GetData();
488     ar << m_fanart.m_xml;
489     ar << m_strTitle;
490     ar << m_strSortTitle;
491     ar << m_studio;
492     ar << m_strTrailer;
493     ar << (int)m_cast.size();
494     for (unsigned int i=0;i<m_cast.size();++i)
495     {
496       ar << m_cast[i].strName;
497       ar << m_cast[i].strRole;
498       ar << m_cast[i].order;
499       ar << m_cast[i].thumb;
500       ar << m_cast[i].thumbUrl.GetData();
501     }
502 
503     ar << m_set.title;
504     ar << m_set.id;
505     ar << m_set.overview;
506     ar << m_tags;
507     ar << m_duration;
508     ar << m_strFile;
509     ar << m_strPath;
510     ar << m_strMPAARating;
511     ar << m_strFileNameAndPath;
512     ar << m_strOriginalTitle;
513     ar << m_strEpisodeGuide;
514     ar << m_premiered;
515     ar << m_bHasPremiered;
516     ar << m_strStatus;
517     ar << m_strProductionCode;
518     ar << m_firstAired;
519     ar << m_strShowTitle;
520     ar << m_strAlbum;
521     ar << m_artist;
522     ar << GetPlayCount();
523     ar << m_lastPlayed;
524     ar << m_iTop250;
525     ar << m_iSeason;
526     ar << m_iEpisode;
527     ar << (int)m_uniqueIDs.size();
528     for (const auto& i : m_uniqueIDs)
529     {
530       ar << i.first;
531       ar << (i.first == m_strDefaultUniqueID);
532       ar << i.second;
533     }
534     ar << (int)m_ratings.size();
535     for (const auto& i : m_ratings)
536     {
537       ar << i.first;
538       ar << (i.first == m_strDefaultRating);
539       ar << i.second.rating;
540       ar << i.second.votes;
541     }
542     ar << m_iUserRating;
543     ar << m_iDbId;
544     ar << m_iFileId;
545     ar << m_iSpecialSortSeason;
546     ar << m_iSpecialSortEpisode;
547     ar << m_iBookmarkId;
548     ar << m_iTrack;
549     ar << dynamic_cast<IArchivable&>(m_streamDetails);
550     ar << m_showLink;
551     ar << static_cast<int>(m_namedSeasons.size());
552     for (const auto& namedSeason : m_namedSeasons)
553     {
554       ar << namedSeason.first;
555       ar << namedSeason.second;
556     }
557     ar << m_EpBookmark.playerState;
558     ar << m_EpBookmark.timeInSeconds;
559     ar << m_basePath;
560     ar << m_parentPathID;
561     ar << m_resumePoint.timeInSeconds;
562     ar << m_resumePoint.totalTimeInSeconds;
563     ar << m_resumePoint.playerState;
564     ar << m_iIdShow;
565     ar << m_dateAdded.GetAsDBDateTime();
566     ar << m_type;
567     ar << m_iIdSeason;
568     ar << m_coverArt.size();
569     for (auto& it : m_coverArt)
570       ar << it;
571   }
572   else
573   {
574     ar >> m_director;
575     ar >> m_writingCredits;
576     ar >> m_genre;
577     ar >> m_country;
578     ar >> m_strTagLine;
579     ar >> m_strPlotOutline;
580     ar >> m_strPlot;
581     std::string data;
582     ar >> data;
583     m_strPictureURL.SetData(data);
584     ar >> m_fanart.m_xml;
585     ar >> m_strTitle;
586     ar >> m_strSortTitle;
587     ar >> m_studio;
588     ar >> m_strTrailer;
589     int iCastSize;
590     ar >> iCastSize;
591     m_cast.reserve(iCastSize);
592     for (int i=0;i<iCastSize;++i)
593     {
594       SActorInfo info;
595       ar >> info.strName;
596       ar >> info.strRole;
597       ar >> info.order;
598       ar >> info.thumb;
599       std::string strXml;
600       ar >> strXml;
601       info.thumbUrl.ParseFromData(strXml);
602       m_cast.push_back(info);
603     }
604 
605     ar >> m_set.title;
606     ar >> m_set.id;
607     ar >> m_set.overview;
608     ar >> m_tags;
609     ar >> m_duration;
610     ar >> m_strFile;
611     ar >> m_strPath;
612     ar >> m_strMPAARating;
613     ar >> m_strFileNameAndPath;
614     ar >> m_strOriginalTitle;
615     ar >> m_strEpisodeGuide;
616     ar >> m_premiered;
617     ar >> m_bHasPremiered;
618     ar >> m_strStatus;
619     ar >> m_strProductionCode;
620     ar >> m_firstAired;
621     ar >> m_strShowTitle;
622     ar >> m_strAlbum;
623     ar >> m_artist;
624     ar >> m_playCount;
625     ar >> m_lastPlayed;
626     ar >> m_iTop250;
627     ar >> m_iSeason;
628     ar >> m_iEpisode;
629     int iUniqueIDSize;
630     ar >> iUniqueIDSize;
631     for (int i = 0; i < iUniqueIDSize; ++i)
632     {
633       std::string value;
634       std::string name;
635       bool defaultUniqueID;
636       ar >> name;
637       ar >> defaultUniqueID;
638       ar >> value;
639       SetUniqueID(value, name);
640       if (defaultUniqueID)
641         m_strDefaultUniqueID = name;
642     }
643     int iRatingSize;
644     ar >> iRatingSize;
645     for (int i = 0; i < iRatingSize; ++i)
646     {
647       CRating rating;
648       std::string name;
649       bool defaultRating;
650       ar >> name;
651       ar >> defaultRating;
652       ar >> rating.rating;
653       ar >> rating.votes;
654       SetRating(rating, name);
655       if (defaultRating)
656         m_strDefaultRating = name;
657     }
658     ar >> m_iUserRating;
659     ar >> m_iDbId;
660     ar >> m_iFileId;
661     ar >> m_iSpecialSortSeason;
662     ar >> m_iSpecialSortEpisode;
663     ar >> m_iBookmarkId;
664     ar >> m_iTrack;
665     ar >> dynamic_cast<IArchivable&>(m_streamDetails);
666     ar >> m_showLink;
667 
668     int namedSeasonSize;
669     ar >> namedSeasonSize;
670     for (int i = 0; i < namedSeasonSize; ++i)
671     {
672       int seasonNumber;
673       ar >> seasonNumber;
674       std::string seasonName;
675       ar >> seasonName;
676       m_namedSeasons.insert(std::make_pair(seasonNumber, seasonName));
677     }
678     ar >> m_EpBookmark.playerState;
679     ar >> m_EpBookmark.timeInSeconds;
680     ar >> m_basePath;
681     ar >> m_parentPathID;
682     ar >> m_resumePoint.timeInSeconds;
683     ar >> m_resumePoint.totalTimeInSeconds;
684     ar >> m_resumePoint.playerState;
685     ar >> m_iIdShow;
686 
687     std::string dateAdded;
688     ar >> dateAdded;
689     m_dateAdded.SetFromDBDateTime(dateAdded);
690     ar >> m_type;
691     ar >> m_iIdSeason;
692     size_t size;
693     ar >> size;
694     m_coverArt.resize(size);
695     for (size_t i = 0; i < size; ++i)
696       ar >> m_coverArt[i];
697   }
698 }
699 
Serialize(CVariant & value) const700 void CVideoInfoTag::Serialize(CVariant& value) const
701 {
702   value["director"] = m_director;
703   value["writer"] = m_writingCredits;
704   value["genre"] = m_genre;
705   value["country"] = m_country;
706   value["tagline"] = m_strTagLine;
707   value["plotoutline"] = m_strPlotOutline;
708   value["plot"] = m_strPlot;
709   value["title"] = m_strTitle;
710   value["votes"] = StringUtils::Format("%i", GetRating().votes);
711   value["studio"] = m_studio;
712   value["trailer"] = m_strTrailer;
713   value["cast"] = CVariant(CVariant::VariantTypeArray);
714   for (unsigned int i = 0; i < m_cast.size(); ++i)
715   {
716     CVariant actor;
717     actor["name"] = m_cast[i].strName;
718     actor["role"] = m_cast[i].strRole;
719     actor["order"] = m_cast[i].order;
720     if (!m_cast[i].thumb.empty())
721       actor["thumbnail"] = CTextureUtils::GetWrappedImageURL(m_cast[i].thumb);
722     value["cast"].push_back(actor);
723   }
724   value["set"] = m_set.title;
725   value["setid"] = m_set.id;
726   value["setoverview"] = m_set.overview;
727   value["tag"] = m_tags;
728   value["runtime"] = GetDuration();
729   value["file"] = m_strFile;
730   value["path"] = m_strPath;
731   value["imdbnumber"] = GetUniqueID();
732   value["mpaa"] = m_strMPAARating;
733   value["filenameandpath"] = m_strFileNameAndPath;
734   value["originaltitle"] = m_strOriginalTitle;
735   value["sorttitle"] = m_strSortTitle;
736   value["episodeguide"] = m_strEpisodeGuide;
737   value["premiered"] = m_premiered.IsValid() ? m_premiered.GetAsDBDate() : StringUtils::Empty;
738   value["status"] = m_strStatus;
739   value["productioncode"] = m_strProductionCode;
740   value["firstaired"] = m_firstAired.IsValid() ? m_firstAired.GetAsDBDate() : StringUtils::Empty;
741   value["showtitle"] = m_strShowTitle;
742   value["album"] = m_strAlbum;
743   value["artist"] = m_artist;
744   value["playcount"] = GetPlayCount();
745   value["lastplayed"] = m_lastPlayed.IsValid() ? m_lastPlayed.GetAsDBDateTime() : StringUtils::Empty;
746   value["top250"] = m_iTop250;
747   value["year"] = GetYear();
748   value["season"] = m_iSeason;
749   value["episode"] = m_iEpisode;
750   for (const auto& i : m_uniqueIDs)
751     value["uniqueid"][i.first] = i.second;
752 
753   value["rating"] = GetRating().rating;
754   CVariant ratings = CVariant(CVariant::VariantTypeObject);
755   for (const auto& i : m_ratings)
756   {
757     CVariant rating;
758     rating["rating"] = i.second.rating;
759     rating["votes"] = i.second.votes;
760     rating["default"] = i.first == m_strDefaultRating;
761 
762     ratings[i.first] = rating;
763   }
764   value["ratings"] = ratings;
765   value["userrating"] = m_iUserRating;
766   value["dbid"] = m_iDbId;
767   value["fileid"] = m_iFileId;
768   value["track"] = m_iTrack;
769   value["showlink"] = m_showLink;
770   m_streamDetails.Serialize(value["streamdetails"]);
771   CVariant resume = CVariant(CVariant::VariantTypeObject);
772   resume["position"] = m_resumePoint.timeInSeconds;
773   resume["total"] = m_resumePoint.totalTimeInSeconds;
774   value["resume"] = resume;
775   value["tvshowid"] = m_iIdShow;
776   value["dateadded"] = m_dateAdded.IsValid() ? m_dateAdded.GetAsDBDateTime() : StringUtils::Empty;
777   value["type"] = m_type;
778   value["seasonid"] = m_iIdSeason;
779   value["specialsortseason"] = m_iSpecialSortSeason;
780   value["specialsortepisode"] = m_iSpecialSortEpisode;
781 }
782 
ToSortable(SortItem & sortable,Field field) const783 void CVideoInfoTag::ToSortable(SortItem& sortable, Field field) const
784 {
785   switch (field)
786   {
787   case FieldDirector:                 sortable[FieldDirector] = m_director; break;
788   case FieldWriter:                   sortable[FieldWriter] = m_writingCredits; break;
789   case FieldGenre:                    sortable[FieldGenre] = m_genre; break;
790   case FieldCountry:                  sortable[FieldCountry] = m_country; break;
791   case FieldTagline:                  sortable[FieldTagline] = m_strTagLine; break;
792   case FieldPlotOutline:              sortable[FieldPlotOutline] = m_strPlotOutline; break;
793   case FieldPlot:                     sortable[FieldPlot] = m_strPlot; break;
794   case FieldTitle:
795   {
796     // make sure not to overwrite an existing title with an empty one
797     std::string title = m_strTitle;
798     if (!title.empty() || sortable.find(FieldTitle) == sortable.end())
799       sortable[FieldTitle] = title;
800     break;
801   }
802   case FieldVotes:                    sortable[FieldVotes] = GetRating().votes; break;
803   case FieldStudio:                   sortable[FieldStudio] = m_studio; break;
804   case FieldTrailer:                  sortable[FieldTrailer] = m_strTrailer; break;
805   case FieldSet:                      sortable[FieldSet] = m_set.title; break;
806   case FieldTime:                     sortable[FieldTime] = GetDuration(); break;
807   case FieldFilename:                 sortable[FieldFilename] = m_strFile; break;
808   case FieldMPAA:                     sortable[FieldMPAA] = m_strMPAARating; break;
809   case FieldPath:
810   {
811     // make sure not to overwrite an existing path with an empty one
812     std::string path = GetPath();
813     if (!path.empty() || sortable.find(FieldPath) == sortable.end())
814       sortable[FieldPath] = path;
815     break;
816   }
817   case FieldSortTitle:
818   {
819     // seasons with a custom name/title need special handling as they should be sorted by season number
820     if (m_type == MediaTypeSeason && !m_strSortTitle.empty())
821       sortable[FieldSortTitle] = StringUtils::Format(g_localizeStrings.Get(20358).c_str(), m_iSeason);
822     else
823       sortable[FieldSortTitle] = m_strSortTitle;
824     break;
825   }
826   case FieldTvShowStatus:             sortable[FieldTvShowStatus] = m_strStatus; break;
827   case FieldProductionCode:           sortable[FieldProductionCode] = m_strProductionCode; break;
828   case FieldAirDate:                  sortable[FieldAirDate] = m_firstAired.IsValid() ? m_firstAired.GetAsDBDate() : (m_premiered.IsValid() ? m_premiered.GetAsDBDate() : StringUtils::Empty); break;
829   case FieldTvShowTitle:              sortable[FieldTvShowTitle] = m_strShowTitle; break;
830   case FieldAlbum:                    sortable[FieldAlbum] = m_strAlbum; break;
831   case FieldArtist:                   sortable[FieldArtist] = m_artist; break;
832   case FieldPlaycount:                sortable[FieldPlaycount] = GetPlayCount(); break;
833   case FieldLastPlayed:               sortable[FieldLastPlayed] = m_lastPlayed.IsValid() ? m_lastPlayed.GetAsDBDateTime() : StringUtils::Empty; break;
834   case FieldTop250:                   sortable[FieldTop250] = m_iTop250; break;
835   case FieldYear:                     sortable[FieldYear] = GetYear(); break;
836   case FieldSeason:                   sortable[FieldSeason] = m_iSeason; break;
837   case FieldEpisodeNumber:            sortable[FieldEpisodeNumber] = m_iEpisode; break;
838   case FieldNumberOfEpisodes:         sortable[FieldNumberOfEpisodes] = m_iEpisode; break;
839   case FieldNumberOfWatchedEpisodes:  sortable[FieldNumberOfWatchedEpisodes] = m_iEpisode; break;
840   case FieldEpisodeNumberSpecialSort: sortable[FieldEpisodeNumberSpecialSort] = m_iSpecialSortEpisode; break;
841   case FieldSeasonSpecialSort:        sortable[FieldSeasonSpecialSort] = m_iSpecialSortSeason; break;
842   case FieldRating:                   sortable[FieldRating] = GetRating().rating; break;
843   case FieldUserRating:               sortable[FieldUserRating] = m_iUserRating; break;
844   case FieldId:                       sortable[FieldId] = m_iDbId; break;
845   case FieldTrackNumber:              sortable[FieldTrackNumber] = m_iTrack; break;
846   case FieldTag:                      sortable[FieldTag] = m_tags; break;
847 
848   case FieldVideoResolution:          sortable[FieldVideoResolution] = m_streamDetails.GetVideoHeight(); break;
849   case FieldVideoAspectRatio:         sortable[FieldVideoAspectRatio] = m_streamDetails.GetVideoAspect(); break;
850   case FieldVideoCodec:               sortable[FieldVideoCodec] = m_streamDetails.GetVideoCodec(); break;
851   case FieldStereoMode:               sortable[FieldStereoMode] = m_streamDetails.GetStereoMode(); break;
852 
853   case FieldAudioChannels:            sortable[FieldAudioChannels] = m_streamDetails.GetAudioChannels(); break;
854   case FieldAudioCodec:               sortable[FieldAudioCodec] = m_streamDetails.GetAudioCodec(); break;
855   case FieldAudioLanguage:            sortable[FieldAudioLanguage] = m_streamDetails.GetAudioLanguage(); break;
856 
857   case FieldSubtitleLanguage:         sortable[FieldSubtitleLanguage] = m_streamDetails.GetSubtitleLanguage(); break;
858 
859   case FieldInProgress:               sortable[FieldInProgress] = m_resumePoint.IsPartWay(); break;
860   case FieldDateAdded:                sortable[FieldDateAdded] = m_dateAdded.IsValid() ? m_dateAdded.GetAsDBDateTime() : StringUtils::Empty; break;
861   case FieldMediaType:                sortable[FieldMediaType] = m_type; break;
862   case FieldRelevance:                sortable[FieldRelevance] = m_relevance; break;
863   default: break;
864   }
865 }
866 
GetRating(std::string type) const867 const CRating CVideoInfoTag::GetRating(std::string type) const
868 {
869   if (type.empty())
870     type = m_strDefaultRating;
871 
872   const auto& rating = m_ratings.find(type);
873   if (rating == m_ratings.end())
874     return CRating();
875 
876   return rating->second;
877 }
878 
GetDefaultRating() const879 const std::string& CVideoInfoTag::GetDefaultRating() const
880 {
881   return m_strDefaultRating;
882 }
883 
HasYear() const884 bool CVideoInfoTag::HasYear() const
885 {
886   return m_year > 0 || m_firstAired.IsValid() || m_premiered.IsValid();
887 }
888 
GetYear() const889 int CVideoInfoTag::GetYear() const
890 {
891   if (m_year > 0)
892     return m_year;
893   if (m_firstAired.IsValid())
894     return GetFirstAired().GetYear();
895   if (m_premiered.IsValid())
896     return GetPremiered().GetYear();
897   return 0;
898 }
899 
HasPremiered() const900 bool CVideoInfoTag::HasPremiered() const
901 {
902   return m_bHasPremiered;
903 }
904 
GetPremiered() const905 const CDateTime& CVideoInfoTag::GetPremiered() const
906 {
907   return m_premiered;
908 }
909 
GetFirstAired() const910 const CDateTime& CVideoInfoTag::GetFirstAired() const
911 {
912   return m_firstAired;
913 }
914 
GetUniqueID(std::string type) const915 const std::string CVideoInfoTag::GetUniqueID(std::string type) const
916 {
917   if (type.empty())
918     type = m_strDefaultUniqueID;
919 
920   const auto& uniqueid = m_uniqueIDs.find(type);
921   if (uniqueid == m_uniqueIDs.end())
922     return "";
923 
924   return uniqueid->second;
925 }
926 
GetUniqueIDs() const927 const std::map<std::string, std::string>& CVideoInfoTag::GetUniqueIDs() const
928 {
929   return m_uniqueIDs;
930 }
931 
GetDefaultUniqueID() const932 const std::string& CVideoInfoTag::GetDefaultUniqueID() const
933 {
934   return m_strDefaultUniqueID;
935 }
936 
HasUniqueID() const937 bool CVideoInfoTag::HasUniqueID() const
938 {
939   return !m_uniqueIDs.empty();
940 }
941 
GetCast(bool bIncludeRole) const942 const std::string CVideoInfoTag::GetCast(bool bIncludeRole /*= false*/) const
943 {
944   std::string strLabel;
945   for (iCast it = m_cast.begin(); it != m_cast.end(); ++it)
946   {
947     std::string character;
948     if (it->strRole.empty() || !bIncludeRole)
949       character = StringUtils::Format("%s\n", it->strName.c_str());
950     else
951       character = StringUtils::Format("%s %s %s\n", it->strName.c_str(), g_localizeStrings.Get(20347).c_str(), it->strRole.c_str());
952     strLabel += character;
953   }
954   return StringUtils::TrimRight(strLabel, "\n");
955 }
956 
ParseNative(const TiXmlElement * movie,bool prioritise)957 void CVideoInfoTag::ParseNative(const TiXmlElement* movie, bool prioritise)
958 {
959   std::string value;
960   float fValue;
961 
962   if (XMLUtils::GetString(movie, "title", value))
963     SetTitle(value);
964 
965   if (XMLUtils::GetString(movie, "originaltitle", value))
966     SetOriginalTitle(value);
967 
968   if (XMLUtils::GetString(movie, "showtitle", value))
969     SetShowTitle(value);
970 
971   if (XMLUtils::GetString(movie, "sorttitle", value))
972     SetSortTitle(value);
973 
974   const TiXmlElement* node = movie->FirstChildElement("ratings");
975   if (node)
976   {
977     for (const TiXmlElement* child = node->FirstChildElement("rating"); child != nullptr; child = child->NextSiblingElement("rating"))
978     {
979       CRating r;
980       std::string name;
981       if (child->QueryStringAttribute("name", &name) != TIXML_SUCCESS)
982         name = "default";
983       XMLUtils::GetFloat(child, "value", r.rating);
984       if (XMLUtils::GetString(child, "votes", value))
985         r.votes = StringUtils::ReturnDigits(value);
986       int max_value = 10;
987       if ((child->QueryIntAttribute("max", &max_value) == TIXML_SUCCESS) && max_value >= 1)
988         r.rating = r.rating / max_value * 10; // Normalise the Movie Rating to between 1 and 10
989       SetRating(r, name);
990       bool isDefault = false;
991       // guard against assert in tinyxml
992       const char* rAtt = child->Attribute("default", static_cast<int*>(nullptr));
993       if (rAtt && strlen(rAtt) != 0 &&
994           (child->QueryBoolAttribute("default", &isDefault) == TIXML_SUCCESS) && isDefault)
995         m_strDefaultRating = name;
996     }
997   }
998   else if (XMLUtils::GetFloat(movie, "rating", fValue))
999   {
1000     CRating r(fValue, 0);
1001     if (XMLUtils::GetString(movie, "votes", value))
1002       r.votes = StringUtils::ReturnDigits(value);
1003     int max_value = 10;
1004     const TiXmlElement* rElement = movie->FirstChildElement("rating");
1005     if (rElement && (rElement->QueryIntAttribute("max", &max_value) == TIXML_SUCCESS) && max_value >= 1)
1006       r.rating = r.rating / max_value * 10; // Normalise the Movie Rating to between 1 and 10
1007     SetRating(r, "default");
1008     m_strDefaultRating = "default";
1009   }
1010   XMLUtils::GetInt(movie, "userrating", m_iUserRating);
1011 
1012   const TiXmlElement *epbookmark = movie->FirstChildElement("episodebookmark");
1013   if (epbookmark)
1014   {
1015     XMLUtils::GetDouble(epbookmark, "position", m_EpBookmark.timeInSeconds);
1016     const TiXmlElement *playerstate = epbookmark->FirstChildElement("playerstate");
1017     if (playerstate)
1018     {
1019       const TiXmlElement *value = playerstate->FirstChildElement();
1020       if (value)
1021         m_EpBookmark.playerState << *value;
1022     }
1023   }
1024   else
1025     XMLUtils::GetDouble(movie, "epbookmark", m_EpBookmark.timeInSeconds);
1026 
1027   int max_value = 10;
1028   const TiXmlElement* urElement = movie->FirstChildElement("userrating");
1029   if (urElement && (urElement->QueryIntAttribute("max", &max_value) == TIXML_SUCCESS) && max_value >= 1)
1030     m_iUserRating = m_iUserRating / max_value * 10; // Normalise the user Movie Rating to between 1 and 10
1031   XMLUtils::GetInt(movie, "top250", m_iTop250);
1032   XMLUtils::GetInt(movie, "season", m_iSeason);
1033   XMLUtils::GetInt(movie, "episode", m_iEpisode);
1034   XMLUtils::GetInt(movie, "track", m_iTrack);
1035 
1036   XMLUtils::GetInt(movie, "displayseason", m_iSpecialSortSeason);
1037   XMLUtils::GetInt(movie, "displayepisode", m_iSpecialSortEpisode);
1038   int after=0;
1039   XMLUtils::GetInt(movie, "displayafterseason",after);
1040   if (after > 0)
1041   {
1042     m_iSpecialSortSeason = after;
1043     m_iSpecialSortEpisode = 0x1000; // should be more than any realistic episode number
1044   }
1045 
1046   if (XMLUtils::GetString(movie, "outline", value))
1047     SetPlotOutline(value);
1048 
1049   if (XMLUtils::GetString(movie, "plot", value))
1050     SetPlot(value);
1051 
1052   if (XMLUtils::GetString(movie, "tagline", value))
1053     SetTagLine(value);
1054 
1055 
1056   if (XMLUtils::GetString(movie, "runtime", value) && !value.empty())
1057     m_duration = GetDurationFromMinuteString(StringUtils::Trim(value));
1058 
1059   if (XMLUtils::GetString(movie, "mpaa", value))
1060     SetMPAARating(value);
1061 
1062   XMLUtils::GetInt(movie, "playcount", m_playCount);
1063   XMLUtils::GetDate(movie, "lastplayed", m_lastPlayed);
1064 
1065   if (XMLUtils::GetString(movie, "file", value))
1066     SetFile(value);
1067 
1068   if (XMLUtils::GetString(movie, "path", value))
1069     SetPath(value);
1070 
1071   const TiXmlElement* uniqueid = movie->FirstChildElement("uniqueid");
1072   if (uniqueid == nullptr)
1073   {
1074     if (XMLUtils::GetString(movie, "id", value))
1075       SetUniqueID(value);
1076   }
1077   else
1078   {
1079     for (; uniqueid != nullptr; uniqueid = uniqueid->NextSiblingElement("uniqueid"))
1080     {
1081       if (uniqueid->FirstChild())
1082       {
1083       if (uniqueid->QueryStringAttribute("type", &value) == TIXML_SUCCESS)
1084         SetUniqueID(uniqueid->FirstChild()->ValueStr(), value);
1085       else
1086         SetUniqueID(uniqueid->FirstChild()->ValueStr());
1087       bool isDefault;
1088       if ((uniqueid->QueryBoolAttribute("default", &isDefault) == TIXML_SUCCESS) && isDefault)
1089         m_strDefaultUniqueID = value;
1090       }
1091     }
1092   }
1093 
1094   if (XMLUtils::GetString(movie, "filenameandpath", value))
1095     SetFileNameAndPath(value);
1096 
1097   if (XMLUtils::GetDate(movie, "premiered", m_premiered))
1098   {
1099     m_bHasPremiered = true;
1100   }
1101   else
1102   {
1103     int year;
1104     if (XMLUtils::GetInt(movie, "year", year))
1105       SetYear(year);
1106   }
1107 
1108   if (XMLUtils::GetString(movie, "status", value))
1109     SetStatus(value);
1110 
1111   if (XMLUtils::GetString(movie, "code", value))
1112     SetProductionCode(value);
1113 
1114   XMLUtils::GetDate(movie, "aired", m_firstAired);
1115 
1116   if (XMLUtils::GetString(movie, "album", value))
1117     SetAlbum(value);
1118 
1119   if (XMLUtils::GetString(movie, "trailer", value))
1120     SetTrailer(value);
1121 
1122   if (XMLUtils::GetString(movie, "basepath", value))
1123     SetBasePath(value);
1124 
1125   // make sure the picture URLs have been parsed
1126   m_strPictureURL.Parse();
1127   size_t iThumbCount = m_strPictureURL.GetUrls().size();
1128   std::string xmlAdd = m_strPictureURL.GetData();
1129 
1130   const TiXmlElement* thumb = movie->FirstChildElement("thumb");
1131   while (thumb)
1132   {
1133     m_strPictureURL.ParseAndAppendUrl(thumb);
1134     if (prioritise)
1135     {
1136       std::string temp;
1137       temp << *thumb;
1138       xmlAdd = temp+xmlAdd;
1139     }
1140     thumb = thumb->NextSiblingElement("thumb");
1141   }
1142 
1143   // prioritise thumbs from nfos
1144   if (prioritise && iThumbCount && iThumbCount != m_strPictureURL.GetUrls().size())
1145   {
1146     auto thumbUrls = m_strPictureURL.GetUrls();
1147     rotate(thumbUrls.begin(), thumbUrls.begin() + iThumbCount, thumbUrls.end());
1148     m_strPictureURL.SetUrls(thumbUrls);
1149     m_strPictureURL.SetData(xmlAdd);
1150   }
1151 
1152   const std::string itemSeparator = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator;
1153 
1154   std::vector<std::string> genres(m_genre);
1155   if (XMLUtils::GetStringArray(movie, "genre", genres, prioritise, itemSeparator))
1156     SetGenre(genres);
1157 
1158   std::vector<std::string> country(m_country);
1159   if (XMLUtils::GetStringArray(movie, "country", country, prioritise, itemSeparator))
1160     SetCountry(country);
1161 
1162   std::vector<std::string> credits(m_writingCredits);
1163   if (XMLUtils::GetStringArray(movie, "credits", credits, prioritise, itemSeparator))
1164     SetWritingCredits(credits);
1165 
1166   std::vector<std::string> director(m_director);
1167   if (XMLUtils::GetStringArray(movie, "director", director, prioritise, itemSeparator))
1168     SetDirector(director);
1169 
1170   std::vector<std::string> showLink(m_showLink);
1171   if (XMLUtils::GetStringArray(movie, "showlink", showLink, prioritise, itemSeparator))
1172     SetShowLink(showLink);
1173 
1174   const TiXmlElement* namedSeason = movie->FirstChildElement("namedseason");
1175   while (namedSeason != nullptr)
1176   {
1177     if (namedSeason->FirstChild() != nullptr)
1178     {
1179       int seasonNumber;
1180       std::string seasonName = namedSeason->FirstChild()->ValueStr();
1181       if (!seasonName.empty() &&
1182           namedSeason->Attribute("number", &seasonNumber) != nullptr)
1183         m_namedSeasons.insert(std::make_pair(seasonNumber, seasonName));
1184     }
1185 
1186     namedSeason = namedSeason->NextSiblingElement("namedseason");
1187   }
1188 
1189   // cast
1190   node = movie->FirstChildElement("actor");
1191   if (node && node->FirstChild() && prioritise)
1192     m_cast.clear();
1193   while (node)
1194   {
1195     const TiXmlNode *actor = node->FirstChild("name");
1196     if (actor && actor->FirstChild())
1197     {
1198       SActorInfo info;
1199       info.strName = actor->FirstChild()->Value();
1200 
1201       if (XMLUtils::GetString(node, "role", value))
1202         info.strRole = StringUtils::Trim(value);
1203 
1204       XMLUtils::GetInt(node, "order", info.order);
1205       const TiXmlElement* thumb = node->FirstChildElement("thumb");
1206       while (thumb)
1207       {
1208         info.thumbUrl.ParseAndAppendUrl(thumb);
1209         thumb = thumb->NextSiblingElement("thumb");
1210       }
1211       const char* clear=node->Attribute("clear");
1212       if (clear && StringUtils::CompareNoCase(clear, "true"))
1213         m_cast.clear();
1214       m_cast.push_back(info);
1215     }
1216     node = node->NextSiblingElement("actor");
1217   }
1218 
1219   // Pre-Jarvis NFO file:
1220   // <set>A set</set>
1221   if (XMLUtils::GetString(movie, "set", value))
1222     SetSet(value);
1223   // Jarvis+:
1224   // <set><name>A set</name><overview>A set with a number of movies...</overview></set>
1225   node = movie->FirstChildElement("set");
1226   if (node)
1227   {
1228     // No name, no set
1229     if (XMLUtils::GetString(node, "name", value))
1230     {
1231       SetSet(value);
1232       if (XMLUtils::GetString(node, "overview", value))
1233         SetSetOverview(value);
1234     }
1235   }
1236 
1237   std::vector<std::string> tags(m_tags);
1238   if (XMLUtils::GetStringArray(movie, "tag", tags, prioritise, itemSeparator))
1239     SetTags(tags);
1240 
1241   std::vector<std::string> studio(m_studio);
1242   if (XMLUtils::GetStringArray(movie, "studio", studio, prioritise, itemSeparator))
1243     SetStudio(studio);
1244 
1245   // artists
1246   std::vector<std::string> artist(m_artist);
1247   node = movie->FirstChildElement("artist");
1248   if (node && node->FirstChild() && prioritise)
1249     artist.clear();
1250   while (node)
1251   {
1252     const TiXmlNode* pNode = node->FirstChild("name");
1253     const char* pValue=NULL;
1254     if (pNode && pNode->FirstChild())
1255       pValue = pNode->FirstChild()->Value();
1256     else if (node->FirstChild())
1257       pValue = node->FirstChild()->Value();
1258     if (pValue)
1259     {
1260       const char* clear=node->Attribute("clear");
1261       if (clear && StringUtils::CompareNoCase(clear, "true") == 0)
1262         artist.clear();
1263       std::vector<std::string> newArtists = StringUtils::Split(pValue, itemSeparator);
1264       artist.insert(artist.end(), newArtists.begin(), newArtists.end());
1265     }
1266     node = node->NextSiblingElement("artist");
1267   }
1268   SetArtist(artist);
1269 
1270   node = movie->FirstChildElement("fileinfo");
1271   if (node)
1272   {
1273     // Try to pull from fileinfo/streamdetails/[video|audio|subtitle]
1274     const TiXmlNode *nodeStreamDetails = node->FirstChild("streamdetails");
1275     if (nodeStreamDetails)
1276     {
1277       const TiXmlNode *nodeDetail = NULL;
1278       while ((nodeDetail = nodeStreamDetails->IterateChildren("audio", nodeDetail)))
1279       {
1280         CStreamDetailAudio *p = new CStreamDetailAudio();
1281         if (XMLUtils::GetString(nodeDetail, "codec", value))
1282           p->m_strCodec = StringUtils::Trim(value);
1283 
1284         if (XMLUtils::GetString(nodeDetail, "language", value))
1285           p->m_strLanguage = StringUtils::Trim(value);
1286 
1287         XMLUtils::GetInt(nodeDetail, "channels", p->m_iChannels);
1288         StringUtils::ToLower(p->m_strCodec);
1289         StringUtils::ToLower(p->m_strLanguage);
1290         m_streamDetails.AddStream(p);
1291       }
1292       nodeDetail = NULL;
1293       while ((nodeDetail = nodeStreamDetails->IterateChildren("video", nodeDetail)))
1294       {
1295         CStreamDetailVideo *p = new CStreamDetailVideo();
1296         if (XMLUtils::GetString(nodeDetail, "codec", value))
1297           p->m_strCodec = StringUtils::Trim(value);
1298 
1299         XMLUtils::GetFloat(nodeDetail, "aspect", p->m_fAspect);
1300         XMLUtils::GetInt(nodeDetail, "width", p->m_iWidth);
1301         XMLUtils::GetInt(nodeDetail, "height", p->m_iHeight);
1302         XMLUtils::GetInt(nodeDetail, "durationinseconds", p->m_iDuration);
1303         if (XMLUtils::GetString(nodeDetail, "stereomode", value))
1304           p->m_strStereoMode = StringUtils::Trim(value);
1305         if (XMLUtils::GetString(nodeDetail, "language", value))
1306           p->m_strLanguage = StringUtils::Trim(value);
1307 
1308         StringUtils::ToLower(p->m_strCodec);
1309         StringUtils::ToLower(p->m_strStereoMode);
1310         StringUtils::ToLower(p->m_strLanguage);
1311         m_streamDetails.AddStream(p);
1312       }
1313       nodeDetail = NULL;
1314       while ((nodeDetail = nodeStreamDetails->IterateChildren("subtitle", nodeDetail)))
1315       {
1316         CStreamDetailSubtitle *p = new CStreamDetailSubtitle();
1317         if (XMLUtils::GetString(nodeDetail, "language", value))
1318           p->m_strLanguage = StringUtils::Trim(value);
1319         StringUtils::ToLower(p->m_strLanguage);
1320         m_streamDetails.AddStream(p);
1321       }
1322     }
1323     m_streamDetails.DetermineBestStreams();
1324   }  /* if fileinfo */
1325 
1326   const TiXmlElement *epguide = movie->FirstChildElement("episodeguide");
1327   if (epguide)
1328   {
1329     // DEPRECIATE ME - support for old XML-encoded <episodeguide> blocks.
1330     if (epguide->FirstChild() &&
1331         StringUtils::CompareNoCase("<episodeguide", epguide->FirstChild()->Value(), 13) == 0)
1332       m_strEpisodeGuide = epguide->FirstChild()->Value();
1333     else
1334     {
1335       std::stringstream stream;
1336       stream << *epguide;
1337       m_strEpisodeGuide = stream.str();
1338     }
1339   }
1340 
1341   // fanart
1342   const TiXmlElement *fanart = movie->FirstChildElement("fanart");
1343   if (fanart)
1344   {
1345     // we prioritise mixed-mode nfo's with fanart set
1346     if (prioritise)
1347     {
1348       std::string temp;
1349       temp << *fanart;
1350       m_fanart.m_xml = temp+m_fanart.m_xml;
1351     }
1352     else
1353       m_fanart.m_xml << *fanart;
1354     m_fanart.Unpack();
1355   }
1356 
1357   // resumePoint
1358   const TiXmlNode *resume = movie->FirstChild("resume");
1359   if (resume)
1360   {
1361     XMLUtils::GetDouble(resume, "position", m_resumePoint.timeInSeconds);
1362     XMLUtils::GetDouble(resume, "total", m_resumePoint.totalTimeInSeconds);
1363     const TiXmlElement *playerstate = resume->FirstChildElement("playerstate");
1364     if (playerstate)
1365     {
1366       const TiXmlElement *value = playerstate->FirstChildElement();
1367       if (value)
1368         m_resumePoint.playerState << *value;
1369     }
1370   }
1371 
1372   XMLUtils::GetDateTime(movie, "dateadded", m_dateAdded);
1373 }
1374 
HasStreamDetails() const1375 bool CVideoInfoTag::HasStreamDetails() const
1376 {
1377   return m_streamDetails.HasItems();
1378 }
1379 
IsEmpty() const1380 bool CVideoInfoTag::IsEmpty() const
1381 {
1382   return (m_strTitle.empty() &&
1383           m_strFile.empty() &&
1384           m_strPath.empty());
1385 }
1386 
SetDuration(int duration)1387 void CVideoInfoTag::SetDuration(int duration)
1388 {
1389   m_duration = duration;
1390 }
1391 
GetDuration() const1392 unsigned int CVideoInfoTag::GetDuration() const
1393 {
1394   /*
1395    Prefer the duration from the stream if it isn't too
1396    small (60%) compared to the duration from the tag.
1397    */
1398   unsigned int duration = m_streamDetails.GetVideoDuration();
1399   if (duration > m_duration * 0.6)
1400     return duration;
1401 
1402   return m_duration;
1403 }
1404 
GetStaticDuration() const1405 unsigned int CVideoInfoTag::GetStaticDuration() const
1406 {
1407   return m_duration;
1408 }
1409 
GetDurationFromMinuteString(const std::string & runtime)1410 unsigned int CVideoInfoTag::GetDurationFromMinuteString(const std::string &runtime)
1411 {
1412   unsigned int duration = (unsigned int)str2uint64(runtime);
1413   if (!duration)
1414   { // failed for some reason, or zero
1415     duration = strtoul(runtime.c_str(), NULL, 10);
1416     CLog::Log(LOGWARNING, "%s <runtime> should be in minutes. Interpreting '%s' as %u minutes", __FUNCTION__, runtime.c_str(), duration);
1417   }
1418   return duration*60;
1419 }
1420 
SetBasePath(std::string basePath)1421 void CVideoInfoTag::SetBasePath(std::string basePath)
1422 {
1423   m_basePath = Trim(std::move(basePath));
1424 }
1425 
SetDirector(std::vector<std::string> director)1426 void CVideoInfoTag::SetDirector(std::vector<std::string> director)
1427 {
1428   m_director = Trim(std::move(director));
1429 }
1430 
SetWritingCredits(std::vector<std::string> writingCredits)1431 void CVideoInfoTag::SetWritingCredits(std::vector<std::string> writingCredits)
1432 {
1433   m_writingCredits = Trim(std::move(writingCredits));
1434 }
1435 
SetGenre(std::vector<std::string> genre)1436 void CVideoInfoTag::SetGenre(std::vector<std::string> genre)
1437 {
1438   m_genre = Trim(std::move(genre));
1439 }
1440 
SetCountry(std::vector<std::string> country)1441 void CVideoInfoTag::SetCountry(std::vector<std::string> country)
1442 {
1443   m_country = Trim(std::move(country));
1444 }
1445 
SetTagLine(std::string tagLine)1446 void CVideoInfoTag::SetTagLine(std::string tagLine)
1447 {
1448   m_strTagLine = Trim(std::move(tagLine));
1449 }
1450 
SetPlotOutline(std::string plotOutline)1451 void CVideoInfoTag::SetPlotOutline(std::string plotOutline)
1452 {
1453   m_strPlotOutline = Trim(std::move(plotOutline));
1454 }
1455 
SetTrailer(std::string trailer)1456 void CVideoInfoTag::SetTrailer(std::string trailer)
1457 {
1458   m_strTrailer = Trim(std::move(trailer));
1459 }
1460 
SetPlot(std::string plot)1461 void CVideoInfoTag::SetPlot(std::string plot)
1462 {
1463   m_strPlot = Trim(std::move(plot));
1464 }
1465 
SetTitle(std::string title)1466 void CVideoInfoTag::SetTitle(std::string title)
1467 {
1468   m_strTitle = Trim(std::move(title));
1469 }
1470 
GetTitle()1471 std::string const &CVideoInfoTag::GetTitle()
1472 {
1473   return m_strTitle;
1474 }
1475 
SetSortTitle(std::string sortTitle)1476 void CVideoInfoTag::SetSortTitle(std::string sortTitle)
1477 {
1478   m_strSortTitle = Trim(std::move(sortTitle));
1479 }
1480 
SetPictureURL(CScraperUrl & pictureURL)1481 void CVideoInfoTag::SetPictureURL(CScraperUrl &pictureURL)
1482 {
1483   m_strPictureURL = pictureURL;
1484 }
1485 
SetRating(float rating,int votes,const std::string & type,bool def)1486 void CVideoInfoTag::SetRating(float rating, int votes, const std::string& type /* = "" */, bool def /* = false */)
1487 {
1488   SetRating(CRating(rating, votes), type, def);
1489 }
1490 
SetRating(CRating rating,const std::string & type,bool def)1491 void CVideoInfoTag::SetRating(CRating rating, const std::string& type /* = "" */, bool def /* = false */)
1492 {
1493   if (rating.rating <= 0 || rating.rating > 10)
1494     return;
1495 
1496   if (type.empty())
1497     m_ratings[m_strDefaultRating] = rating;
1498   else
1499   {
1500     if (def || m_ratings.empty())
1501       m_strDefaultRating = type;
1502     m_ratings[type] = rating;
1503   }
1504 }
1505 
SetRating(float rating,const std::string & type,bool def)1506 void CVideoInfoTag::SetRating(float rating, const std::string& type /* = "" */, bool def /* = false */)
1507 {
1508   if (rating <= 0 || rating > 10)
1509     return;
1510 
1511   if (type.empty())
1512     m_ratings[m_strDefaultRating].rating = rating;
1513   else
1514   {
1515     if (def || m_ratings.empty())
1516       m_strDefaultRating = type;
1517     m_ratings[type].rating = rating;
1518   }
1519 }
1520 
RemoveRating(const std::string & type)1521 void CVideoInfoTag::RemoveRating(const std::string& type)
1522 {
1523   if (m_ratings.find(type) != m_ratings.end())
1524   {
1525     m_ratings.erase(type);
1526     if (m_strDefaultRating == type && !m_ratings.empty())
1527       m_strDefaultRating = m_ratings.begin()->first;
1528   }
1529 }
1530 
SetRatings(RatingMap ratings)1531 void CVideoInfoTag::SetRatings(RatingMap ratings)
1532 {
1533   m_ratings = std::move(ratings);
1534 }
1535 
SetVotes(int votes,const std::string & type)1536 void CVideoInfoTag::SetVotes(int votes, const std::string& type /* = "" */)
1537 {
1538   if (type.empty())
1539     m_ratings[m_strDefaultRating].votes = votes;
1540   else
1541     m_ratings[type].votes = votes;
1542 }
1543 
SetPremiered(const CDateTime & premiered)1544 void CVideoInfoTag::SetPremiered(const CDateTime& premiered)
1545 {
1546   m_premiered = premiered;
1547   m_bHasPremiered = premiered.IsValid();
1548 }
1549 
SetPremieredFromDBDate(const std::string & premieredString)1550 void CVideoInfoTag::SetPremieredFromDBDate(const std::string& premieredString)
1551 {
1552   CDateTime premiered;
1553   premiered.SetFromDBDate(premieredString);
1554   SetPremiered(premiered);
1555 }
1556 
SetYear(int year)1557 void CVideoInfoTag::SetYear(int year)
1558 {
1559   if (year <= 0)
1560     return;
1561 
1562   m_year = year;
1563 }
1564 
SetArtist(std::vector<std::string> artist)1565 void CVideoInfoTag::SetArtist(std::vector<std::string> artist)
1566 {
1567   m_artist = Trim(std::move(artist));
1568 }
1569 
SetUniqueIDs(std::map<std::string,std::string> uniqueIDs)1570 void CVideoInfoTag::SetUniqueIDs(std::map<std::string, std::string> uniqueIDs)
1571 {
1572   for (const auto& uniqueid : uniqueIDs)
1573   {
1574     if (uniqueid.first.empty())
1575       uniqueIDs.erase(uniqueid.first);
1576   }
1577   if (uniqueIDs.find(m_strDefaultUniqueID) == uniqueIDs.end())
1578   {
1579     const auto defaultUniqueId = GetUniqueID();
1580     if (!defaultUniqueId.empty())
1581       uniqueIDs[m_strDefaultUniqueID] = defaultUniqueId;
1582   }
1583   m_uniqueIDs = std::move(uniqueIDs);
1584 }
1585 
SetSet(std::string set)1586 void CVideoInfoTag::SetSet(std::string set)
1587 {
1588   m_set.title = Trim(std::move(set));
1589 }
1590 
SetSetOverview(std::string setOverview)1591 void CVideoInfoTag::SetSetOverview(std::string setOverview)
1592 {
1593   m_set.overview = Trim(std::move(setOverview));
1594 }
1595 
SetTags(std::vector<std::string> tags)1596 void CVideoInfoTag::SetTags(std::vector<std::string> tags)
1597 {
1598   m_tags = Trim(std::move(tags));
1599 }
1600 
SetFile(std::string file)1601 void CVideoInfoTag::SetFile(std::string file)
1602 {
1603   m_strFile = Trim(std::move(file));
1604 }
1605 
SetPath(std::string path)1606 void CVideoInfoTag::SetPath(std::string path)
1607 {
1608   m_strPath = Trim(std::move(path));
1609 }
1610 
SetMPAARating(std::string mpaaRating)1611 void CVideoInfoTag::SetMPAARating(std::string mpaaRating)
1612 {
1613   m_strMPAARating = Trim(std::move(mpaaRating));
1614 }
1615 
SetFileNameAndPath(std::string fileNameAndPath)1616 void CVideoInfoTag::SetFileNameAndPath(std::string fileNameAndPath)
1617 {
1618   m_strFileNameAndPath = Trim(std::move(fileNameAndPath));
1619 }
1620 
SetOriginalTitle(std::string originalTitle)1621 void CVideoInfoTag::SetOriginalTitle(std::string originalTitle)
1622 {
1623   m_strOriginalTitle = Trim(std::move(originalTitle));
1624 }
1625 
SetEpisodeGuide(std::string episodeGuide)1626 void CVideoInfoTag::SetEpisodeGuide(std::string episodeGuide)
1627 {
1628   if (StringUtils::StartsWith(episodeGuide, "<episodeguide"))
1629     m_strEpisodeGuide = Trim(std::move(episodeGuide));
1630   else
1631     m_strEpisodeGuide = StringUtils::Format("<episodeguide>%s</episodeguide>", Trim(std::move(episodeGuide)).c_str());
1632 }
1633 
SetStatus(std::string status)1634 void CVideoInfoTag::SetStatus(std::string status)
1635 {
1636   m_strStatus = Trim(std::move(status));
1637 }
1638 
SetProductionCode(std::string productionCode)1639 void CVideoInfoTag::SetProductionCode(std::string productionCode)
1640 {
1641   m_strProductionCode = Trim(std::move(productionCode));
1642 }
1643 
SetShowTitle(std::string showTitle)1644 void CVideoInfoTag::SetShowTitle(std::string showTitle)
1645 {
1646   m_strShowTitle = Trim(std::move(showTitle));
1647 }
1648 
SetStudio(std::vector<std::string> studio)1649 void CVideoInfoTag::SetStudio(std::vector<std::string> studio)
1650 {
1651   m_studio = Trim(std::move(studio));
1652 }
1653 
SetAlbum(std::string album)1654 void CVideoInfoTag::SetAlbum(std::string album)
1655 {
1656   m_strAlbum = Trim(std::move(album));
1657 }
1658 
SetShowLink(std::vector<std::string> showLink)1659 void CVideoInfoTag::SetShowLink(std::vector<std::string> showLink)
1660 {
1661   m_showLink = Trim(std::move(showLink));
1662 }
1663 
SetUniqueID(const std::string & uniqueid,const std::string & type,bool isDefaultID)1664 void CVideoInfoTag::SetUniqueID(const std::string& uniqueid, const std::string& type /* = "" */, bool isDefaultID /* = false */)
1665 {
1666   if (uniqueid.empty())
1667     return;
1668 
1669   if (type.empty())
1670     m_uniqueIDs[m_strDefaultUniqueID] = uniqueid;
1671   else
1672   {
1673     m_uniqueIDs[type] = uniqueid;
1674     if (isDefaultID)
1675       m_strDefaultUniqueID = type;
1676   }
1677 }
1678 
RemoveUniqueID(const std::string & type)1679 void CVideoInfoTag::RemoveUniqueID(const std::string& type)
1680 {
1681   if (m_uniqueIDs.find(type) != m_uniqueIDs.end())
1682     m_uniqueIDs.erase(type);
1683 }
1684 
SetNamedSeasons(std::map<int,std::string> namedSeasons)1685 void CVideoInfoTag::SetNamedSeasons(std::map<int, std::string> namedSeasons)
1686 {
1687   m_namedSeasons = std::move(namedSeasons);
1688 }
1689 
SetUserrating(int userrating)1690 void CVideoInfoTag::SetUserrating(int userrating)
1691 {
1692   //This value needs to be between 0-10 - 0 will unset the userrating
1693   userrating = std::max(userrating, 0);
1694   userrating = std::min(userrating, 10);
1695 
1696   m_iUserRating = userrating;
1697 }
1698 
Trim(std::string && value)1699 std::string CVideoInfoTag::Trim(std::string &&value)
1700 {
1701   return StringUtils::Trim(value);
1702 }
1703 
Trim(std::vector<std::string> && items)1704 std::vector<std::string> CVideoInfoTag::Trim(std::vector<std::string>&& items)
1705 {
1706   std::for_each(items.begin(), items.end(), [](std::string &str){
1707     str = StringUtils::Trim(str);
1708   });
1709   return std::move(items);
1710 }
1711 
GetPlayCount() const1712 int CVideoInfoTag::GetPlayCount() const
1713 {
1714   return IsPlayCountSet() ? m_playCount : 0;
1715 }
1716 
SetPlayCount(int count)1717 bool CVideoInfoTag::SetPlayCount(int count)
1718 {
1719   m_playCount = count;
1720   return true;
1721 }
1722 
IncrementPlayCount()1723 bool CVideoInfoTag::IncrementPlayCount()
1724 {
1725   if (!IsPlayCountSet())
1726     m_playCount = 0;
1727 
1728   m_playCount++;
1729   return true;
1730 }
1731 
ResetPlayCount()1732 void CVideoInfoTag::ResetPlayCount()
1733 {
1734   m_playCount = PLAYCOUNT_NOT_SET;
1735 }
1736 
IsPlayCountSet() const1737 bool CVideoInfoTag::IsPlayCountSet() const
1738 {
1739   return m_playCount != PLAYCOUNT_NOT_SET;
1740 }
1741 
GetResumePoint() const1742 CBookmark CVideoInfoTag::GetResumePoint() const
1743 {
1744   return m_resumePoint;
1745 }
1746 
SetResumePoint(const CBookmark & resumePoint)1747 bool CVideoInfoTag::SetResumePoint(const CBookmark &resumePoint)
1748 {
1749   m_resumePoint = resumePoint;
1750   return true;
1751 }
1752 
SetResumePoint(double timeInSeconds,double totalTimeInSeconds,const std::string & playerState)1753 bool CVideoInfoTag::SetResumePoint(double timeInSeconds, double totalTimeInSeconds, const std::string &playerState)
1754 {
1755   CBookmark resumePoint;
1756   resumePoint.timeInSeconds = timeInSeconds;
1757   resumePoint.totalTimeInSeconds = totalTimeInSeconds;
1758   resumePoint.playerState = playerState;
1759   resumePoint.type = CBookmark::RESUME;
1760 
1761   m_resumePoint = resumePoint;
1762   return true;
1763 }
1764