Merge pull request #3819 from arnova/subtitles_for_stacks
[vuplus_xbmc] / xbmc / interfaces / legacy / ListItem.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
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)
8  *  any later version.
9  *
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.
14  *
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/>.
18  *
19  */
20
21 #include <sstream>
22
23 #include "ListItem.h"
24 #include "AddonUtils.h"
25
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"
33
34 namespace XBMCAddon
35 {
36   namespace xbmcgui
37   {
38     ListItem::ListItem(const String& label, 
39                        const String& label2,
40                        const String& iconImage,
41                        const String& thumbnailImage,
42                        const String& path)
43     {
44       item.reset();
45
46       // create CFileItem
47       item.reset(new CFileItem());
48       if (!item) // not sure if this is really possible
49         return;
50
51       if (!label.empty())
52         item->SetLabel( label );
53       if (!label2.empty())
54         item->SetLabel2( label2 );
55       if (!iconImage.empty())
56         item->SetIconImage( iconImage );
57       if (!thumbnailImage.empty())
58         item->SetArt("thumb",  thumbnailImage );
59       if (!path.empty())
60         item->SetPath(path);
61     }
62
63     ListItem::~ListItem()
64     {
65       item.reset();
66     }
67
68     String ListItem::getLabel()
69     {
70       if (!item) return "";
71
72       String ret;
73       {
74         LOCKGUI;
75         ret = item->GetLabel();
76       }
77
78       return ret;
79     }
80
81     String ListItem::getLabel2()
82     {
83       if (!item) return "";
84
85       String ret;
86       {
87         LOCKGUI;
88         ret = item->GetLabel2();
89       }
90
91       return ret;
92     }
93
94     void ListItem::setLabel(const String& label)
95     {
96       if (!item) return;
97       // set label
98       {
99         LOCKGUI;
100         item->SetLabel(label);
101       }
102     }
103
104     void ListItem::setLabel2(const String& label)
105     {
106       if (!item) return;
107       // set label
108       {
109         LOCKGUI;
110         item->SetLabel2(label);
111       }
112     }
113
114     void ListItem::setIconImage(const String& iconImage)
115     {
116       if (!item) return;
117       {
118         LOCKGUI;
119         item->SetIconImage(iconImage);
120       }
121     }
122
123     void ListItem::setThumbnailImage(const String& thumbFilename)
124     {
125       if (!item) return;
126       {
127         LOCKGUI;
128         item->SetArt("thumb", thumbFilename);
129       }
130     }
131
132     void ListItem::setArt(const Properties& dictionary)
133     {
134       if (!item) return;
135       {
136         LOCKGUI;
137         for (Properties::const_iterator it = dictionary.begin(); it != dictionary.end(); ++it)
138         {
139           CStdString artName = it->first;
140           StringUtils::ToLower(artName);
141           const CStdString artFilename(it->second.c_str());
142           item->SetArt(artName, artFilename);
143         }
144       }
145     }
146
147     void ListItem::select(bool selected)
148     {
149       if (!item) return;
150       {
151         LOCKGUI;
152         item->Select(selected);
153       }
154     }
155
156
157     bool ListItem::isSelected()
158     {
159       if (!item) return false;
160
161       bool ret;
162       {
163         LOCKGUI;
164         ret = item->IsSelected();
165       }
166
167       return ret;
168     }
169
170     void ListItem::setProperty(const char * key, const String& value)
171     {
172       LOCKGUI;
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
179       }
180       else if (lowerKey == "mimetype")
181       { // special case for mime type - don't actually stored in a property,
182         item->SetMimeType(value.c_str());
183       }
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")
189       {
190         if (value == "bottom")
191           item->SetSpecialSort(SortSpecialOnBottom);
192         else if (value == "top")
193           item->SetSpecialSort(SortSpecialOnTop);
194       }
195       else if (lowerKey == "fanart_image")
196         item->SetArt("fanart", value);
197       else
198         item->SetProperty(lowerKey, value);
199     }
200
201     String ListItem::getProperty(const char* key)
202     {
203       LOCKGUI;
204       String lowerKey = key;
205       StringUtils::ToLower(lowerKey);
206       CStdString value;
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);
211       }
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");
218       else
219         value = item->GetProperty(lowerKey).asString();
220
221       return value.c_str();
222     }
223
224     void ListItem::setPath(const String& path)
225     {
226       LOCKGUI;
227       item->SetPath(path);
228     }
229
230     void ListItem::setMimeType(const String& mimetype)
231     {
232       LOCKGUI;
233       item->SetMimeType(mimetype);
234     }
235
236     String ListItem::getdescription()
237     {
238       return item->GetLabel();
239     }
240
241     String ListItem::getduration()
242     {
243       if (item->LoadMusicTag())
244       {
245         std::ostringstream oss;
246         oss << item->GetMusicInfoTag()->GetDuration();
247         return oss.str();
248       }
249
250       if (item->HasVideoInfoTag())
251       {
252         std::ostringstream oss;
253         oss << item->GetVideoInfoTag()->GetDuration() / 60;
254         return oss.str();
255       }
256       return "0";
257     }
258
259     String ListItem::getfilename()
260     {
261       return item->GetPath();
262     }
263
264     void ListItem::setInfo(const char* type, const InfoLabelDict& infoLabels) throw (WrongTypeException)
265     {
266       LOCKGUI;
267
268       if (strcmpi(type, "video") == 0)
269       {
270         for (InfoLabelDict::const_iterator it = infoLabels.begin(); it != infoLabels.end(); ++it)
271         {
272           String key = it->first;
273           StringUtils::ToLower(key);
274
275           const InfoLabelValue& alt = it->second;
276           const String value(alt.which() == first ? alt.former() : emptyString);
277
278           if (key == "year")
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")
299           {
300             long overlay = strtol(value.c_str(), NULL, 10);
301             if (overlay >= 0 && overlay <= 8)
302               item->SetOverlayImage((CGUIListItem::GUIIconOverlay)overlay);
303           }
304           else if (key == "cast" || key == "castandrole")
305           {
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");
308
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)
312             {
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();
317               SActorInfo info;
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);
322             }
323           }
324           else if (key == "artist")
325           {
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");
328             
329             item->GetVideoInfoTag()->m_artist.clear();
330
331             const std::vector<InfoLabelStringOrTuple>& listValue = alt.later();
332             for (std::vector<InfoLabelStringOrTuple>::const_iterator viter = listValue.begin(); viter != listValue.end(); ++viter)
333             {
334               
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);
338             }
339           }
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")
385           {
386             if (value.length() == 10)
387             {
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);
392             }
393             else
394               CLog::Log(LOGERROR,"NEWADDON Invalid Date Format \"%s\"",value.c_str());
395           }
396           else if (key == "dateadded")
397             item->GetVideoInfoTag()->m_dateAdded.SetFromDBDateTime(value.c_str());
398         }
399       }
400       else if (strcmpi(type, "music") == 0)
401       {
402         for (InfoLabelDict::const_iterator it = infoLabels.begin(); it != infoLabels.end(); ++it)
403         {
404           String key = it->first;
405           StringUtils::ToLower(key);
406
407           const InfoLabelValue& alt = it->second;
408           const String value(alt.which() == first ? alt.former() : emptyString);
409
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")
452           {
453             if (strlen(value.c_str()) == 10)
454             {
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);
459             }
460           }
461           else
462             CLog::Log(LOGERROR,"NEWADDON Unknown Music Info Key \"%s\"", key.c_str());
463
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);
467         }
468       }
469       else if (strcmpi(type,"pictures") == 0)
470       {
471         for (InfoLabelDict::const_iterator it = infoLabels.begin(); it != infoLabels.end(); ++it)
472         {
473           String key = it->first;
474           StringUtils::ToLower(key);
475
476           const InfoLabelValue& alt = it->second;
477           const String value(alt.which() == first ? alt.former() : emptyString);
478
479           if (key == "count")
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")
488           {
489             if (strlen(value.c_str()) == 10)
490             {
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);
495             }
496           }
497           else
498           {
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);
503           }
504         }
505       }
506     } // end ListItem::setInfo
507
508     void ListItem::addStreamInfo(const char* cType, const Properties& dictionary)
509     {
510       LOCKGUI;
511
512       if (strcmpi(cType, "video") == 0)
513       {
514         CStreamDetailVideo* video = new CStreamDetailVideo;
515         for (Properties::const_iterator it = dictionary.begin(); it != dictionary.end(); ++it)
516         {
517           const String& key = it->first;
518           const String value(it->second.c_str());
519
520           if (key == "codec")
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;
532         }
533         item->GetVideoInfoTag()->m_streamDetails.AddStream(video);
534       }
535       else if (strcmpi(cType, "audio") == 0)
536       {
537         CStreamDetailAudio* audio = new CStreamDetailAudio;
538         for (Properties::const_iterator it = dictionary.begin(); it != dictionary.end(); ++it)
539         {
540           const String& key = it->first;
541           const String& value = it->second;
542
543           if (key == "codec")
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);
549         }
550         item->GetVideoInfoTag()->m_streamDetails.AddStream(audio);
551       }
552       else if (strcmpi(cType, "subtitle") == 0)
553       {
554         CStreamDetailSubtitle* subtitle = new CStreamDetailSubtitle;
555         for (Properties::const_iterator it = dictionary.begin(); it != dictionary.end(); ++it)
556         {
557           const String& key = it->first;
558           const String& value = it->second;
559
560           if (key == "language")
561             subtitle->m_strLanguage = value;
562         }
563         item->GetVideoInfoTag()->m_streamDetails.AddStream(subtitle);
564       }
565     } // end ListItem::addStreamInfo
566
567     void ListItem::addContextMenuItems(const std::vector<Tuple<String,String> >& items, bool replaceItems /* = false */)
568       throw (ListItemException)
569     {
570       int itemCount = 0;
571       for (std::vector<Tuple<String,String> >::const_iterator iter = items.begin(); iter < items.end(); ++iter, ++itemCount)
572       {
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();
578
579         LOCKGUI;
580         String property;
581         property = StringUtils::Format("contextmenulabel(%i)", itemCount);
582         item->SetProperty(property, uText);
583
584         property = StringUtils::Format("contextmenuaction(%i)", itemCount);
585         item->SetProperty(property, uAction);
586       }
587
588       // set our replaceItems status
589       if (replaceItems)
590         item->SetProperty("pluginreplacecontextitems", replaceItems);
591     } // end addContextMenuItems
592   }
593 }