2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
9 #include "VideoInfoTag.h"
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"
27 void CVideoInfoTag::Reset()
30 m_writingCredits
.clear();
34 m_strPlotOutline
.clear();
36 m_strPictureURL
.Clear();
38 m_strShowTitle
.clear();
39 m_strOriginalTitle
.clear();
40 m_strSortTitle
.clear();
44 m_set
.overview
.clear();
48 m_strMPAARating
.clear();
49 m_strFileNameAndPath
.clear();
51 m_bHasPremiered
= false;
53 m_strProductionCode
.clear();
65 m_strDefaultUniqueID
= "unknown";
66 m_iSpecialSortSeason
= -1;
67 m_iSpecialSortEpisode
= -1;
68 m_strDefaultRating
= "default";
76 m_fanart
.m_xml
.clear();
80 m_namedSeasons
.clear();
81 m_streamDetails
.Reset();
82 m_playCount
= PLAYCOUNT_NOT_SET
;
84 m_EpBookmark
.type
= CBookmark::EPISODE
;
87 m_resumePoint
.Reset();
88 m_resumePoint
.type
= CBookmark::RESUME
;
98 bool CVideoInfoTag::Save(TiXmlNode
*node
, const std::string
&tag
, bool savePathInfo
, const TiXmlElement
*additionalNode
)
100 if (!node
) return false;
102 // we start with a <tag> tag
103 TiXmlElement
movieElement(tag
.c_str());
104 TiXmlNode
*movie
= node
->InsertEndChild(movieElement
);
106 if (!movie
) return false;
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())
117 TiXmlElement
ratings("ratings");
118 for (const auto& it
: m_ratings
)
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
);
129 movie
->InsertEndChild(ratings
);
131 XMLUtils::SetInt(movie
, "userrating", m_iUserRating
);
133 if (m_EpBookmark
.timeInSeconds
> 0)
135 TiXmlElement
epbookmark("episodebookmark");
136 XMLUtils::SetDouble(&epbookmark
, "position", m_EpBookmark
.timeInSeconds
);
137 if (!m_EpBookmark
.playerState
.empty())
139 TiXmlElement
playerstate("playerstate");
141 doc
.Parse(m_EpBookmark
.playerState
);
142 playerstate
.InsertEndChild(*doc
.RootElement());
143 epbookmark
.InsertEndChild(playerstate
);
145 movie
->InsertEndChild(epbookmark
);
148 XMLUtils::SetInt(movie
, "top250", m_iTop250
);
149 if (tag
== "episodedetails" || tag
== "tvshow")
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
);
156 if (tag
== "musicvideo")
158 XMLUtils::SetInt(movie
, "track", m_iTrack
);
159 XMLUtils::SetString(movie
, "album", m_strAlbum
);
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())
168 doc
.Parse(m_strPictureURL
.GetData());
169 const TiXmlNode
* thumb
= doc
.FirstChild("thumb");
172 movie
->InsertEndChild(*thumb
);
173 thumb
= thumb
->NextSibling("thumb");
176 if (m_fanart
.m_xml
.size())
179 doc
.Parse(m_fanart
.m_xml
);
180 movie
->InsertEndChild(*doc
.RootElement());
182 XMLUtils::SetString(movie
, "mpaa", m_strMPAARating
);
183 XMLUtils::SetInt(movie
, "playcount", GetPlayCount());
184 XMLUtils::SetDate(movie
, "lastplayed", m_lastPlayed
);
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
);
192 if (!m_strEpisodeGuide
.empty())
195 doc
.Parse(m_strEpisodeGuide
);
196 if (doc
.RootElement())
197 movie
->InsertEndChild(*doc
.RootElement());
199 XMLUtils::SetString(movie
, "episodeguide", m_strEpisodeGuide
);
202 XMLUtils::SetString(movie
, "id", GetUniqueID());
203 for (const auto& uniqueid
: m_uniqueIDs
)
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
);
212 movie
->InsertEndChild(uniqueID
);
214 XMLUtils::SetStringArray(movie
, "genre", m_genre
);
215 XMLUtils::SetStringArray(movie
, "country", m_country
);
216 if (!m_set
.title
.empty())
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
);
224 XMLUtils::SetStringArray(movie
, "tag", m_tags
);
225 XMLUtils::SetStringArray(movie
, "credits", m_writingCredits
);
226 XMLUtils::SetStringArray(movie
, "director", m_director
);
228 XMLUtils::SetDate(movie
, "premiered", m_premiered
);
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
);
237 if (m_streamDetails
.HasItems())
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
++)
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 XMLUtils::SetString(&stream
, "hdrtype", m_streamDetails
.GetVideoHdrType(iStream
));
252 streamdetails
.InsertEndChild(stream
);
254 for (int iStream
=1; iStream
<=m_streamDetails
.GetAudioStreamCount(); iStream
++)
256 TiXmlElement
stream("audio");
257 XMLUtils::SetString(&stream
, "codec", m_streamDetails
.GetAudioCodec(iStream
));
258 XMLUtils::SetString(&stream
, "language", m_streamDetails
.GetAudioLanguage(iStream
));
259 XMLUtils::SetInt(&stream
, "channels", m_streamDetails
.GetAudioChannels(iStream
));
260 streamdetails
.InsertEndChild(stream
);
262 for (int iStream
=1; iStream
<=m_streamDetails
.GetSubtitleStreamCount(); iStream
++)
264 TiXmlElement
stream("subtitle");
265 XMLUtils::SetString(&stream
, "language", m_streamDetails
.GetSubtitleLanguage(iStream
));
266 streamdetails
.InsertEndChild(stream
);
268 fileinfo
.InsertEndChild(streamdetails
);
269 movie
->InsertEndChild(fileinfo
);
270 } /* if has stream details */
273 for (iCast it
= m_cast
.begin(); it
!= m_cast
.end(); ++it
)
276 TiXmlElement
cast("actor");
277 TiXmlNode
*node
= movie
->InsertEndChild(cast
);
278 XMLUtils::SetString(node
, "name", it
->strName
);
279 XMLUtils::SetString(node
, "role", it
->strRole
);
280 XMLUtils::SetInt(node
, "order", it
->order
);
281 XMLUtils::SetString(node
, "thumb", it
->thumbUrl
.GetFirstUrlByType().m_url
);
283 XMLUtils::SetStringArray(movie
, "artist", m_artist
);
284 XMLUtils::SetStringArray(movie
, "showlink", m_showLink
);
286 for (const auto& namedSeason
: m_namedSeasons
)
288 TiXmlElement
season("namedseason");
289 season
.SetAttribute("number", namedSeason
.first
);
290 TiXmlText
value(namedSeason
.second
);
291 season
.InsertEndChild(value
);
292 movie
->InsertEndChild(season
);
295 TiXmlElement
resume("resume");
296 XMLUtils::SetDouble(&resume
, "position", m_resumePoint
.timeInSeconds
);
297 XMLUtils::SetDouble(&resume
, "total", m_resumePoint
.totalTimeInSeconds
);
298 if (!m_resumePoint
.playerState
.empty())
300 TiXmlElement
playerstate("playerstate");
302 doc
.Parse(m_resumePoint
.playerState
);
303 playerstate
.InsertEndChild(*doc
.RootElement());
304 resume
.InsertEndChild(playerstate
);
306 movie
->InsertEndChild(resume
);
308 XMLUtils::SetDateTime(movie
, "dateadded", m_dateAdded
);
311 movie
->InsertEndChild(*additionalNode
);
316 bool CVideoInfoTag::Load(const TiXmlElement
*element
, bool append
, bool prioritise
)
322 ParseNative(element
, prioritise
);
326 void CVideoInfoTag::Merge(CVideoInfoTag
& other
)
328 if (!other
.m_director
.empty())
329 m_director
= other
.m_director
;
330 if (!other
.m_writingCredits
.empty())
331 m_writingCredits
= other
.m_writingCredits
;
332 if (!other
.m_genre
.empty())
333 m_genre
= other
.m_genre
;
334 if (!other
.m_country
.empty())
335 m_country
= other
.m_country
;
336 if (!other
.m_strTagLine
.empty())
337 m_strTagLine
= other
.m_strTagLine
;
338 if (!other
.m_strPlotOutline
.empty())
339 m_strPlotOutline
= other
.m_strPlotOutline
;
340 if (!other
.m_strPlot
.empty())
341 m_strPlot
= other
.m_strPlot
;
342 if (other
.m_strPictureURL
.HasData())
343 m_strPictureURL
= other
.m_strPictureURL
;
344 if (!other
.m_strTitle
.empty())
345 m_strTitle
= other
.m_strTitle
;
346 if (!other
.m_strShowTitle
.empty())
347 m_strShowTitle
= other
.m_strShowTitle
;
348 if (!other
.m_strOriginalTitle
.empty())
349 m_strOriginalTitle
= other
.m_strOriginalTitle
;
350 if (!other
.m_strSortTitle
.empty())
351 m_strSortTitle
= other
.m_strSortTitle
;
352 if (other
.m_cast
.size())
353 m_cast
= other
.m_cast
;
355 if (!other
.m_set
.title
.empty())
356 m_set
.title
= other
.m_set
.title
;
358 m_set
.id
= other
.m_set
.id
;
359 if (!other
.m_set
.overview
.empty())
360 m_set
.overview
= other
.m_set
.overview
;
361 if (!other
.m_tags
.empty())
362 m_tags
= other
.m_tags
;
364 if (!other
.m_strFile
.empty())
365 m_strFile
= other
.m_strFile
;
366 if (!other
.m_strPath
.empty())
367 m_strPath
= other
.m_strPath
;
369 if (!other
.m_strMPAARating
.empty())
370 m_strMPAARating
= other
.m_strMPAARating
;
371 if (!other
.m_strFileNameAndPath
.empty())
372 m_strFileNameAndPath
= other
.m_strFileNameAndPath
;
374 if (other
.m_premiered
.IsValid())
375 SetPremiered(other
.GetPremiered());
377 if (!other
.m_strStatus
.empty())
378 m_strStatus
= other
.m_strStatus
;
379 if (!other
.m_strProductionCode
.empty())
380 m_strProductionCode
= other
.m_strProductionCode
;
382 if (other
.m_firstAired
.IsValid())
383 m_firstAired
= other
.m_firstAired
;
384 if (!other
.m_studio
.empty())
385 m_studio
= other
.m_studio
;
386 if (!other
.m_strAlbum
.empty())
387 m_strAlbum
= other
.m_strAlbum
;
388 if (!other
.m_artist
.empty())
389 m_artist
= other
.m_artist
;
390 if (!other
.m_strTrailer
.empty())
391 m_strTrailer
= other
.m_strTrailer
;
393 m_iTop250
= other
.m_iTop250
;
394 if (other
.m_iSeason
!= -1)
395 m_iSeason
= other
.m_iSeason
;
396 if (other
.m_iEpisode
!= -1)
397 m_iEpisode
= other
.m_iEpisode
;
399 if (other
.m_iIdUniqueID
!= -1)
400 m_iIdUniqueID
= other
.m_iIdUniqueID
;
401 if (other
.m_uniqueIDs
.size())
403 m_uniqueIDs
= other
.m_uniqueIDs
;
404 m_strDefaultUniqueID
= other
.m_strDefaultUniqueID
;
406 if (other
.m_iSpecialSortSeason
!= -1)
407 m_iSpecialSortSeason
= other
.m_iSpecialSortSeason
;
408 if (other
.m_iSpecialSortEpisode
!= -1)
409 m_iSpecialSortEpisode
= other
.m_iSpecialSortEpisode
;
411 if (!other
.m_ratings
.empty())
413 m_ratings
= other
.m_ratings
;
414 m_strDefaultRating
= other
.m_strDefaultRating
;
416 if (other
.m_iIdRating
!= -1)
417 m_iIdRating
= other
.m_iIdRating
;
418 if (other
.m_iUserRating
)
419 m_iUserRating
= other
.m_iUserRating
;
421 if (other
.m_iDbId
!= -1)
422 m_iDbId
= other
.m_iDbId
;
423 if (other
.m_iFileId
!= -1)
424 m_iFileId
= other
.m_iFileId
;
425 if (other
.m_iBookmarkId
!= -1)
426 m_iBookmarkId
= other
.m_iBookmarkId
;
427 if (other
.m_iTrack
!= -1)
428 m_iTrack
= other
.m_iTrack
;
430 if (other
.m_fanart
.GetNumFanarts())
431 m_fanart
= other
.m_fanart
;
433 if (other
.m_duration
)
434 m_duration
= other
.m_duration
;
435 if (other
.m_lastPlayed
.IsValid())
436 m_lastPlayed
= other
.m_lastPlayed
;
438 if (!other
.m_showLink
.empty())
439 m_showLink
= other
.m_showLink
;
440 if (other
.m_namedSeasons
.size())
441 m_namedSeasons
= other
.m_namedSeasons
;
442 if (other
.m_streamDetails
.HasItems())
443 m_streamDetails
= other
.m_streamDetails
;
444 if (other
.IsPlayCountSet())
445 SetPlayCount(other
.GetPlayCount());
447 if (other
.m_EpBookmark
.IsSet())
448 m_EpBookmark
= other
.m_EpBookmark
;
450 if (!other
.m_basePath
.empty())
451 m_basePath
= other
.m_basePath
;
452 if (other
.m_parentPathID
!= -1)
453 m_parentPathID
= other
.m_parentPathID
;
454 if (other
.GetResumePoint().IsSet())
455 SetResumePoint(other
.GetResumePoint());
456 if (other
.m_iIdShow
!= -1)
457 m_iIdShow
= other
.m_iIdShow
;
458 if (other
.m_iIdSeason
!= -1)
459 m_iIdSeason
= other
.m_iIdSeason
;
461 if (other
.m_dateAdded
.IsValid())
462 m_dateAdded
= other
.m_dateAdded
;
464 if (!other
.m_type
.empty())
465 m_type
= other
.m_type
;
467 if (other
.m_relevance
!= -1)
468 m_relevance
= other
.m_relevance
;
469 if (other
.m_parsedDetails
)
470 m_parsedDetails
= other
.m_parsedDetails
;
471 if (other
.m_coverArt
.size())
472 m_coverArt
= other
.m_coverArt
;
473 if (other
.m_year
!= -1)
474 m_year
= other
.m_year
;
477 void CVideoInfoTag::Archive(CArchive
& ar
)
482 ar
<< m_writingCredits
;
486 ar
<< m_strPlotOutline
;
488 ar
<< m_strPictureURL
.GetData();
489 ar
<< m_fanart
.m_xml
;
491 ar
<< m_strSortTitle
;
494 ar
<< (int)m_cast
.size();
495 for (unsigned int i
=0;i
<m_cast
.size();++i
)
497 ar
<< m_cast
[i
].strName
;
498 ar
<< m_cast
[i
].strRole
;
499 ar
<< m_cast
[i
].order
;
500 ar
<< m_cast
[i
].thumb
;
501 ar
<< m_cast
[i
].thumbUrl
.GetData();
506 ar
<< m_set
.overview
;
511 ar
<< m_strMPAARating
;
512 ar
<< m_strFileNameAndPath
;
513 ar
<< m_strOriginalTitle
;
514 ar
<< m_strEpisodeGuide
;
516 ar
<< m_bHasPremiered
;
518 ar
<< m_strProductionCode
;
520 ar
<< m_strShowTitle
;
523 ar
<< GetPlayCount();
528 ar
<< (int)m_uniqueIDs
.size();
529 for (const auto& i
: m_uniqueIDs
)
532 ar
<< (i
.first
== m_strDefaultUniqueID
);
535 ar
<< (int)m_ratings
.size();
536 for (const auto& i
: m_ratings
)
539 ar
<< (i
.first
== m_strDefaultRating
);
540 ar
<< i
.second
.rating
;
541 ar
<< i
.second
.votes
;
546 ar
<< m_iSpecialSortSeason
;
547 ar
<< m_iSpecialSortEpisode
;
550 ar
<< dynamic_cast<IArchivable
&>(m_streamDetails
);
552 ar
<< static_cast<int>(m_namedSeasons
.size());
553 for (const auto& namedSeason
: m_namedSeasons
)
555 ar
<< namedSeason
.first
;
556 ar
<< namedSeason
.second
;
558 ar
<< m_EpBookmark
.playerState
;
559 ar
<< m_EpBookmark
.timeInSeconds
;
561 ar
<< m_parentPathID
;
562 ar
<< m_resumePoint
.timeInSeconds
;
563 ar
<< m_resumePoint
.totalTimeInSeconds
;
564 ar
<< m_resumePoint
.playerState
;
566 ar
<< m_dateAdded
.GetAsDBDateTime();
569 ar
<< m_coverArt
.size();
570 for (auto& it
: m_coverArt
)
576 ar
>> m_writingCredits
;
580 ar
>> m_strPlotOutline
;
584 m_strPictureURL
.SetData(data
);
585 ar
>> m_fanart
.m_xml
;
587 ar
>> m_strSortTitle
;
592 m_cast
.reserve(iCastSize
);
593 for (int i
=0;i
<iCastSize
;++i
)
602 info
.thumbUrl
.ParseFromData(strXml
);
603 m_cast
.push_back(info
);
608 ar
>> m_set
.overview
;
613 ar
>> m_strMPAARating
;
614 ar
>> m_strFileNameAndPath
;
615 ar
>> m_strOriginalTitle
;
616 ar
>> m_strEpisodeGuide
;
618 ar
>> m_bHasPremiered
;
620 ar
>> m_strProductionCode
;
622 ar
>> m_strShowTitle
;
632 for (int i
= 0; i
< iUniqueIDSize
; ++i
)
636 bool defaultUniqueID
;
638 ar
>> defaultUniqueID
;
640 SetUniqueID(value
, name
);
642 m_strDefaultUniqueID
= name
;
646 for (int i
= 0; i
< iRatingSize
; ++i
)
655 SetRating(rating
, name
);
657 m_strDefaultRating
= name
;
662 ar
>> m_iSpecialSortSeason
;
663 ar
>> m_iSpecialSortEpisode
;
666 ar
>> dynamic_cast<IArchivable
&>(m_streamDetails
);
670 ar
>> namedSeasonSize
;
671 for (int i
= 0; i
< namedSeasonSize
; ++i
)
675 std::string seasonName
;
677 m_namedSeasons
.insert(std::make_pair(seasonNumber
, seasonName
));
679 ar
>> m_EpBookmark
.playerState
;
680 ar
>> m_EpBookmark
.timeInSeconds
;
682 ar
>> m_parentPathID
;
683 ar
>> m_resumePoint
.timeInSeconds
;
684 ar
>> m_resumePoint
.totalTimeInSeconds
;
685 ar
>> m_resumePoint
.playerState
;
688 std::string dateAdded
;
690 m_dateAdded
.SetFromDBDateTime(dateAdded
);
695 m_coverArt
.resize(size
);
696 for (size_t i
= 0; i
< size
; ++i
)
701 void CVideoInfoTag::Serialize(CVariant
& value
) const
703 value
["director"] = m_director
;
704 value
["writer"] = m_writingCredits
;
705 value
["genre"] = m_genre
;
706 value
["country"] = m_country
;
707 value
["tagline"] = m_strTagLine
;
708 value
["plotoutline"] = m_strPlotOutline
;
709 value
["plot"] = m_strPlot
;
710 value
["title"] = m_strTitle
;
711 value
["votes"] = std::to_string(GetRating().votes
);
712 value
["studio"] = m_studio
;
713 value
["trailer"] = m_strTrailer
;
714 value
["cast"] = CVariant(CVariant::VariantTypeArray
);
715 for (unsigned int i
= 0; i
< m_cast
.size(); ++i
)
718 actor
["name"] = m_cast
[i
].strName
;
719 actor
["role"] = m_cast
[i
].strRole
;
720 actor
["order"] = m_cast
[i
].order
;
721 if (!m_cast
[i
].thumb
.empty())
722 actor
["thumbnail"] = CTextureUtils::GetWrappedImageURL(m_cast
[i
].thumb
);
723 value
["cast"].push_back(actor
);
725 value
["set"] = m_set
.title
;
726 value
["setid"] = m_set
.id
;
727 value
["setoverview"] = m_set
.overview
;
728 value
["tag"] = m_tags
;
729 value
["runtime"] = GetDuration();
730 value
["file"] = m_strFile
;
731 value
["path"] = m_strPath
;
732 value
["imdbnumber"] = GetUniqueID();
733 value
["mpaa"] = m_strMPAARating
;
734 value
["filenameandpath"] = m_strFileNameAndPath
;
735 value
["originaltitle"] = m_strOriginalTitle
;
736 value
["sorttitle"] = m_strSortTitle
;
737 value
["episodeguide"] = m_strEpisodeGuide
;
738 value
["premiered"] = m_premiered
.IsValid() ? m_premiered
.GetAsDBDate() : StringUtils::Empty
;
739 value
["status"] = m_strStatus
;
740 value
["productioncode"] = m_strProductionCode
;
741 value
["firstaired"] = m_firstAired
.IsValid() ? m_firstAired
.GetAsDBDate() : StringUtils::Empty
;
742 value
["showtitle"] = m_strShowTitle
;
743 value
["album"] = m_strAlbum
;
744 value
["artist"] = m_artist
;
745 value
["playcount"] = GetPlayCount();
746 value
["lastplayed"] = m_lastPlayed
.IsValid() ? m_lastPlayed
.GetAsDBDateTime() : StringUtils::Empty
;
747 value
["top250"] = m_iTop250
;
748 value
["year"] = GetYear();
749 value
["season"] = m_iSeason
;
750 value
["episode"] = m_iEpisode
;
751 for (const auto& i
: m_uniqueIDs
)
752 value
["uniqueid"][i
.first
] = i
.second
;
754 value
["rating"] = GetRating().rating
;
755 CVariant ratings
= CVariant(CVariant::VariantTypeObject
);
756 for (const auto& i
: m_ratings
)
759 rating
["rating"] = i
.second
.rating
;
760 rating
["votes"] = i
.second
.votes
;
761 rating
["default"] = i
.first
== m_strDefaultRating
;
763 ratings
[i
.first
] = rating
;
765 value
["ratings"] = ratings
;
766 value
["userrating"] = m_iUserRating
;
767 value
["dbid"] = m_iDbId
;
768 value
["fileid"] = m_iFileId
;
769 value
["track"] = m_iTrack
;
770 value
["showlink"] = m_showLink
;
771 m_streamDetails
.Serialize(value
["streamdetails"]);
772 CVariant resume
= CVariant(CVariant::VariantTypeObject
);
773 resume
["position"] = m_resumePoint
.timeInSeconds
;
774 resume
["total"] = m_resumePoint
.totalTimeInSeconds
;
775 value
["resume"] = resume
;
776 value
["tvshowid"] = m_iIdShow
;
777 value
["dateadded"] = m_dateAdded
.IsValid() ? m_dateAdded
.GetAsDBDateTime() : StringUtils::Empty
;
778 value
["type"] = m_type
;
779 value
["seasonid"] = m_iIdSeason
;
780 value
["specialsortseason"] = m_iSpecialSortSeason
;
781 value
["specialsortepisode"] = m_iSpecialSortEpisode
;
784 void CVideoInfoTag::ToSortable(SortItem
& sortable
, Field field
) const
788 case FieldDirector
: sortable
[FieldDirector
] = m_director
; break;
789 case FieldWriter
: sortable
[FieldWriter
] = m_writingCredits
; break;
790 case FieldGenre
: sortable
[FieldGenre
] = m_genre
; break;
791 case FieldCountry
: sortable
[FieldCountry
] = m_country
; break;
792 case FieldTagline
: sortable
[FieldTagline
] = m_strTagLine
; break;
793 case FieldPlotOutline
: sortable
[FieldPlotOutline
] = m_strPlotOutline
; break;
794 case FieldPlot
: sortable
[FieldPlot
] = m_strPlot
; break;
797 // make sure not to overwrite an existing title with an empty one
798 std::string title
= m_strTitle
;
799 if (!title
.empty() || sortable
.find(FieldTitle
) == sortable
.end())
800 sortable
[FieldTitle
] = title
;
803 case FieldVotes
: sortable
[FieldVotes
] = GetRating().votes
; break;
804 case FieldStudio
: sortable
[FieldStudio
] = m_studio
; break;
805 case FieldTrailer
: sortable
[FieldTrailer
] = m_strTrailer
; break;
806 case FieldSet
: sortable
[FieldSet
] = m_set
.title
; break;
807 case FieldTime
: sortable
[FieldTime
] = GetDuration(); break;
808 case FieldFilename
: sortable
[FieldFilename
] = m_strFile
; break;
809 case FieldMPAA
: sortable
[FieldMPAA
] = m_strMPAARating
; break;
812 // make sure not to overwrite an existing path with an empty one
813 std::string path
= GetPath();
814 if (!path
.empty() || sortable
.find(FieldPath
) == sortable
.end())
815 sortable
[FieldPath
] = path
;
820 // seasons with a custom name/title need special handling as they should be sorted by season number
821 if (m_type
== MediaTypeSeason
&& !m_strSortTitle
.empty())
822 sortable
[FieldSortTitle
] = StringUtils::Format(g_localizeStrings
.Get(20358), m_iSeason
);
824 sortable
[FieldSortTitle
] = m_strSortTitle
;
827 case FieldOriginalTitle
:
829 // seasons with a custom name/title need special handling as they should be sorted by season number
830 if (m_type
== MediaTypeSeason
&& !m_strOriginalTitle
.empty())
831 sortable
[FieldOriginalTitle
] =
832 StringUtils::Format(g_localizeStrings
.Get(20358).c_str(), m_iSeason
);
834 sortable
[FieldOriginalTitle
] = m_strOriginalTitle
;
837 case FieldTvShowStatus
: sortable
[FieldTvShowStatus
] = m_strStatus
; break;
838 case FieldProductionCode
: sortable
[FieldProductionCode
] = m_strProductionCode
; break;
839 case FieldAirDate
: sortable
[FieldAirDate
] = m_firstAired
.IsValid() ? m_firstAired
.GetAsDBDate() : (m_premiered
.IsValid() ? m_premiered
.GetAsDBDate() : StringUtils::Empty
); break;
840 case FieldTvShowTitle
: sortable
[FieldTvShowTitle
] = m_strShowTitle
; break;
841 case FieldAlbum
: sortable
[FieldAlbum
] = m_strAlbum
; break;
842 case FieldArtist
: sortable
[FieldArtist
] = m_artist
; break;
843 case FieldPlaycount
: sortable
[FieldPlaycount
] = GetPlayCount(); break;
844 case FieldLastPlayed
: sortable
[FieldLastPlayed
] = m_lastPlayed
.IsValid() ? m_lastPlayed
.GetAsDBDateTime() : StringUtils::Empty
; break;
845 case FieldTop250
: sortable
[FieldTop250
] = m_iTop250
; break;
846 case FieldYear
: sortable
[FieldYear
] = GetYear(); break;
847 case FieldSeason
: sortable
[FieldSeason
] = m_iSeason
; break;
848 case FieldEpisodeNumber
: sortable
[FieldEpisodeNumber
] = m_iEpisode
; break;
849 case FieldNumberOfEpisodes
: sortable
[FieldNumberOfEpisodes
] = m_iEpisode
; break;
850 case FieldNumberOfWatchedEpisodes
: sortable
[FieldNumberOfWatchedEpisodes
] = m_iEpisode
; break;
851 case FieldEpisodeNumberSpecialSort
: sortable
[FieldEpisodeNumberSpecialSort
] = m_iSpecialSortEpisode
; break;
852 case FieldSeasonSpecialSort
: sortable
[FieldSeasonSpecialSort
] = m_iSpecialSortSeason
; break;
853 case FieldRating
: sortable
[FieldRating
] = GetRating().rating
; break;
854 case FieldUserRating
: sortable
[FieldUserRating
] = m_iUserRating
; break;
855 case FieldId
: sortable
[FieldId
] = m_iDbId
; break;
856 case FieldTrackNumber
: sortable
[FieldTrackNumber
] = m_iTrack
; break;
857 case FieldTag
: sortable
[FieldTag
] = m_tags
; break;
859 case FieldVideoResolution
: sortable
[FieldVideoResolution
] = m_streamDetails
.GetVideoHeight(); break;
860 case FieldVideoAspectRatio
: sortable
[FieldVideoAspectRatio
] = m_streamDetails
.GetVideoAspect(); break;
861 case FieldVideoCodec
: sortable
[FieldVideoCodec
] = m_streamDetails
.GetVideoCodec(); break;
862 case FieldStereoMode
: sortable
[FieldStereoMode
] = m_streamDetails
.GetStereoMode(); break;
864 case FieldAudioChannels
: sortable
[FieldAudioChannels
] = m_streamDetails
.GetAudioChannels(); break;
865 case FieldAudioCodec
: sortable
[FieldAudioCodec
] = m_streamDetails
.GetAudioCodec(); break;
866 case FieldAudioLanguage
: sortable
[FieldAudioLanguage
] = m_streamDetails
.GetAudioLanguage(); break;
868 case FieldSubtitleLanguage
: sortable
[FieldSubtitleLanguage
] = m_streamDetails
.GetSubtitleLanguage(); break;
870 case FieldInProgress
: sortable
[FieldInProgress
] = m_resumePoint
.IsPartWay(); break;
871 case FieldDateAdded
: sortable
[FieldDateAdded
] = m_dateAdded
.IsValid() ? m_dateAdded
.GetAsDBDateTime() : StringUtils::Empty
; break;
872 case FieldMediaType
: sortable
[FieldMediaType
] = m_type
; break;
873 case FieldRelevance
: sortable
[FieldRelevance
] = m_relevance
; break;
878 const CRating
CVideoInfoTag::GetRating(std::string type
) const
881 type
= m_strDefaultRating
;
883 const auto& rating
= m_ratings
.find(type
);
884 if (rating
== m_ratings
.end())
887 return rating
->second
;
890 const std::string
& CVideoInfoTag::GetDefaultRating() const
892 return m_strDefaultRating
;
895 bool CVideoInfoTag::HasYear() const
897 return m_year
> 0 || m_firstAired
.IsValid() || m_premiered
.IsValid();
900 int CVideoInfoTag::GetYear() const
904 if (m_firstAired
.IsValid())
905 return GetFirstAired().GetYear();
906 if (m_premiered
.IsValid())
907 return GetPremiered().GetYear();
911 bool CVideoInfoTag::HasPremiered() const
913 return m_bHasPremiered
;
916 const CDateTime
& CVideoInfoTag::GetPremiered() const
921 const CDateTime
& CVideoInfoTag::GetFirstAired() const
926 const std::string
CVideoInfoTag::GetUniqueID(std::string type
) const
929 type
= m_strDefaultUniqueID
;
931 const auto& uniqueid
= m_uniqueIDs
.find(type
);
932 if (uniqueid
== m_uniqueIDs
.end())
935 return uniqueid
->second
;
938 const std::map
<std::string
, std::string
>& CVideoInfoTag::GetUniqueIDs() const
943 const std::string
& CVideoInfoTag::GetDefaultUniqueID() const
945 return m_strDefaultUniqueID
;
948 bool CVideoInfoTag::HasUniqueID() const
950 return !m_uniqueIDs
.empty();
953 const std::string
CVideoInfoTag::GetCast(bool bIncludeRole
/*= false*/) const
955 std::string strLabel
;
956 for (iCast it
= m_cast
.begin(); it
!= m_cast
.end(); ++it
)
958 std::string character
;
959 if (it
->strRole
.empty() || !bIncludeRole
)
960 character
= StringUtils::Format("{}\n", it
->strName
);
963 StringUtils::Format("{} {} {}\n", it
->strName
, g_localizeStrings
.Get(20347), it
->strRole
);
964 strLabel
+= character
;
966 return StringUtils::TrimRight(strLabel
, "\n");
969 void CVideoInfoTag::ParseNative(const TiXmlElement
* movie
, bool prioritise
)
974 if (XMLUtils::GetString(movie
, "title", value
))
977 if (XMLUtils::GetString(movie
, "originaltitle", value
))
978 SetOriginalTitle(value
);
980 if (XMLUtils::GetString(movie
, "showtitle", value
))
983 if (XMLUtils::GetString(movie
, "sorttitle", value
))
986 const TiXmlElement
* node
= movie
->FirstChildElement("ratings");
989 for (const TiXmlElement
* child
= node
->FirstChildElement("rating"); child
!= nullptr; child
= child
->NextSiblingElement("rating"))
993 if (child
->QueryStringAttribute("name", &name
) != TIXML_SUCCESS
)
995 XMLUtils::GetFloat(child
, "value", r
.rating
);
996 if (XMLUtils::GetString(child
, "votes", value
))
997 r
.votes
= StringUtils::ReturnDigits(value
);
999 if ((child
->QueryIntAttribute("max", &max_value
) == TIXML_SUCCESS
) && max_value
>= 1)
1000 r
.rating
= r
.rating
/ max_value
* 10; // Normalise the Movie Rating to between 1 and 10
1002 bool isDefault
= false;
1003 // guard against assert in tinyxml
1004 const char* rAtt
= child
->Attribute("default", static_cast<int*>(nullptr));
1005 if (rAtt
&& strlen(rAtt
) != 0 &&
1006 (child
->QueryBoolAttribute("default", &isDefault
) == TIXML_SUCCESS
) && isDefault
)
1007 m_strDefaultRating
= name
;
1010 else if (XMLUtils::GetFloat(movie
, "rating", fValue
))
1012 CRating
r(fValue
, 0);
1013 if (XMLUtils::GetString(movie
, "votes", value
))
1014 r
.votes
= StringUtils::ReturnDigits(value
);
1016 const TiXmlElement
* rElement
= movie
->FirstChildElement("rating");
1017 if (rElement
&& (rElement
->QueryIntAttribute("max", &max_value
) == TIXML_SUCCESS
) && max_value
>= 1)
1018 r
.rating
= r
.rating
/ max_value
* 10; // Normalise the Movie Rating to between 1 and 10
1019 SetRating(r
, "default");
1020 m_strDefaultRating
= "default";
1022 XMLUtils::GetInt(movie
, "userrating", m_iUserRating
);
1024 const TiXmlElement
*epbookmark
= movie
->FirstChildElement("episodebookmark");
1027 XMLUtils::GetDouble(epbookmark
, "position", m_EpBookmark
.timeInSeconds
);
1028 const TiXmlElement
*playerstate
= epbookmark
->FirstChildElement("playerstate");
1031 const TiXmlElement
*value
= playerstate
->FirstChildElement();
1033 m_EpBookmark
.playerState
<< *value
;
1037 XMLUtils::GetDouble(movie
, "epbookmark", m_EpBookmark
.timeInSeconds
);
1040 const TiXmlElement
* urElement
= movie
->FirstChildElement("userrating");
1041 if (urElement
&& (urElement
->QueryIntAttribute("max", &max_value
) == TIXML_SUCCESS
) && max_value
>= 1)
1042 m_iUserRating
= m_iUserRating
/ max_value
* 10; // Normalise the user Movie Rating to between 1 and 10
1043 XMLUtils::GetInt(movie
, "top250", m_iTop250
);
1044 XMLUtils::GetInt(movie
, "season", m_iSeason
);
1045 XMLUtils::GetInt(movie
, "episode", m_iEpisode
);
1046 XMLUtils::GetInt(movie
, "track", m_iTrack
);
1048 XMLUtils::GetInt(movie
, "displayseason", m_iSpecialSortSeason
);
1049 XMLUtils::GetInt(movie
, "displayepisode", m_iSpecialSortEpisode
);
1051 XMLUtils::GetInt(movie
, "displayafterseason",after
);
1054 m_iSpecialSortSeason
= after
;
1055 m_iSpecialSortEpisode
= 0x1000; // should be more than any realistic episode number
1058 if (XMLUtils::GetString(movie
, "outline", value
))
1059 SetPlotOutline(value
);
1061 if (XMLUtils::GetString(movie
, "plot", value
))
1064 if (XMLUtils::GetString(movie
, "tagline", value
))
1068 if (XMLUtils::GetString(movie
, "runtime", value
) && !value
.empty())
1069 m_duration
= GetDurationFromMinuteString(StringUtils::Trim(value
));
1071 if (XMLUtils::GetString(movie
, "mpaa", value
))
1072 SetMPAARating(value
);
1074 XMLUtils::GetInt(movie
, "playcount", m_playCount
);
1075 XMLUtils::GetDate(movie
, "lastplayed", m_lastPlayed
);
1077 if (XMLUtils::GetString(movie
, "file", value
))
1080 if (XMLUtils::GetString(movie
, "path", value
))
1083 const TiXmlElement
* uniqueid
= movie
->FirstChildElement("uniqueid");
1084 if (uniqueid
== nullptr)
1086 if (XMLUtils::GetString(movie
, "id", value
))
1091 for (; uniqueid
!= nullptr; uniqueid
= uniqueid
->NextSiblingElement("uniqueid"))
1093 if (uniqueid
->FirstChild())
1095 if (uniqueid
->QueryStringAttribute("type", &value
) == TIXML_SUCCESS
)
1096 SetUniqueID(uniqueid
->FirstChild()->ValueStr(), value
);
1098 SetUniqueID(uniqueid
->FirstChild()->ValueStr());
1100 if (m_strDefaultUniqueID
== "unknown" &&
1101 (uniqueid
->QueryBoolAttribute("default", &isDefault
) == TIXML_SUCCESS
) && isDefault
)
1103 m_strDefaultUniqueID
= value
;
1109 if (XMLUtils::GetString(movie
, "filenameandpath", value
))
1110 SetFileNameAndPath(value
);
1112 if (XMLUtils::GetDate(movie
, "premiered", m_premiered
))
1114 m_bHasPremiered
= true;
1119 if (XMLUtils::GetInt(movie
, "year", year
))
1123 if (XMLUtils::GetString(movie
, "status", value
))
1126 if (XMLUtils::GetString(movie
, "code", value
))
1127 SetProductionCode(value
);
1129 XMLUtils::GetDate(movie
, "aired", m_firstAired
);
1131 if (XMLUtils::GetString(movie
, "album", value
))
1134 if (XMLUtils::GetString(movie
, "trailer", value
))
1137 if (XMLUtils::GetString(movie
, "basepath", value
))
1140 // make sure the picture URLs have been parsed
1141 m_strPictureURL
.Parse();
1142 size_t iThumbCount
= m_strPictureURL
.GetUrls().size();
1143 std::string xmlAdd
= m_strPictureURL
.GetData();
1145 const TiXmlElement
* thumb
= movie
->FirstChildElement("thumb");
1148 m_strPictureURL
.ParseAndAppendUrl(thumb
);
1153 xmlAdd
= temp
+xmlAdd
;
1155 thumb
= thumb
->NextSiblingElement("thumb");
1158 // prioritise thumbs from nfos
1159 if (prioritise
&& iThumbCount
&& iThumbCount
!= m_strPictureURL
.GetUrls().size())
1161 auto thumbUrls
= m_strPictureURL
.GetUrls();
1162 rotate(thumbUrls
.begin(), thumbUrls
.begin() + iThumbCount
, thumbUrls
.end());
1163 m_strPictureURL
.SetUrls(thumbUrls
);
1164 m_strPictureURL
.SetData(xmlAdd
);
1167 const std::string itemSeparator
= CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator
;
1169 std::vector
<std::string
> genres(m_genre
);
1170 if (XMLUtils::GetStringArray(movie
, "genre", genres
, prioritise
, itemSeparator
))
1173 std::vector
<std::string
> country(m_country
);
1174 if (XMLUtils::GetStringArray(movie
, "country", country
, prioritise
, itemSeparator
))
1175 SetCountry(country
);
1177 std::vector
<std::string
> credits(m_writingCredits
);
1178 if (XMLUtils::GetStringArray(movie
, "credits", credits
, prioritise
, itemSeparator
))
1179 SetWritingCredits(credits
);
1181 std::vector
<std::string
> director(m_director
);
1182 if (XMLUtils::GetStringArray(movie
, "director", director
, prioritise
, itemSeparator
))
1183 SetDirector(director
);
1185 std::vector
<std::string
> showLink(m_showLink
);
1186 if (XMLUtils::GetStringArray(movie
, "showlink", showLink
, prioritise
, itemSeparator
))
1187 SetShowLink(showLink
);
1189 const TiXmlElement
* namedSeason
= movie
->FirstChildElement("namedseason");
1190 while (namedSeason
!= nullptr)
1192 if (namedSeason
->FirstChild() != nullptr)
1195 std::string seasonName
= namedSeason
->FirstChild()->ValueStr();
1196 if (!seasonName
.empty() &&
1197 namedSeason
->Attribute("number", &seasonNumber
) != nullptr)
1198 m_namedSeasons
.insert(std::make_pair(seasonNumber
, seasonName
));
1201 namedSeason
= namedSeason
->NextSiblingElement("namedseason");
1205 node
= movie
->FirstChildElement("actor");
1206 if (node
&& node
->FirstChild() && prioritise
)
1210 const TiXmlNode
*actor
= node
->FirstChild("name");
1211 if (actor
&& actor
->FirstChild())
1214 info
.strName
= actor
->FirstChild()->Value();
1216 if (XMLUtils::GetString(node
, "role", value
))
1217 info
.strRole
= StringUtils::Trim(value
);
1219 XMLUtils::GetInt(node
, "order", info
.order
);
1220 const TiXmlElement
* thumb
= node
->FirstChildElement("thumb");
1223 info
.thumbUrl
.ParseAndAppendUrl(thumb
);
1224 thumb
= thumb
->NextSiblingElement("thumb");
1226 const char* clear
=node
->Attribute("clear");
1227 if (clear
&& StringUtils::CompareNoCase(clear
, "true"))
1229 m_cast
.push_back(info
);
1231 node
= node
->NextSiblingElement("actor");
1234 // Pre-Jarvis NFO file:
1236 if (XMLUtils::GetString(movie
, "set", value
))
1239 // <set><name>A set</name><overview>A set with a number of movies...</overview></set>
1240 node
= movie
->FirstChildElement("set");
1244 if (XMLUtils::GetString(node
, "name", value
))
1247 if (XMLUtils::GetString(node
, "overview", value
))
1248 SetSetOverview(value
);
1252 std::vector
<std::string
> tags(m_tags
);
1253 if (XMLUtils::GetStringArray(movie
, "tag", tags
, prioritise
, itemSeparator
))
1256 std::vector
<std::string
> studio(m_studio
);
1257 if (XMLUtils::GetStringArray(movie
, "studio", studio
, prioritise
, itemSeparator
))
1261 std::vector
<std::string
> artist(m_artist
);
1262 node
= movie
->FirstChildElement("artist");
1263 if (node
&& node
->FirstChild() && prioritise
)
1267 const TiXmlNode
* pNode
= node
->FirstChild("name");
1268 const char* pValue
=NULL
;
1269 if (pNode
&& pNode
->FirstChild())
1270 pValue
= pNode
->FirstChild()->Value();
1271 else if (node
->FirstChild())
1272 pValue
= node
->FirstChild()->Value();
1275 const char* clear
=node
->Attribute("clear");
1276 if (clear
&& StringUtils::CompareNoCase(clear
, "true") == 0)
1278 std::vector
<std::string
> newArtists
= StringUtils::Split(pValue
, itemSeparator
);
1279 artist
.insert(artist
.end(), newArtists
.begin(), newArtists
.end());
1281 node
= node
->NextSiblingElement("artist");
1285 node
= movie
->FirstChildElement("fileinfo");
1288 // Try to pull from fileinfo/streamdetails/[video|audio|subtitle]
1289 const TiXmlNode
*nodeStreamDetails
= node
->FirstChild("streamdetails");
1290 if (nodeStreamDetails
)
1292 const TiXmlNode
*nodeDetail
= NULL
;
1293 while ((nodeDetail
= nodeStreamDetails
->IterateChildren("audio", nodeDetail
)))
1295 CStreamDetailAudio
*p
= new CStreamDetailAudio();
1296 if (XMLUtils::GetString(nodeDetail
, "codec", value
))
1297 p
->m_strCodec
= StringUtils::Trim(value
);
1299 if (XMLUtils::GetString(nodeDetail
, "language", value
))
1300 p
->m_strLanguage
= StringUtils::Trim(value
);
1302 XMLUtils::GetInt(nodeDetail
, "channels", p
->m_iChannels
);
1303 StringUtils::ToLower(p
->m_strCodec
);
1304 StringUtils::ToLower(p
->m_strLanguage
);
1305 m_streamDetails
.AddStream(p
);
1308 while ((nodeDetail
= nodeStreamDetails
->IterateChildren("video", nodeDetail
)))
1310 CStreamDetailVideo
*p
= new CStreamDetailVideo();
1311 if (XMLUtils::GetString(nodeDetail
, "codec", value
))
1312 p
->m_strCodec
= StringUtils::Trim(value
);
1314 XMLUtils::GetFloat(nodeDetail
, "aspect", p
->m_fAspect
);
1315 XMLUtils::GetInt(nodeDetail
, "width", p
->m_iWidth
);
1316 XMLUtils::GetInt(nodeDetail
, "height", p
->m_iHeight
);
1317 XMLUtils::GetInt(nodeDetail
, "durationinseconds", p
->m_iDuration
);
1318 if (XMLUtils::GetString(nodeDetail
, "stereomode", value
))
1319 p
->m_strStereoMode
= StringUtils::Trim(value
);
1320 if (XMLUtils::GetString(nodeDetail
, "language", value
))
1321 p
->m_strLanguage
= StringUtils::Trim(value
);
1322 if (XMLUtils::GetString(nodeDetail
, "hdrtype", value
))
1323 p
->m_strHdrType
= StringUtils::Trim(value
);
1325 StringUtils::ToLower(p
->m_strCodec
);
1326 StringUtils::ToLower(p
->m_strStereoMode
);
1327 StringUtils::ToLower(p
->m_strLanguage
);
1328 StringUtils::ToLower(p
->m_strHdrType
);
1329 m_streamDetails
.AddStream(p
);
1332 while ((nodeDetail
= nodeStreamDetails
->IterateChildren("subtitle", nodeDetail
)))
1334 CStreamDetailSubtitle
*p
= new CStreamDetailSubtitle();
1335 if (XMLUtils::GetString(nodeDetail
, "language", value
))
1336 p
->m_strLanguage
= StringUtils::Trim(value
);
1337 StringUtils::ToLower(p
->m_strLanguage
);
1338 m_streamDetails
.AddStream(p
);
1341 m_streamDetails
.DetermineBestStreams();
1344 if (m_strEpisodeGuide
.empty())
1346 const TiXmlElement
* epguide
= movie
->FirstChildElement("episodeguide");
1349 // DEPRECIATE ME - support for old XML-encoded <episodeguide> blocks.
1350 if (epguide
->FirstChild() &&
1351 StringUtils::CompareNoCase("<episodeguide", epguide
->FirstChild()->Value(), 13) == 0)
1353 m_strEpisodeGuide
= epguide
->FirstChild()->Value();
1357 std::stringstream stream
;
1359 m_strEpisodeGuide
= stream
.str();
1365 const TiXmlElement
*fanart
= movie
->FirstChildElement("fanart");
1368 // we prioritise mixed-mode nfo's with fanart set
1373 m_fanart
.m_xml
= temp
+m_fanart
.m_xml
;
1376 m_fanart
.m_xml
<< *fanart
;
1381 const TiXmlNode
*resume
= movie
->FirstChild("resume");
1384 XMLUtils::GetDouble(resume
, "position", m_resumePoint
.timeInSeconds
);
1385 XMLUtils::GetDouble(resume
, "total", m_resumePoint
.totalTimeInSeconds
);
1386 const TiXmlElement
*playerstate
= resume
->FirstChildElement("playerstate");
1389 const TiXmlElement
*value
= playerstate
->FirstChildElement();
1391 m_resumePoint
.playerState
<< *value
;
1395 XMLUtils::GetDateTime(movie
, "dateadded", m_dateAdded
);
1398 bool CVideoInfoTag::HasStreamDetails() const
1400 return m_streamDetails
.HasItems();
1403 bool CVideoInfoTag::IsEmpty() const
1405 return (m_strTitle
.empty() &&
1406 m_strFile
.empty() &&
1410 void CVideoInfoTag::SetDuration(int duration
)
1412 m_duration
= duration
;
1415 unsigned int CVideoInfoTag::GetDuration() const
1418 Prefer the duration from the stream if it isn't too
1419 small (60%) compared to the duration from the tag.
1421 unsigned int duration
= m_streamDetails
.GetVideoDuration();
1422 if (duration
> m_duration
* 0.6)
1428 unsigned int CVideoInfoTag::GetStaticDuration() const
1433 unsigned int CVideoInfoTag::GetDurationFromMinuteString(const std::string
&runtime
)
1435 unsigned int duration
= (unsigned int)str2uint64(runtime
);
1437 { // failed for some reason, or zero
1438 duration
= strtoul(runtime
.c_str(), NULL
, 10);
1439 CLog::Log(LOGWARNING
, "{} <runtime> should be in minutes. Interpreting '{}' as {} minutes",
1440 __FUNCTION__
, runtime
, duration
);
1445 void CVideoInfoTag::SetBasePath(std::string basePath
)
1447 m_basePath
= Trim(std::move(basePath
));
1450 void CVideoInfoTag::SetDirector(std::vector
<std::string
> director
)
1452 m_director
= Trim(std::move(director
));
1455 void CVideoInfoTag::SetWritingCredits(std::vector
<std::string
> writingCredits
)
1457 m_writingCredits
= Trim(std::move(writingCredits
));
1460 void CVideoInfoTag::SetGenre(std::vector
<std::string
> genre
)
1462 m_genre
= Trim(std::move(genre
));
1465 void CVideoInfoTag::SetCountry(std::vector
<std::string
> country
)
1467 m_country
= Trim(std::move(country
));
1470 void CVideoInfoTag::SetTagLine(std::string tagLine
)
1472 m_strTagLine
= Trim(std::move(tagLine
));
1475 void CVideoInfoTag::SetPlotOutline(std::string plotOutline
)
1477 m_strPlotOutline
= Trim(std::move(plotOutline
));
1480 void CVideoInfoTag::SetTrailer(std::string trailer
)
1482 m_strTrailer
= Trim(std::move(trailer
));
1485 void CVideoInfoTag::SetPlot(std::string plot
)
1487 m_strPlot
= Trim(std::move(plot
));
1490 void CVideoInfoTag::SetTitle(std::string title
)
1492 m_strTitle
= Trim(std::move(title
));
1495 std::string
const &CVideoInfoTag::GetTitle()
1500 void CVideoInfoTag::SetSortTitle(std::string sortTitle
)
1502 m_strSortTitle
= Trim(std::move(sortTitle
));
1505 void CVideoInfoTag::SetPictureURL(CScraperUrl
&pictureURL
)
1507 m_strPictureURL
= pictureURL
;
1510 void CVideoInfoTag::SetRating(float rating
, int votes
, const std::string
& type
/* = "" */, bool def
/* = false */)
1512 SetRating(CRating(rating
, votes
), type
, def
);
1515 void CVideoInfoTag::SetRating(CRating rating
, const std::string
& type
/* = "" */, bool def
/* = false */)
1517 if (rating
.rating
<= 0 || rating
.rating
> 10)
1521 m_ratings
[m_strDefaultRating
] = rating
;
1524 if (def
|| m_ratings
.empty())
1525 m_strDefaultRating
= type
;
1526 m_ratings
[type
] = rating
;
1530 void CVideoInfoTag::SetRating(float rating
, const std::string
& type
/* = "" */, bool def
/* = false */)
1532 if (rating
<= 0 || rating
> 10)
1536 m_ratings
[m_strDefaultRating
].rating
= rating
;
1539 if (def
|| m_ratings
.empty())
1540 m_strDefaultRating
= type
;
1541 m_ratings
[type
].rating
= rating
;
1545 void CVideoInfoTag::RemoveRating(const std::string
& type
)
1547 if (m_ratings
.find(type
) != m_ratings
.end())
1549 m_ratings
.erase(type
);
1550 if (m_strDefaultRating
== type
&& !m_ratings
.empty())
1551 m_strDefaultRating
= m_ratings
.begin()->first
;
1555 void CVideoInfoTag::SetRatings(RatingMap ratings
, const std::string
& defaultRating
/* = "" */)
1557 m_ratings
= std::move(ratings
);
1559 if (!defaultRating
.empty() && m_ratings
.find(defaultRating
) != m_ratings
.end())
1560 m_strDefaultRating
= defaultRating
;
1563 void CVideoInfoTag::SetVotes(int votes
, const std::string
& type
/* = "" */)
1566 m_ratings
[m_strDefaultRating
].votes
= votes
;
1568 m_ratings
[type
].votes
= votes
;
1571 void CVideoInfoTag::SetPremiered(const CDateTime
& premiered
)
1573 m_premiered
= premiered
;
1574 m_bHasPremiered
= premiered
.IsValid();
1577 void CVideoInfoTag::SetPremieredFromDBDate(const std::string
& premieredString
)
1579 CDateTime premiered
;
1580 premiered
.SetFromDBDate(premieredString
);
1581 SetPremiered(premiered
);
1584 void CVideoInfoTag::SetYear(int year
)
1592 void CVideoInfoTag::SetArtist(std::vector
<std::string
> artist
)
1594 m_artist
= Trim(std::move(artist
));
1597 void CVideoInfoTag::SetUniqueIDs(std::map
<std::string
, std::string
> uniqueIDs
)
1599 for (const auto& uniqueid
: uniqueIDs
)
1601 if (uniqueid
.first
.empty())
1602 uniqueIDs
.erase(uniqueid
.first
);
1604 if (uniqueIDs
.find(m_strDefaultUniqueID
) == uniqueIDs
.end())
1606 const auto defaultUniqueId
= GetUniqueID();
1607 if (!defaultUniqueId
.empty())
1608 uniqueIDs
[m_strDefaultUniqueID
] = defaultUniqueId
;
1610 m_uniqueIDs
= std::move(uniqueIDs
);
1613 void CVideoInfoTag::SetSet(std::string set
)
1615 m_set
.title
= Trim(std::move(set
));
1618 void CVideoInfoTag::SetSetOverview(std::string setOverview
)
1620 m_set
.overview
= Trim(std::move(setOverview
));
1623 void CVideoInfoTag::SetTags(std::vector
<std::string
> tags
)
1625 m_tags
= Trim(std::move(tags
));
1628 void CVideoInfoTag::SetFile(std::string file
)
1630 m_strFile
= Trim(std::move(file
));
1633 void CVideoInfoTag::SetPath(std::string path
)
1635 m_strPath
= Trim(std::move(path
));
1638 void CVideoInfoTag::SetMPAARating(std::string mpaaRating
)
1640 m_strMPAARating
= Trim(std::move(mpaaRating
));
1643 void CVideoInfoTag::SetFileNameAndPath(std::string fileNameAndPath
)
1645 m_strFileNameAndPath
= Trim(std::move(fileNameAndPath
));
1648 void CVideoInfoTag::SetOriginalTitle(std::string originalTitle
)
1650 m_strOriginalTitle
= Trim(std::move(originalTitle
));
1653 void CVideoInfoTag::SetEpisodeGuide(std::string episodeGuide
)
1655 if (StringUtils::StartsWith(episodeGuide
, "<episodeguide"))
1656 m_strEpisodeGuide
= Trim(std::move(episodeGuide
));
1659 StringUtils::Format("<episodeguide>{}</episodeguide>", Trim(std::move(episodeGuide
)));
1662 void CVideoInfoTag::SetStatus(std::string status
)
1664 m_strStatus
= Trim(std::move(status
));
1667 void CVideoInfoTag::SetProductionCode(std::string productionCode
)
1669 m_strProductionCode
= Trim(std::move(productionCode
));
1672 void CVideoInfoTag::SetShowTitle(std::string showTitle
)
1674 m_strShowTitle
= Trim(std::move(showTitle
));
1677 void CVideoInfoTag::SetStudio(std::vector
<std::string
> studio
)
1679 m_studio
= Trim(std::move(studio
));
1682 void CVideoInfoTag::SetAlbum(std::string album
)
1684 m_strAlbum
= Trim(std::move(album
));
1687 void CVideoInfoTag::SetShowLink(std::vector
<std::string
> showLink
)
1689 m_showLink
= Trim(std::move(showLink
));
1692 void CVideoInfoTag::SetUniqueID(const std::string
& uniqueid
, const std::string
& type
/* = "" */, bool isDefaultID
/* = false */)
1694 if (uniqueid
.empty())
1698 m_uniqueIDs
[m_strDefaultUniqueID
] = uniqueid
;
1701 m_uniqueIDs
[type
] = uniqueid
;
1703 m_strDefaultUniqueID
= type
;
1707 void CVideoInfoTag::RemoveUniqueID(const std::string
& type
)
1709 if (m_uniqueIDs
.find(type
) != m_uniqueIDs
.end())
1710 m_uniqueIDs
.erase(type
);
1713 void CVideoInfoTag::SetNamedSeasons(std::map
<int, std::string
> namedSeasons
)
1715 m_namedSeasons
= std::move(namedSeasons
);
1718 void CVideoInfoTag::SetUserrating(int userrating
)
1720 //This value needs to be between 0-10 - 0 will unset the userrating
1721 userrating
= std::max(userrating
, 0);
1722 userrating
= std::min(userrating
, 10);
1724 m_iUserRating
= userrating
;
1727 std::string
CVideoInfoTag::Trim(std::string
&&value
)
1729 return StringUtils::Trim(value
);
1732 std::vector
<std::string
> CVideoInfoTag::Trim(std::vector
<std::string
>&& items
)
1734 std::for_each(items
.begin(), items
.end(), [](std::string
&str
){
1735 str
= StringUtils::Trim(str
);
1737 return std::move(items
);
1740 int CVideoInfoTag::GetPlayCount() const
1742 return IsPlayCountSet() ? m_playCount
: 0;
1745 bool CVideoInfoTag::SetPlayCount(int count
)
1747 m_playCount
= count
;
1751 bool CVideoInfoTag::IncrementPlayCount()
1753 if (!IsPlayCountSet())
1760 void CVideoInfoTag::ResetPlayCount()
1762 m_playCount
= PLAYCOUNT_NOT_SET
;
1765 bool CVideoInfoTag::IsPlayCountSet() const
1767 return m_playCount
!= PLAYCOUNT_NOT_SET
;
1770 CBookmark
CVideoInfoTag::GetResumePoint() const
1772 return m_resumePoint
;
1775 bool CVideoInfoTag::SetResumePoint(const CBookmark
&resumePoint
)
1777 m_resumePoint
= resumePoint
;
1781 bool CVideoInfoTag::SetResumePoint(double timeInSeconds
, double totalTimeInSeconds
, const std::string
&playerState
)
1783 CBookmark resumePoint
;
1784 resumePoint
.timeInSeconds
= timeInSeconds
;
1785 resumePoint
.totalTimeInSeconds
= totalTimeInSeconds
;
1786 resumePoint
.playerState
= playerState
;
1787 resumePoint
.type
= CBookmark::RESUME
;
1789 m_resumePoint
= resumePoint
;