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 "ListItem.h"
10 
11 #include "AddonUtils.h"
12 #include "ServiceBroker.h"
13 #include "Util.h"
14 #include "games/GameTypes.h"
15 #include "games/tags/GameInfoTag.h"
16 #include "music/tags/MusicInfoTag.h"
17 #include "pictures/PictureInfoTag.h"
18 #include "settings/AdvancedSettings.h"
19 #include "settings/SettingsComponent.h"
20 #include "utils/StringUtils.h"
21 #include "utils/Variant.h"
22 #include "utils/log.h"
23 #include "video/VideoInfoTag.h"
24 
25 #include <cstdlib>
26 #include <sstream>
27 #include <utility>
28 
29 namespace XBMCAddon
30 {
31   namespace xbmcgui
32   {
ListItem(const String & label,const String & label2,const String & path,bool offscreen)33     ListItem::ListItem(const String& label,
34                        const String& label2,
35                        const String& path,
36                        bool offscreen) :
37       m_offscreen(offscreen)
38     {
39       item.reset();
40 
41       // create CFileItem
42       item.reset(new CFileItem());
43       if (!item) // not sure if this is really possible
44         return;
45 
46       if (!label.empty())
47         item->SetLabel( label );
48       if (!label2.empty())
49         item->SetLabel2( label2 );
50       if (!path.empty())
51         item->SetPath(path);
52     }
53 
~ListItem()54     ListItem::~ListItem()
55     {
56       item.reset();
57     }
58 
getLabel()59     String ListItem::getLabel()
60     {
61       if (!item) return "";
62 
63       String ret;
64       {
65         XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen);
66         ret = item->GetLabel();
67       }
68 
69       return ret;
70     }
71 
getLabel2()72     String ListItem::getLabel2()
73     {
74       if (!item) return "";
75 
76       String ret;
77       {
78         XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen);
79         ret = item->GetLabel2();
80       }
81 
82       return ret;
83     }
84 
setLabel(const String & label)85     void ListItem::setLabel(const String& label)
86     {
87       if (!item) return;
88       // set label
89       {
90         XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen);
91         item->SetLabel(label);
92       }
93     }
94 
setLabel2(const String & label)95     void ListItem::setLabel2(const String& label)
96     {
97       if (!item) return;
98       // set label
99       {
100         XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen);
101         item->SetLabel2(label);
102       }
103     }
104 
setArt(const Properties & dictionary)105     void ListItem::setArt(const Properties& dictionary)
106     {
107       if (!item) return;
108       {
109         XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen);
110         for (const auto& it: dictionary)
111         {
112           std::string artName = it.first;
113           StringUtils::ToLower(artName);
114           item->SetArt(artName, it.second);
115         }
116       }
117     }
118 
setIsFolder(bool isFolder)119     void ListItem::setIsFolder(bool isFolder)
120     {
121       if (!item)
122         return;
123       {
124         XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen);
125         item->m_bIsFolder = isFolder;
126       }
127     }
128 
setUniqueIDs(const Properties & dictionary,const String & defaultrating)129     void ListItem::setUniqueIDs(const Properties& dictionary, const String& defaultrating /* = "" */)
130     {
131       if (!item) return;
132 
133       XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen);
134       CVideoInfoTag& vtag = *GetVideoInfoTag();
135       for (const auto& it : dictionary)
136         vtag.SetUniqueID(it.second, it.first, it.first == defaultrating);
137     }
138 
setRating(const std::string & type,float rating,int votes,bool defaultt)139     void ListItem::setRating(const std::string& type,
140                              float rating,
141                              int votes /* = 0 */,
142                              bool defaultt /* = false */)
143     {
144       if (!item) return;
145 
146       XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen);
147       GetVideoInfoTag()->SetRating(rating, votes, type, defaultt);
148     }
149 
addSeason(int number,std::string name)150     void ListItem::addSeason(int number, std::string name /* = "" */)
151     {
152       if (!item) return;
153 
154       XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen);
155       GetVideoInfoTag()->m_namedSeasons[number] = std::move(name);
156     }
157 
select(bool selected)158     void ListItem::select(bool selected)
159     {
160       if (!item) return;
161       {
162         XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen);
163         item->Select(selected);
164       }
165     }
166 
167 
isSelected()168     bool ListItem::isSelected()
169     {
170       if (!item) return false;
171 
172       bool ret;
173       {
174         XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen);
175         ret = item->IsSelected();
176       }
177 
178       return ret;
179     }
180 
setProperty(const char * key,const String & value)181     void ListItem::setProperty(const char * key, const String& value)
182     {
183       XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen);
184       String lowerKey = key;
185       StringUtils::ToLower(lowerKey);
186       if (lowerKey == "startoffset")
187       { // special case for start offset - don't actually store in a property,
188         // we store it in item.m_lStartOffset instead
189         item->m_lStartOffset = CUtil::ConvertSecsToMilliSecs(atof(value.c_str())); // we store the offset in frames, or 1/75th of a second
190       }
191       else if (lowerKey == "mimetype")
192       { // special case for mime type - don't actually stored in a property,
193         item->SetMimeType(value.c_str());
194       }
195       else if (lowerKey == "totaltime")
196       {
197         CBookmark resumePoint(GetVideoInfoTag()->GetResumePoint());
198         resumePoint.totalTimeInSeconds = static_cast<float>(atof(value.c_str()));
199         GetVideoInfoTag()->SetResumePoint(resumePoint);
200       }
201       else if (lowerKey == "resumetime")
202       {
203         CBookmark resumePoint(GetVideoInfoTag()->GetResumePoint());
204         resumePoint.timeInSeconds = static_cast<float>(atof(value.c_str()));
205         GetVideoInfoTag()->SetResumePoint(resumePoint);
206       }
207       else if (lowerKey == "specialsort")
208       {
209         if (value == "bottom")
210           item->SetSpecialSort(SortSpecialOnBottom);
211         else if (value == "top")
212           item->SetSpecialSort(SortSpecialOnTop);
213       }
214       else if (lowerKey == "fanart_image")
215         item->SetArt("fanart", value);
216       else
217         item->SetProperty(lowerKey, value);
218     }
219 
setProperties(const Properties & dictionary)220     void ListItem::setProperties(const Properties& dictionary)
221     {
222       for (const auto& it: dictionary)
223         setProperty(it.first.c_str(), it.second);
224     }
225 
getProperty(const char * key)226     String ListItem::getProperty(const char* key)
227     {
228       XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen);
229       String lowerKey = key;
230       StringUtils::ToLower(lowerKey);
231       std::string value;
232       if (lowerKey == "startoffset")
233       { // special case for start offset - don't actually store in a property,
234         // we store it in item.m_lStartOffset instead
235         value = StringUtils::Format("%f", CUtil::ConvertMilliSecsToSecs(item->m_lStartOffset));
236       }
237       else if (lowerKey == "totaltime")
238         value = StringUtils::Format("%f", GetVideoInfoTag()->GetResumePoint().totalTimeInSeconds);
239       else if (lowerKey == "resumetime")
240         value = StringUtils::Format("%f", GetVideoInfoTag()->GetResumePoint().timeInSeconds);
241       else if (lowerKey == "fanart_image")
242         value = item->GetArt("fanart");
243       else
244         value = item->GetProperty(lowerKey).asString();
245 
246       return value;
247     }
248 
getArt(const char * key)249     String ListItem::getArt(const char* key)
250     {
251       XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen);
252       return item->GetArt(key);
253     }
254 
getUniqueID(const char * key)255     String ListItem::getUniqueID(const char* key)
256     {
257       XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen);
258       return GetVideoInfoTag()->GetUniqueID(key);
259     }
260 
getRating(const char * key)261     float ListItem::getRating(const char* key)
262     {
263       XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen);
264       return GetVideoInfoTag()->GetRating(key).rating;
265     }
266 
getVotes(const char * key)267     int ListItem::getVotes(const char* key)
268     {
269       XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen);
270       return GetVideoInfoTag()->GetRating(key).votes;
271     }
272 
setPath(const String & path)273     void ListItem::setPath(const String& path)
274     {
275       XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen);
276       item->SetPath(path);
277     }
278 
setMimeType(const String & mimetype)279     void ListItem::setMimeType(const String& mimetype)
280     {
281       XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen);
282       item->SetMimeType(mimetype);
283     }
284 
setContentLookup(bool enable)285     void ListItem::setContentLookup(bool enable)
286     {
287       XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen);
288       item->SetContentLookup(enable);
289     }
290 
getPath()291     String ListItem::getPath()
292     {
293       XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen);
294       return item->GetPath();
295     }
296 
setInfo(const char * type,const InfoLabelDict & infoLabels)297     void ListItem::setInfo(const char* type, const InfoLabelDict& infoLabels)
298     {
299       XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen);
300 
301       if (StringUtils::CompareNoCase(type, "video") == 0)
302       {
303         auto& videotag = *GetVideoInfoTag();
304         for (const auto& it: infoLabels)
305         {
306           String key = it.first;
307           StringUtils::ToLower(key);
308 
309           const InfoLabelValue& alt = it.second;
310           const String value(alt.which() == first ? alt.former() : emptyString);
311 
312           if (key == "dbid")
313             videotag.m_iDbId = strtol(value.c_str(), nullptr, 10);
314           else if (key == "year")
315             videotag.SetYear(strtol(value.c_str(), nullptr, 10));
316           else if (key == "episode")
317             videotag.m_iEpisode = strtol(value.c_str(), nullptr, 10);
318           else if (key == "season")
319             videotag.m_iSeason = strtol(value.c_str(), nullptr, 10);
320           else if (key == "sortepisode")
321             videotag.m_iSpecialSortEpisode = strtol(value.c_str(), nullptr, 10);
322           else if (key == "sortseason")
323             videotag.m_iSpecialSortSeason = strtol(value.c_str(), nullptr, 10);
324           else if (key == "episodeguide")
325             videotag.SetEpisodeGuide(value);
326           else if (key == "showlink")
327             videotag.SetShowLink(getStringArray(alt, key, value));
328           else if (key == "top250")
329             videotag.m_iTop250 = strtol(value.c_str(), nullptr, 10);
330           else if (key == "setid")
331             videotag.m_set.id = strtol(value.c_str(), nullptr, 10);
332           else if (key == "tracknumber")
333             videotag.m_iTrack = strtol(value.c_str(), nullptr, 10);
334           else if (key == "count")
335             item->m_iprogramCount = strtol(value.c_str(), nullptr, 10);
336           else if (key == "rating")
337             videotag.SetRating(static_cast<float>(strtod(value.c_str(), nullptr)));
338           else if (key == "userrating")
339             videotag.m_iUserRating = strtol(value.c_str(), nullptr, 10);
340           else if (key == "size")
341             item->m_dwSize = (int64_t)strtoll(value.c_str(), nullptr, 10);
342           else if (key == "watched") // backward compat - do we need it?
343             videotag.SetPlayCount(strtol(value.c_str(), nullptr, 10));
344           else if (key == "playcount")
345             videotag.SetPlayCount(strtol(value.c_str(), nullptr, 10));
346           else if (key == "overlay")
347           {
348             long overlay = strtol(value.c_str(), nullptr, 10);
349             if (overlay >= 0 && overlay <= 8)
350               item->SetOverlayImage(static_cast<CGUIListItem::GUIIconOverlay>(overlay));
351           }
352           else if (key == "cast" || key == "castandrole")
353           {
354             if (alt.which() != second)
355               throw WrongTypeException("When using \"cast\" or \"castandrole\" you need to supply a list of tuples for the value in the dictionary");
356 
357             videotag.m_cast.clear();
358             for (const auto& castEntry: alt.later())
359             {
360               // castEntry can be a string meaning it's the actor or it can be a tuple meaning it's the
361               //  actor and the role.
362               const String& actor = castEntry.which() == first ? castEntry.former() : castEntry.later().first();
363               SActorInfo info;
364               info.strName = actor;
365               if (castEntry.which() == second)
366                 info.strRole = static_cast<const String&>(castEntry.later().second());
367               videotag.m_cast.push_back(info);
368             }
369           }
370           else if (key == "artist")
371           {
372             if (alt.which() != second)
373               throw WrongTypeException("When using \"artist\" you need to supply a list of strings for the value in the dictionary");
374 
375             videotag.m_artist.clear();
376 
377             for (const auto& castEntry: alt.later())
378             {
379               const String& actor = castEntry.which() == first ? castEntry.former() : castEntry.later().first();
380               videotag.m_artist.push_back(actor);
381             }
382           }
383           else if (key == "genre")
384             videotag.SetGenre(getStringArray(alt, key, value));
385           else if (key == "country")
386             videotag.SetCountry(getStringArray(alt, key, value));
387           else if (key == "director")
388             videotag.SetDirector(getStringArray(alt, key, value));
389           else if (key == "mpaa")
390             videotag.SetMPAARating(value);
391           else if (key == "plot")
392             videotag.SetPlot(value);
393           else if (key == "plotoutline")
394             videotag.SetPlotOutline(value);
395           else if (key == "title")
396             videotag.SetTitle(value);
397           else if (key == "originaltitle")
398             videotag.SetOriginalTitle(value);
399           else if (key == "sorttitle")
400             videotag.SetSortTitle(value);
401           else if (key == "duration")
402             videotag.SetDuration(strtol(value.c_str(), nullptr, 10));
403           else if (key == "studio")
404             videotag.SetStudio(getStringArray(alt, key, value));
405           else if (key == "tagline")
406             videotag.SetTagLine(value);
407           else if (key == "writer" || key == "credits")
408             videotag.SetWritingCredits(getStringArray(alt, key, value));
409           else if (key == "tvshowtitle")
410             videotag.SetShowTitle(value);
411           else if (key == "premiered")
412           {
413             CDateTime premiered;
414             premiered.SetFromDateString(value);
415             videotag.SetPremiered(premiered);
416           }
417           else if (key == "status")
418             videotag.SetStatus(value);
419           else if (key == "set")
420             videotag.SetSet(value);
421           else if (key == "setoverview")
422             videotag.SetSetOverview(value);
423           else if (key == "tag")
424             videotag.SetTags(getStringArray(alt, key, value));
425           else if (key == "imdbnumber")
426             videotag.SetUniqueID(value);
427           else if (key == "code")
428             videotag.SetProductionCode(value);
429           else if (key == "aired")
430             videotag.m_firstAired.SetFromDateString(value);
431           else if (key == "lastplayed")
432             videotag.m_lastPlayed.SetFromDBDateTime(value);
433           else if (key == "album")
434             videotag.SetAlbum(value);
435           else if (key == "votes")
436             videotag.SetVotes(StringUtils::ReturnDigits(value));
437           else if (key == "trailer")
438             videotag.SetTrailer(value);
439           else if (key == "path")
440             videotag.SetPath(value);
441           else if (key == "filenameandpath")
442             videotag.SetFileNameAndPath(value);
443           else if (key == "date")
444           {
445             if (value.length() == 10)
446             {
447               int year = atoi(value.substr(value.size() - 4).c_str());
448               int month = atoi(value.substr(3, 4).c_str());
449               int day = atoi(value.substr(0, 2).c_str());
450               item->m_dateTime.SetDate(year, month, day);
451             }
452             else
453               CLog::Log(LOGERROR,"NEWADDON Invalid Date Format \"%s\"",value.c_str());
454           }
455           else if (key == "dateadded")
456             videotag.m_dateAdded.SetFromDBDateTime(value.c_str());
457           else if (key == "mediatype")
458           {
459             if (CMediaTypes::IsValidMediaType(value))
460               videotag.m_type = value;
461             else
462               CLog::Log(LOGWARNING, "Invalid media type \"%s\"", value.c_str());
463           }
464           else
465             CLog::Log(LOGERROR,"NEWADDON Unknown Video Info Key \"%s\"", key.c_str());
466         }
467       }
468       else if (StringUtils::CompareNoCase(type, "music") == 0)
469       {
470         std::string type;
471         for (auto it = infoLabels.begin(); it != infoLabels.end(); ++it)
472         {
473           String key = it->first;
474           StringUtils::ToLower(key);
475           const InfoLabelValue& alt = it->second;
476           const String value(alt.which() == first ? alt.former() : emptyString);
477 
478           if (key == "mediatype")
479           {
480             if (CMediaTypes::IsValidMediaType(value))
481             {
482               type = value;
483               item->GetMusicInfoTag()->SetType(value);
484             }
485             else
486               CLog::Log(LOGWARNING, "Invalid media type \"%s\"", value.c_str());
487           }
488         }
489         auto& musictag = *item->GetMusicInfoTag();
490         for (const auto& it : infoLabels)
491         {
492           String key = it.first;
493           StringUtils::ToLower(key);
494 
495           const InfoLabelValue& alt = it.second;
496           const String value(alt.which() == first ? alt.former() : emptyString);
497 
498           //! @todo add the rest of the infolabels
499           if (key == "dbid" && !type.empty())
500             musictag.SetDatabaseId(static_cast<int>(strtol(value.c_str(), NULL, 10)), type);
501           else if (key == "tracknumber")
502             musictag.SetTrackNumber(strtol(value.c_str(), NULL, 10));
503           else if (key == "discnumber")
504             musictag.SetDiscNumber(strtol(value.c_str(), nullptr, 10));
505           else if (key == "count")
506             item->m_iprogramCount = strtol(value.c_str(), nullptr, 10);
507           else if (key == "size")
508             item->m_dwSize = static_cast<int64_t>(strtoll(value.c_str(), nullptr, 10));
509           else if (key == "duration")
510             musictag.SetDuration(strtol(value.c_str(), nullptr, 10));
511           else if (key == "year")
512             musictag.SetYear(strtol(value.c_str(), nullptr, 10));
513           else if (key == "listeners")
514             musictag.SetListeners(strtol(value.c_str(), nullptr, 10));
515           else if (key == "playcount")
516             musictag.SetPlayCount(strtol(value.c_str(), nullptr, 10));
517           else if (key == "genre")
518             musictag.SetGenre(value);
519           else if (key == "album")
520             musictag.SetAlbum(value);
521           else if (key == "artist")
522             musictag.SetArtist(value);
523           else if (key == "title")
524             musictag.SetTitle(value);
525           else if (key == "rating")
526             musictag.SetRating(static_cast<float>(strtod(value.c_str(), nullptr)));
527           else if (key == "userrating")
528             musictag.SetUserrating(strtol(value.c_str(), nullptr, 10));
529           else if (key == "lyrics")
530             musictag.SetLyrics(value);
531           else if (key == "lastplayed")
532             musictag.SetLastPlayed(value);
533           else if (key == "musicbrainztrackid")
534             musictag.SetMusicBrainzTrackID(value);
535           else if (key == "musicbrainzartistid")
536             musictag.SetMusicBrainzArtistID(StringUtils::Split(value, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator));
537           else if (key == "musicbrainzalbumid")
538             musictag.SetMusicBrainzAlbumID(value);
539           else if (key == "musicbrainzalbumartistid")
540             musictag.SetMusicBrainzAlbumArtistID(StringUtils::Split(value, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator));
541           else if (key == "comment")
542             musictag.SetComment(value);
543           else if (key == "date")
544           {
545             if (strlen(value.c_str()) == 10)
546             {
547               int year = atoi(value.substr(value.size() - 4).c_str());
548               int month = atoi(value.substr(3, 4).c_str());
549               int day = atoi(value.substr(0, 2).c_str());
550               item->m_dateTime.SetDate(year, month, day);
551             }
552           }
553           else if (key != "mediatype")
554             CLog::Log(LOGERROR,"NEWADDON Unknown Music Info Key \"%s\"", key.c_str());
555 
556           // This should probably be set outside of the loop but since the original
557           //  implementation set it inside of the loop, I'll leave it that way. - Jim C.
558           musictag.SetLoaded(true);
559         }
560       }
561       else if (StringUtils::CompareNoCase(type, "pictures") == 0)
562       {
563         for (const auto& it: infoLabels)
564         {
565           String key = it.first;
566           StringUtils::ToLower(key);
567 
568           const InfoLabelValue& alt = it.second;
569           const String value(alt.which() == first ? alt.former() : emptyString);
570 
571           if (key == "count")
572             item->m_iprogramCount = strtol(value.c_str(), nullptr, 10);
573           else if (key == "size")
574             item->m_dwSize = static_cast<int64_t>(strtoll(value.c_str(), nullptr, 10));
575           else if (key == "title")
576             item->m_strTitle = value;
577           else if (key == "picturepath")
578             item->SetPath(value);
579           else if (key == "date")
580           {
581             if (strlen(value.c_str()) == 10)
582             {
583               int year = atoi(value.substr(value.size() - 4).c_str());
584               int month = atoi(value.substr(3, 4).c_str());
585               int day = atoi(value.substr(0, 2).c_str());
586               item->m_dateTime.SetDate(year, month, day);
587             }
588           }
589           else
590           {
591             const String& exifkey = key;
592             if (!StringUtils::StartsWithNoCase(exifkey, "exif:") || exifkey.length() < 6)
593               continue;
594 
595             item->GetPictureInfoTag()->SetInfo(StringUtils::Mid(exifkey, 5), value);
596           }
597         }
598       }
599       else if (StringUtils::EqualsNoCase(type, "game"))
600       {
601         auto& gametag = *item->GetGameInfoTag();
602         for (const auto& it: infoLabels)
603         {
604           String key = it.first;
605           StringUtils::ToLower(key);
606 
607           const InfoLabelValue& alt = it.second;
608           const String value(alt.which() == first ? alt.former() : emptyString);
609 
610           if (key == "title")
611           {
612             item->m_strTitle = value;
613             gametag.SetTitle(value);
614           }
615           else if (key == "platform")
616             gametag.SetPlatform(value);
617           else if (key == "genres")
618           {
619             if (alt.which() != second)
620               throw WrongTypeException("When using \"genres\" you need to supply a list of strings for the value in the dictionary");
621 
622             std::vector<std::string> genres;
623 
624             for (const auto& genreEntry: alt.later())
625             {
626               const String& genre = genreEntry.which() == first ? genreEntry.former() : genreEntry.later().first();
627               genres.emplace_back(genre);
628             }
629 
630             gametag.SetGenres(genres);
631           }
632           else if (key == "publisher")
633             gametag.SetPublisher(value);
634           else if (key == "developer")
635             gametag.SetDeveloper(value);
636           else if (key == "overview")
637             gametag.SetOverview(value);
638           else if (key == "year")
639             gametag.SetYear(strtol(value.c_str(), nullptr, 10));
640           else if (key == "gameclient")
641             gametag.SetGameClient(value);
642         }
643       }
644     } // end ListItem::setInfo
645 
setCast(const std::vector<Properties> & actors)646     void ListItem::setCast(const std::vector<Properties>& actors)
647     {
648       XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen);
649       GetVideoInfoTag()->m_cast.clear();
650       for (const auto& dictionary: actors)
651       {
652         SActorInfo info;
653         for (auto it = dictionary.begin(); it != dictionary.end(); ++it)
654         {
655           const String& key = it->first;
656           const String& value = it->second;
657           if (key == "name")
658             info.strName = value;
659           else if (key == "role")
660             info.strRole = value;
661           else if (key == "thumbnail")
662             info.thumbUrl = CScraperUrl(value);
663           else if (key == "order")
664             info.order = strtol(value.c_str(), nullptr, 10);
665         }
666         GetVideoInfoTag()->m_cast.push_back(std::move(info));
667       }
668     }
669 
setAvailableFanart(const std::vector<Properties> & images)670     void ListItem::setAvailableFanart(const std::vector<Properties>& images)
671     {
672       XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen);
673       GetVideoInfoTag()->m_fanart.Clear();
674       for (const auto& dictionary : images)
675       {
676         std::string image;
677         std::string preview;
678         std::string colors;
679         for (const auto& it: dictionary)
680         {
681           const String& key = it.first;
682           const String& value = it.second;
683           if (key == "image")
684             image = value;
685           else if (key == "preview")
686             preview = value;
687           else if (key == "colors")
688             colors = value;
689         }
690         GetVideoInfoTag()->m_fanart.AddFanart(image, preview, colors);
691       }
692       GetVideoInfoTag()->m_fanart.Pack();
693     }
694 
addAvailableArtwork(const std::string & url,const std::string & art_type,const std::string & preview,const std::string & referrer,const std::string & cache,bool post,bool isgz,int season)695     void ListItem::addAvailableArtwork(const std::string& url,
696                                        const std::string& art_type,
697                                        const std::string& preview,
698                                        const std::string& referrer,
699                                        const std::string& cache,
700                                        bool post,
701                                        bool isgz,
702                                        int season)
703     {
704       XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen);
705       GetVideoInfoTag()->m_strPictureURL.AddParsedUrl(url, art_type, preview, referrer, cache, post,
706                                                       isgz, season);
707     }
708 
addStreamInfo(const char * cType,const Properties & dictionary)709     void ListItem::addStreamInfo(const char* cType, const Properties& dictionary)
710     {
711       XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen);
712 
713       if (StringUtils::CompareNoCase(cType, "video") == 0)
714       {
715         CStreamDetailVideo* video = new CStreamDetailVideo;
716         for (const auto& it: dictionary)
717         {
718           const String& key = it.first;
719           const String value(it.second.c_str());
720 
721           if (key == "codec")
722             video->m_strCodec = value;
723           else if (key == "aspect")
724             video->m_fAspect = static_cast<float>(atof(value.c_str()));
725           else if (key == "width")
726             video->m_iWidth = strtol(value.c_str(), nullptr, 10);
727           else if (key == "height")
728             video->m_iHeight = strtol(value.c_str(), nullptr, 10);
729           else if (key == "duration")
730             video->m_iDuration = strtol(value.c_str(), nullptr, 10);
731           else if (key == "stereomode")
732             video->m_strStereoMode = value;
733           else if (key == "language")
734             video->m_strLanguage = value;
735         }
736         GetVideoInfoTag()->m_streamDetails.AddStream(video);
737       }
738       else if (StringUtils::CompareNoCase(cType, "audio") == 0)
739       {
740         CStreamDetailAudio* audio = new CStreamDetailAudio;
741         for (const auto& it: dictionary)
742         {
743           const String& key = it.first;
744           const String& value = it.second;
745 
746           if (key == "codec")
747             audio->m_strCodec = value;
748           else if (key == "language")
749             audio->m_strLanguage = value;
750           else if (key == "channels")
751             audio->m_iChannels = strtol(value.c_str(), nullptr, 10);
752         }
753         GetVideoInfoTag()->m_streamDetails.AddStream(audio);
754       }
755       else if (StringUtils::CompareNoCase(cType, "subtitle") == 0)
756       {
757         CStreamDetailSubtitle* subtitle = new CStreamDetailSubtitle;
758         for (const auto& it: dictionary)
759         {
760           const String& key = it.first;
761           const String& value = it.second;
762 
763           if (key == "language")
764             subtitle->m_strLanguage = value;
765         }
766         GetVideoInfoTag()->m_streamDetails.AddStream(subtitle);
767       }
768       GetVideoInfoTag()->m_streamDetails.DetermineBestStreams();
769     } // end ListItem::addStreamInfo
770 
addContextMenuItems(const std::vector<Tuple<String,String>> & items,bool replaceItems)771     void ListItem::addContextMenuItems(const std::vector<Tuple<String,String> >& items, bool replaceItems /* = false */)
772     {
773       for (size_t i = 0; i < items.size(); ++i)
774       {
775         auto& tuple = items[i];
776         if (tuple.GetNumValuesSet() != 2)
777           throw ListItemException("Must pass in a list of tuples of pairs of strings. One entry in the list only has %d elements.",tuple.GetNumValuesSet());
778 
779         XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen);
780         item->SetProperty(StringUtils::Format("contextmenulabel(%zu)", i), tuple.first());
781         item->SetProperty(StringUtils::Format("contextmenuaction(%zu)", i), tuple.second());
782       }
783     }
784 
setSubtitles(const std::vector<String> & paths)785     void ListItem::setSubtitles(const std::vector<String>& paths)
786     {
787       XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen);
788       unsigned int i = 1;
789       for (const auto& it: paths)
790       {
791         String property = StringUtils::Format("subtitle:%u", i++);
792         item->SetProperty(property, it);
793       }
794     }
795 
getVideoInfoTag()796     xbmc::InfoTagVideo* ListItem::getVideoInfoTag()
797     {
798       XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen);
799       if (item->HasVideoInfoTag())
800         return new xbmc::InfoTagVideo(*GetVideoInfoTag());
801       return new xbmc::InfoTagVideo();
802     }
803 
getMusicInfoTag()804     xbmc::InfoTagMusic* ListItem::getMusicInfoTag()
805     {
806       XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen);
807       if (item->HasMusicInfoTag())
808         return new xbmc::InfoTagMusic(*item->GetMusicInfoTag());
809       return new xbmc::InfoTagMusic();
810     }
811 
getStringArray(const InfoLabelValue & alt,const std::string & tag,std::string value)812     std::vector<std::string> ListItem::getStringArray(const InfoLabelValue& alt, const std::string& tag, std::string value)
813     {
814       if (alt.which() == first)
815       {
816         if (value.empty())
817           value = alt.former();
818         return StringUtils::Split(value, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
819       }
820 
821       std::vector<std::string> els;
822       for (const auto& el : alt.later())
823       {
824         if (el.which() == second)
825           throw WrongTypeException("When using \"%s\" you need to supply a string or list of strings for the value in the dictionary", tag.c_str());
826         els.emplace_back(el.former());
827       }
828       return els;
829     }
830 
GetVideoInfoTag()831     CVideoInfoTag* ListItem::GetVideoInfoTag()
832     {
833       // make sure the playcount is reset to -1
834       if (!item->HasVideoInfoTag())
835         item->GetVideoInfoTag()->ResetPlayCount();
836 
837       return item->GetVideoInfoTag();
838     }
839 
GetVideoInfoTag() const840     const CVideoInfoTag* ListItem::GetVideoInfoTag() const
841     {
842       return item->GetVideoInfoTag();
843     }
844   }
845 }
846