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