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