2 * Copyright (C) 2005-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
24 #include "AddonUtils.h"
26 #include "video/VideoInfoTag.h"
27 #include "music/tags/MusicInfoTag.h"
28 #include "pictures/PictureInfoTag.h"
29 #include "utils/log.h"
30 #include "utils/Variant.h"
31 #include "utils/StringUtils.h"
32 #include "settings/AdvancedSettings.h"
38 ListItem::ListItem(const String& label,
40 const String& iconImage,
41 const String& thumbnailImage,
47 item.reset(new CFileItem());
48 if (!item) // not sure if this is really possible
52 item->SetLabel( label );
54 item->SetLabel2( label2 );
55 if (!iconImage.empty())
56 item->SetIconImage( iconImage );
57 if (!thumbnailImage.empty())
58 item->SetArt("thumb", thumbnailImage );
68 String ListItem::getLabel()
75 ret = item->GetLabel();
81 String ListItem::getLabel2()
88 ret = item->GetLabel2();
94 void ListItem::setLabel(const String& label)
100 item->SetLabel(label);
104 void ListItem::setLabel2(const String& label)
110 item->SetLabel2(label);
114 void ListItem::setIconImage(const String& iconImage)
119 item->SetIconImage(iconImage);
123 void ListItem::setThumbnailImage(const String& thumbFilename)
128 item->SetArt("thumb", thumbFilename);
132 void ListItem::setArt(const Properties& dictionary)
137 for (Properties::const_iterator it = dictionary.begin(); it != dictionary.end(); ++it)
139 CStdString artName = it->first;
140 StringUtils::ToLower(artName);
141 const CStdString artFilename(it->second.c_str());
142 item->SetArt(artName, artFilename);
147 void ListItem::select(bool selected)
152 item->Select(selected);
157 bool ListItem::isSelected()
159 if (!item) return false;
164 ret = item->IsSelected();
170 void ListItem::setProperty(const char * key, const String& value)
173 String lowerKey = key;
174 StringUtils::ToLower(lowerKey);
175 if (lowerKey == "startoffset")
176 { // special case for start offset - don't actually store in a property,
177 // we store it in item.m_lStartOffset instead
178 item->m_lStartOffset = (int)(atof(value.c_str()) * 75.0); // we store the offset in frames, or 1/75th of a second
180 else if (lowerKey == "mimetype")
181 { // special case for mime type - don't actually stored in a property,
182 item->SetMimeType(value.c_str());
184 else if (lowerKey == "totaltime")
185 item->GetVideoInfoTag()->m_resumePoint.totalTimeInSeconds = (float)atof(value.c_str());
186 else if (lowerKey == "resumetime")
187 item->GetVideoInfoTag()->m_resumePoint.timeInSeconds = (float)atof(value.c_str());
188 else if (lowerKey == "specialsort")
190 if (value == "bottom")
191 item->SetSpecialSort(SortSpecialOnBottom);
192 else if (value == "top")
193 item->SetSpecialSort(SortSpecialOnTop);
195 else if (lowerKey == "fanart_image")
196 item->SetArt("fanart", value);
198 item->SetProperty(lowerKey, value);
201 String ListItem::getProperty(const char* key)
204 String lowerKey = key;
205 StringUtils::ToLower(lowerKey);
207 if (lowerKey == "startoffset")
208 { // special case for start offset - don't actually store in a property,
209 // we store it in item.m_lStartOffset instead
210 value = StringUtils::Format("%f", item->m_lStartOffset / 75.0);
212 else if (lowerKey == "totaltime")
213 value = StringUtils::Format("%f", item->GetVideoInfoTag()->m_resumePoint.totalTimeInSeconds);
214 else if (lowerKey == "resumetime")
215 value = StringUtils::Format("%f", item->GetVideoInfoTag()->m_resumePoint.timeInSeconds);
216 else if (lowerKey == "fanart_image")
217 value = item->GetArt("fanart");
219 value = item->GetProperty(lowerKey).asString();
221 return value.c_str();
224 void ListItem::setPath(const String& path)
230 void ListItem::setMimeType(const String& mimetype)
233 item->SetMimeType(mimetype);
236 String ListItem::getdescription()
238 return item->GetLabel();
241 String ListItem::getduration()
243 if (item->LoadMusicTag())
245 std::ostringstream oss;
246 oss << item->GetMusicInfoTag()->GetDuration();
250 if (item->HasVideoInfoTag())
252 std::ostringstream oss;
253 oss << item->GetVideoInfoTag()->GetDuration() / 60;
259 String ListItem::getfilename()
261 return item->GetPath();
264 void ListItem::setInfo(const char* type, const InfoLabelDict& infoLabels) throw (WrongTypeException)
268 if (strcmpi(type, "video") == 0)
270 for (InfoLabelDict::const_iterator it = infoLabels.begin(); it != infoLabels.end(); ++it)
272 String key = it->first;
273 StringUtils::ToLower(key);
275 const InfoLabelValue& alt = it->second;
276 const String value(alt.which() == first ? alt.former() : emptyString);
279 item->GetVideoInfoTag()->m_iYear = strtol(value.c_str(), NULL, 10);
280 else if (key == "episode")
281 item->GetVideoInfoTag()->m_iEpisode = strtol(value.c_str(), NULL, 10);
282 else if (key == "season")
283 item->GetVideoInfoTag()->m_iSeason = strtol(value.c_str(), NULL, 10);
284 else if (key == "top250")
285 item->GetVideoInfoTag()->m_iTop250 = strtol(value.c_str(), NULL, 10);
286 else if (key == "tracknumber")
287 item->GetVideoInfoTag()->m_iTrack = strtol(value.c_str(), NULL, 10);
288 else if (key == "count")
289 item->m_iprogramCount = strtol(value.c_str(), NULL, 10);
290 else if (key == "rating")
291 item->GetVideoInfoTag()->m_fRating = (float)strtod(value.c_str(), NULL);
292 else if (key == "size")
293 item->m_dwSize = (int64_t)strtoll(value.c_str(), NULL, 10);
294 else if (key == "watched") // backward compat - do we need it?
295 item->GetVideoInfoTag()->m_playCount = strtol(value.c_str(), NULL, 10);
296 else if (key == "playcount")
297 item->GetVideoInfoTag()->m_playCount = strtol(value.c_str(), NULL, 10);
298 else if (key == "overlay")
300 long overlay = strtol(value.c_str(), NULL, 10);
301 if (overlay >= 0 && overlay <= 8)
302 item->SetOverlayImage((CGUIListItem::GUIIconOverlay)overlay);
304 else if (key == "cast" || key == "castandrole")
306 if (alt.which() != second)
307 throw WrongTypeException("When using \"cast\" or \"castandrole\" you need to supply a list of tuples for the value in the dictionary");
309 item->GetVideoInfoTag()->m_cast.clear();
310 const std::vector<InfoLabelStringOrTuple>& listValue = alt.later();
311 for (std::vector<InfoLabelStringOrTuple>::const_iterator viter = listValue.begin(); viter != listValue.end(); ++viter)
313 const InfoLabelStringOrTuple& castEntry = *viter;
314 // castEntry can be a string meaning it's the actor or it can be a tuple meaning it's the
315 // actor and the role.
316 const String& actor = castEntry.which() == first ? castEntry.former() : castEntry.later().first();
318 info.strName = actor;
319 if (castEntry.which() == second)
320 info.strRole = (const String&)(castEntry.later().second());
321 item->GetVideoInfoTag()->m_cast.push_back(info);
324 else if (key == "artist")
326 if (alt.which() != second)
327 throw WrongTypeException("When using \"artist\" you need to supply a list of strings for the value in the dictionary");
329 item->GetVideoInfoTag()->m_artist.clear();
331 const std::vector<InfoLabelStringOrTuple>& listValue = alt.later();
332 for (std::vector<InfoLabelStringOrTuple>::const_iterator viter = listValue.begin(); viter != listValue.end(); ++viter)
335 const InfoLabelStringOrTuple& castEntry = *viter;
336 const String& actor = castEntry.which() == first ? castEntry.former() : castEntry.later().first();
337 item->GetVideoInfoTag()->m_artist.push_back(actor);
340 else if (key == "genre")
341 item->GetVideoInfoTag()->m_genre = StringUtils::Split(value, g_advancedSettings.m_videoItemSeparator);
342 else if (key == "director")
343 item->GetVideoInfoTag()->m_director = StringUtils::Split(value, g_advancedSettings.m_videoItemSeparator);
344 else if (key == "mpaa")
345 item->GetVideoInfoTag()->m_strMPAARating = value;
346 else if (key == "plot")
347 item->GetVideoInfoTag()->m_strPlot = value;
348 else if (key == "plotoutline")
349 item->GetVideoInfoTag()->m_strPlotOutline = value;
350 else if (key == "title")
351 item->GetVideoInfoTag()->m_strTitle = value;
352 else if (key == "originaltitle")
353 item->GetVideoInfoTag()->m_strOriginalTitle = value;
354 else if (key == "sorttitle")
355 item->GetVideoInfoTag()->m_strSortTitle = value;
356 else if (key == "duration")
357 item->GetVideoInfoTag()->m_duration = CVideoInfoTag::GetDurationFromMinuteString(value);
358 else if (key == "studio")
359 item->GetVideoInfoTag()->m_studio = StringUtils::Split(value, g_advancedSettings.m_videoItemSeparator);
360 else if (key == "tagline")
361 item->GetVideoInfoTag()->m_strTagLine = value;
362 else if (key == "writer")
363 item->GetVideoInfoTag()->m_writingCredits = StringUtils::Split(value, g_advancedSettings.m_videoItemSeparator);
364 else if (key == "tvshowtitle")
365 item->GetVideoInfoTag()->m_strShowTitle = value;
366 else if (key == "premiered")
367 item->GetVideoInfoTag()->m_premiered.SetFromDateString(value);
368 else if (key == "status")
369 item->GetVideoInfoTag()->m_strStatus = value;
370 else if (key == "code")
371 item->GetVideoInfoTag()->m_strProductionCode = value;
372 else if (key == "aired")
373 item->GetVideoInfoTag()->m_firstAired.SetFromDateString(value);
374 else if (key == "credits")
375 item->GetVideoInfoTag()->m_writingCredits = StringUtils::Split(value, g_advancedSettings.m_videoItemSeparator);
376 else if (key == "lastplayed")
377 item->GetVideoInfoTag()->m_lastPlayed.SetFromDBDateTime(value);
378 else if (key == "album")
379 item->GetVideoInfoTag()->m_strAlbum = value;
380 else if (key == "votes")
381 item->GetVideoInfoTag()->m_strVotes = value;
382 else if (key == "trailer")
383 item->GetVideoInfoTag()->m_strTrailer = value;
384 else if (key == "date")
386 if (value.length() == 10)
388 int year = atoi(value.substr(value.size() - 4).c_str());
389 int month = atoi(value.substr(3, 4).c_str());
390 int day = atoi(value.substr(0, 2).c_str());
391 item->m_dateTime.SetDate(year, month, day);
394 CLog::Log(LOGERROR,"NEWADDON Invalid Date Format \"%s\"",value.c_str());
396 else if (key == "dateadded")
397 item->GetVideoInfoTag()->m_dateAdded.SetFromDBDateTime(value.c_str());
400 else if (strcmpi(type, "music") == 0)
402 for (InfoLabelDict::const_iterator it = infoLabels.begin(); it != infoLabels.end(); ++it)
404 String key = it->first;
405 StringUtils::ToLower(key);
407 const InfoLabelValue& alt = it->second;
408 const String value(alt.which() == first ? alt.former() : emptyString);
410 // TODO: add the rest of the infolabels
411 if (key == "tracknumber")
412 item->GetMusicInfoTag()->SetTrackNumber(strtol(value.c_str(), NULL, 10));
413 else if (key == "count")
414 item->m_iprogramCount = strtol(value.c_str(), NULL, 10);
415 else if (key == "size")
416 item->m_dwSize = (int64_t)strtoll(value.c_str(), NULL, 10);
417 else if (key == "duration")
418 item->GetMusicInfoTag()->SetDuration(strtol(value.c_str(), NULL, 10));
419 else if (key == "year")
420 item->GetMusicInfoTag()->SetYear(strtol(value.c_str(), NULL, 10));
421 else if (key == "listeners")
422 item->GetMusicInfoTag()->SetListeners(strtol(value.c_str(), NULL, 10));
423 else if (key == "playcount")
424 item->GetMusicInfoTag()->SetPlayCount(strtol(value.c_str(), NULL, 10));
425 else if (key == "genre")
426 item->GetMusicInfoTag()->SetGenre(value);
427 else if (key == "album")
428 item->GetMusicInfoTag()->SetAlbum(value);
429 else if (key == "artist")
430 item->GetMusicInfoTag()->SetArtist(value);
431 else if (key == "title")
432 item->GetMusicInfoTag()->SetTitle(value);
433 else if (key == "rating")
434 item->GetMusicInfoTag()->SetRating(value[0]);
435 else if (key == "lyrics")
436 item->GetMusicInfoTag()->SetLyrics(value);
437 else if (key == "lastplayed")
438 item->GetMusicInfoTag()->SetLastPlayed(value);
439 else if (key == "musicbrainztrackid")
440 item->GetMusicInfoTag()->SetMusicBrainzTrackID(value);
441 else if (key == "musicbrainzartistid")
442 item->GetMusicInfoTag()->SetMusicBrainzArtistID(StringUtils::Split(value, g_advancedSettings.m_musicItemSeparator));
443 else if (key == "musicbrainzalbumid")
444 item->GetMusicInfoTag()->SetMusicBrainzAlbumID(value);
445 else if (key == "musicbrainzalbumartistid")
446 item->GetMusicInfoTag()->SetMusicBrainzAlbumArtistID(StringUtils::Split(value, g_advancedSettings.m_musicItemSeparator));
447 else if (key == "musicbrainztrmid")
448 item->GetMusicInfoTag()->SetMusicBrainzTRMID(value);
449 else if (key == "comment")
450 item->GetMusicInfoTag()->SetComment(value);
451 else if (key == "date")
453 if (strlen(value.c_str()) == 10)
455 int year = atoi(value.substr(value.size() - 4).c_str());
456 int month = atoi(value.substr(3, 4).c_str());
457 int day = atoi(value.substr(0, 2).c_str());
458 item->m_dateTime.SetDate(year, month, day);
462 CLog::Log(LOGERROR,"NEWADDON Unknown Music Info Key \"%s\"", key.c_str());
464 // This should probably be set outside of the loop but since the original
465 // implementation set it inside of the loop, I'll leave it that way. - Jim C.
466 item->GetMusicInfoTag()->SetLoaded(true);
469 else if (strcmpi(type,"pictures") == 0)
471 for (InfoLabelDict::const_iterator it = infoLabels.begin(); it != infoLabels.end(); ++it)
473 String key = it->first;
474 StringUtils::ToLower(key);
476 const InfoLabelValue& alt = it->second;
477 const String value(alt.which() == first ? alt.former() : emptyString);
480 item->m_iprogramCount = strtol(value.c_str(), NULL, 10);
481 else if (key == "size")
482 item->m_dwSize = (int64_t)strtoll(value.c_str(), NULL, 10);
483 else if (key == "title")
484 item->m_strTitle = value;
485 else if (key == "picturepath")
486 item->SetPath(value);
487 else if (key == "date")
489 if (strlen(value.c_str()) == 10)
491 int year = atoi(value.substr(value.size() - 4).c_str());
492 int month = atoi(value.substr(3, 4).c_str());
493 int day = atoi(value.substr(0, 2).c_str());
494 item->m_dateTime.SetDate(year, month, day);
499 const String& exifkey = key;
500 if (!StringUtils::StartsWithNoCase(exifkey, "exif:") || exifkey.length() < 6) continue;
501 int info = CPictureInfoTag::TranslateString(StringUtils::Mid(exifkey,5));
502 item->GetPictureInfoTag()->SetInfo(info, value);
506 } // end ListItem::setInfo
508 void ListItem::addStreamInfo(const char* cType, const Properties& dictionary)
512 if (strcmpi(cType, "video") == 0)
514 CStreamDetailVideo* video = new CStreamDetailVideo;
515 for (Properties::const_iterator it = dictionary.begin(); it != dictionary.end(); ++it)
517 const String& key = it->first;
518 const String value(it->second.c_str());
521 video->m_strCodec = value;
522 else if (key == "aspect")
523 video->m_fAspect = (float)atof(value.c_str());
524 else if (key == "width")
525 video->m_iWidth = strtol(value.c_str(), NULL, 10);
526 else if (key == "height")
527 video->m_iHeight = strtol(value.c_str(), NULL, 10);
528 else if (key == "duration")
529 video->m_iDuration = strtol(value.c_str(), NULL, 10);
530 else if (key == "stereomode")
531 video->m_strStereoMode = value;
533 item->GetVideoInfoTag()->m_streamDetails.AddStream(video);
535 else if (strcmpi(cType, "audio") == 0)
537 CStreamDetailAudio* audio = new CStreamDetailAudio;
538 for (Properties::const_iterator it = dictionary.begin(); it != dictionary.end(); ++it)
540 const String& key = it->first;
541 const String& value = it->second;
544 audio->m_strCodec = value;
545 else if (key == "language")
546 audio->m_strLanguage = value;
547 else if (key == "channels")
548 audio->m_iChannels = strtol(value.c_str(), NULL, 10);
550 item->GetVideoInfoTag()->m_streamDetails.AddStream(audio);
552 else if (strcmpi(cType, "subtitle") == 0)
554 CStreamDetailSubtitle* subtitle = new CStreamDetailSubtitle;
555 for (Properties::const_iterator it = dictionary.begin(); it != dictionary.end(); ++it)
557 const String& key = it->first;
558 const String& value = it->second;
560 if (key == "language")
561 subtitle->m_strLanguage = value;
563 item->GetVideoInfoTag()->m_streamDetails.AddStream(subtitle);
565 } // end ListItem::addStreamInfo
567 void ListItem::addContextMenuItems(const std::vector<Tuple<String,String> >& items, bool replaceItems /* = false */)
568 throw (ListItemException)
571 for (std::vector<Tuple<String,String> >::const_iterator iter = items.begin(); iter < items.end(); ++iter, ++itemCount)
573 Tuple<String,String> tuple = *iter;
574 if (tuple.GetNumValuesSet() != 2)
575 throw ListItemException("Must pass in a list of tuples of pairs of strings. One entry in the list only has %d elements.",tuple.GetNumValuesSet());
576 std::string uText = tuple.first();
577 std::string uAction = tuple.second();
581 property = StringUtils::Format("contextmenulabel(%i)", itemCount);
582 item->SetProperty(property, uText);
584 property = StringUtils::Format("contextmenuaction(%i)", itemCount);
585 item->SetProperty(property, uAction);
588 // set our replaceItems status
590 item->SetProperty("pluginreplacecontextitems", replaceItems);
591 } // end addContextMenuItems