strip added smb:// shares of their user/pass when adding, and instead store that...
[vuplus_xbmc] / xbmc / video / VideoInfoTag.cpp
1 /*
2  *      Copyright (C) 2005-2008 Team XBMC
3  *      http://www.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, write to
17  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
18  *  http://www.gnu.org/copyleft/gpl.html
19  *
20  */
21
22 #include "VideoInfoTag.h"
23 #include "utils/XMLUtils.h"
24 #include "guilib/LocalizeStrings.h"
25 #include "settings/GUISettings.h"
26 #include "settings/AdvancedSettings.h"
27 #include "utils/log.h"
28 #include "utils/StringUtils.h"
29 #include "utils/Variant.h"
30 #include "utils/CharsetConverter.h"
31 #include "TextureCache.h"
32 #include "filesystem/File.h"
33
34 #include <sstream>
35
36 using namespace std;
37
38 void CVideoInfoTag::Reset()
39 {
40   m_director.clear();
41   m_writingCredits.clear();
42   m_genre.clear();
43   m_country.clear();
44   m_strTagLine.clear();
45   m_strPlotOutline.clear();
46   m_strPlot.clear();
47   m_strPictureURL.Clear();
48   m_strTitle.clear();
49   m_strShowTitle.clear();
50   m_strOriginalTitle.clear();
51   m_strSortTitle.clear();
52   m_strVotes.clear();
53   m_cast.clear();
54   m_set.clear();
55   m_setId.clear();
56   m_strFile.clear();
57   m_strPath.clear();
58   m_strIMDBNumber.clear();
59   m_strMPAARating.clear();
60   m_strFileNameAndPath.clear();
61   m_premiered.Reset();
62   m_strStatus.clear();
63   m_strProductionCode.clear();
64   m_firstAired.Reset();
65   m_studio.clear();
66   m_strAlbum.clear();
67   m_artist.clear();
68   m_strTrailer.clear();
69   m_iTop250 = 0;
70   m_iYear = 0;
71   m_iSeason = -1;
72   m_iEpisode = -1;
73   m_iSpecialSortSeason = -1;
74   m_iSpecialSortEpisode = -1;
75   m_fRating = 0.0f;
76   m_iDbId = -1;
77   m_iFileId = -1;
78   m_iBookmarkId = -1;
79   m_iTrack = -1;
80   m_fanart.m_xml.clear();
81   m_strRuntime.clear();
82   m_lastPlayed.Reset();
83   m_showLink.clear();
84   m_streamDetails.Reset();
85   m_playCount = 0;
86   m_fEpBookmark = 0;
87   m_basePath.clear();
88   m_parentPathID = -1;
89   m_resumePoint.Reset();
90   m_resumePoint.type = CBookmark::RESUME;
91   m_iIdShow = -1;
92   m_iIdSeason = -1;
93   m_strShowPath.clear();
94   m_dateAdded.Reset();
95   m_type.clear();
96 }
97
98 bool CVideoInfoTag::Save(TiXmlNode *node, const CStdString &tag, bool savePathInfo, const TiXmlElement *additionalNode)
99 {
100   if (!node) return false;
101
102   // we start with a <tag> tag
103   TiXmlElement movieElement(tag.c_str());
104   TiXmlNode *movie = node->InsertEndChild(movieElement);
105
106   if (!movie) return false;
107
108   XMLUtils::SetString(movie, "title", m_strTitle);
109   if (!m_strOriginalTitle.IsEmpty())
110     XMLUtils::SetString(movie, "originaltitle", m_strOriginalTitle);
111   if (!m_strShowTitle.IsEmpty())
112     XMLUtils::SetString(movie, "showtitle", m_strShowTitle);
113   if (!m_strSortTitle.IsEmpty())
114     XMLUtils::SetString(movie, "sorttitle", m_strSortTitle);
115   XMLUtils::SetFloat(movie, "rating", m_fRating);
116   XMLUtils::SetFloat(movie, "epbookmark", m_fEpBookmark);
117   XMLUtils::SetInt(movie, "year", m_iYear);
118   XMLUtils::SetInt(movie, "top250", m_iTop250);
119   if (tag == "episodedetails" || tag == "tvshow")
120   {
121     XMLUtils::SetInt(movie, "season", m_iSeason);
122     XMLUtils::SetInt(movie, "episode", m_iEpisode);
123     XMLUtils::SetInt(movie, "displayseason",m_iSpecialSortSeason);
124     XMLUtils::SetInt(movie, "displayepisode",m_iSpecialSortEpisode);
125   }
126   if (tag == "musicvideo")
127   {
128     XMLUtils::SetInt(movie, "track", m_iTrack);
129     XMLUtils::SetString(movie, "album", m_strAlbum);
130   }
131   XMLUtils::SetString(movie, "votes", m_strVotes);
132   XMLUtils::SetString(movie, "outline", m_strPlotOutline);
133   XMLUtils::SetString(movie, "plot", m_strPlot);
134   XMLUtils::SetString(movie, "tagline", m_strTagLine);
135   XMLUtils::SetString(movie, "runtime", m_strRuntime);
136   if (!m_strPictureURL.m_xml.empty())
137   {
138     CXBMCTinyXML doc;
139     doc.Parse(m_strPictureURL.m_xml);
140     const TiXmlNode* thumb = doc.FirstChild("thumb");
141     while (thumb)
142     {
143       movie->InsertEndChild(*thumb);
144       thumb = thumb->NextSibling("thumb");
145     }
146   }
147   if (m_fanart.m_xml.size())
148   {
149     CXBMCTinyXML doc;
150     doc.Parse(m_fanart.m_xml);
151     movie->InsertEndChild(*doc.RootElement());
152   }
153   XMLUtils::SetString(movie, "mpaa", m_strMPAARating);
154   XMLUtils::SetInt(movie, "playcount", m_playCount);
155   XMLUtils::SetDate(movie, "lastplayed", m_lastPlayed);
156   if (savePathInfo)
157   {
158     XMLUtils::SetString(movie, "file", m_strFile);
159     XMLUtils::SetString(movie, "path", m_strPath);
160     XMLUtils::SetString(movie, "filenameandpath", m_strFileNameAndPath);
161     XMLUtils::SetString(movie, "basepath", m_basePath);
162   }
163   if (!m_strEpisodeGuide.IsEmpty())
164   {
165     CXBMCTinyXML doc;
166     doc.Parse(m_strEpisodeGuide);
167     if (doc.RootElement())
168       movie->InsertEndChild(*doc.RootElement());
169     else
170       XMLUtils::SetString(movie, "episodeguide", m_strEpisodeGuide);
171   }
172
173   XMLUtils::SetString(movie, "id", m_strIMDBNumber);
174   XMLUtils::SetStringArray(movie, "genre", m_genre);
175   XMLUtils::SetStringArray(movie, "country", m_country);
176   XMLUtils::SetStringArray(movie, "set", m_set);
177   XMLUtils::SetStringArray(movie, "credits", m_writingCredits);
178   XMLUtils::SetStringArray(movie, "director", m_director);
179   XMLUtils::SetDate(movie, "premiered", m_premiered);
180   XMLUtils::SetString(movie, "status", m_strStatus);
181   XMLUtils::SetString(movie, "code", m_strProductionCode);
182   XMLUtils::SetDate(movie, "aired", m_firstAired);
183   XMLUtils::SetStringArray(movie, "studio", m_studio);
184   XMLUtils::SetString(movie, "trailer", m_strTrailer);
185
186   if (m_streamDetails.HasItems())
187   {
188     // it goes fileinfo/streamdetails/[video|audio|subtitle]
189     TiXmlElement fileinfo("fileinfo");
190     TiXmlElement streamdetails("streamdetails");
191     for (int iStream=1; iStream<=m_streamDetails.GetVideoStreamCount(); iStream++)
192     {
193       TiXmlElement stream("video");
194       XMLUtils::SetString(&stream, "codec", m_streamDetails.GetVideoCodec(iStream));
195       XMLUtils::SetFloat(&stream, "aspect", m_streamDetails.GetVideoAspect(iStream));
196       XMLUtils::SetInt(&stream, "width", m_streamDetails.GetVideoWidth(iStream));
197       XMLUtils::SetInt(&stream, "height", m_streamDetails.GetVideoHeight(iStream));
198       XMLUtils::SetInt(&stream, "durationinseconds", m_streamDetails.GetVideoDuration(iStream));
199       streamdetails.InsertEndChild(stream);
200     }
201     for (int iStream=1; iStream<=m_streamDetails.GetAudioStreamCount(); iStream++)
202     {
203       TiXmlElement stream("audio");
204       XMLUtils::SetString(&stream, "codec", m_streamDetails.GetAudioCodec(iStream));
205       XMLUtils::SetString(&stream, "language", m_streamDetails.GetAudioLanguage(iStream));
206       XMLUtils::SetInt(&stream, "channels", m_streamDetails.GetAudioChannels(iStream));
207       streamdetails.InsertEndChild(stream);
208     }
209     for (int iStream=1; iStream<=m_streamDetails.GetSubtitleStreamCount(); iStream++)
210     {
211       TiXmlElement stream("subtitle");
212       XMLUtils::SetString(&stream, "language", m_streamDetails.GetSubtitleLanguage(iStream));
213       streamdetails.InsertEndChild(stream);
214     }
215     fileinfo.InsertEndChild(streamdetails);
216     movie->InsertEndChild(fileinfo);
217   }  /* if has stream details */
218
219   // cast
220   for (iCast it = m_cast.begin(); it != m_cast.end(); ++it)
221   {
222     // add a <actor> tag
223     TiXmlElement cast("actor");
224     TiXmlNode *node = movie->InsertEndChild(cast);
225     TiXmlElement actor("name");
226     TiXmlNode *actorNode = node->InsertEndChild(actor);
227     TiXmlText name(it->strName);
228     actorNode->InsertEndChild(name);
229     TiXmlElement role("role");
230     TiXmlNode *roleNode = node->InsertEndChild(role);
231     TiXmlText character(it->strRole);
232     roleNode->InsertEndChild(character);
233     TiXmlElement thumb("thumb");
234     TiXmlNode *thumbNode = node->InsertEndChild(thumb);
235     TiXmlText th(it->thumbUrl.GetFirstThumb().m_url);
236     thumbNode->InsertEndChild(th);
237   }
238   XMLUtils::SetStringArray(movie, "artist", m_artist);
239   XMLUtils::SetStringArray(movie, "showlink", m_showLink);
240  
241   TiXmlElement resume("resume");
242   XMLUtils::SetFloat(&resume, "position", (float)m_resumePoint.timeInSeconds);
243   XMLUtils::SetFloat(&resume, "total", (float)m_resumePoint.totalTimeInSeconds);
244   movie->InsertEndChild(resume);
245
246   XMLUtils::SetString(movie, "dateadded", m_dateAdded.GetAsDBDateTime());
247
248   if (additionalNode)
249     movie->InsertEndChild(*additionalNode);
250
251   return true;
252 }
253
254 bool CVideoInfoTag::Load(const TiXmlElement *element, bool append, bool prioritise)
255 {
256   if (!element)
257     return false;
258   if (!append)
259     Reset();
260   ParseNative(element, prioritise);
261   return true;
262 }
263
264 void CVideoInfoTag::Archive(CArchive& ar)
265 {
266   if (ar.IsStoring())
267   {
268     ar << m_director;
269     ar << m_writingCredits;
270     ar << m_genre;
271     ar << m_country;
272     ar << m_strTagLine;
273     ar << m_strPlotOutline;
274     ar << m_strPlot;
275     ar << m_strPictureURL.m_spoof;
276     ar << m_strPictureURL.m_xml;
277     ar << m_fanart.m_xml;
278     ar << m_strTitle;
279     ar << m_strSortTitle;
280     ar << m_strVotes;
281     ar << m_studio;
282     ar << m_strTrailer;
283     ar << (int)m_cast.size();
284     for (unsigned int i=0;i<m_cast.size();++i)
285     {
286       ar << m_cast[i].strName;
287       ar << m_cast[i].strRole;
288       ar << m_cast[i].thumb;
289       ar << m_cast[i].thumbUrl.m_xml;
290     }
291
292     ar << m_set;
293     ar << m_setId;
294     ar << m_strRuntime;
295     ar << m_strFile;
296     ar << m_strPath;
297     ar << m_strIMDBNumber;
298     ar << m_strMPAARating;
299     ar << m_strFileNameAndPath;
300     ar << m_strOriginalTitle;
301     ar << m_strEpisodeGuide;
302     ar << m_premiered;
303     ar << m_strStatus;
304     ar << m_strProductionCode;
305     ar << m_firstAired;
306     ar << m_strShowTitle;
307     ar << m_strAlbum;
308     ar << m_artist;
309     ar << m_playCount;
310     ar << m_lastPlayed;
311     ar << m_iTop250;
312     ar << m_iYear;
313     ar << m_iSeason;
314     ar << m_iEpisode;
315     ar << m_fRating;
316     ar << m_iDbId;
317     ar << m_iFileId;
318     ar << m_iSpecialSortSeason;
319     ar << m_iSpecialSortEpisode;
320     ar << m_iBookmarkId;
321     ar << m_iTrack;
322     ar << dynamic_cast<IArchivable&>(m_streamDetails);
323     ar << m_showLink;
324     ar << m_fEpBookmark;
325     ar << m_basePath;
326     ar << m_parentPathID;
327     ar << m_resumePoint.timeInSeconds;
328     ar << m_resumePoint.totalTimeInSeconds;
329     ar << m_iIdShow;
330     ar << m_strShowPath;
331     ar << m_dateAdded.GetAsDBDateTime();
332     ar << m_type;
333     ar << m_iIdSeason;
334   }
335   else
336   {
337     ar >> m_director;
338     ar >> m_writingCredits;
339     ar >> m_genre;
340     ar >> m_country;
341     ar >> m_strTagLine;
342     ar >> m_strPlotOutline;
343     ar >> m_strPlot;
344     ar >> m_strPictureURL.m_spoof;
345     ar >> m_strPictureURL.m_xml;
346     m_strPictureURL.Parse();
347     ar >> m_fanart.m_xml;
348     m_fanart.Unpack();
349     ar >> m_strTitle;
350     ar >> m_strSortTitle;
351     ar >> m_strVotes;
352     ar >> m_studio;
353     ar >> m_strTrailer;
354     int iCastSize;
355     ar >> iCastSize;
356     for (int i=0;i<iCastSize;++i)
357     {
358       SActorInfo info;
359       ar >> info.strName;
360       ar >> info.strRole;
361       ar >> info.thumb;
362       CStdString strXml;
363       ar >> strXml;
364       info.thumbUrl.ParseString(strXml);
365       m_cast.push_back(info);
366     }
367
368     ar >> m_set;
369     ar >> m_setId;
370     ar >> m_strRuntime;
371     ar >> m_strFile;
372     ar >> m_strPath;
373     ar >> m_strIMDBNumber;
374     ar >> m_strMPAARating;
375     ar >> m_strFileNameAndPath;
376     ar >> m_strOriginalTitle;
377     ar >> m_strEpisodeGuide;
378     ar >> m_premiered;
379     ar >> m_strStatus;
380     ar >> m_strProductionCode;
381     ar >> m_firstAired;
382     ar >> m_strShowTitle;
383     ar >> m_strAlbum;
384     ar >> m_artist;
385     ar >> m_playCount;
386     ar >> m_lastPlayed;
387     ar >> m_iTop250;
388     ar >> m_iYear;
389     ar >> m_iSeason;
390     ar >> m_iEpisode;
391     ar >> m_fRating;
392     ar >> m_iDbId;
393     ar >> m_iFileId;
394     ar >> m_iSpecialSortSeason;
395     ar >> m_iSpecialSortEpisode;
396     ar >> m_iBookmarkId;
397     ar >> m_iTrack;
398     ar >> dynamic_cast<IArchivable&>(m_streamDetails);
399     ar >> m_showLink;
400     ar >> m_fEpBookmark;
401     ar >> m_basePath;
402     ar >> m_parentPathID;
403     ar >> m_resumePoint.timeInSeconds;
404     ar >> m_resumePoint.totalTimeInSeconds;
405     ar >> m_iIdShow;
406     ar >> m_strShowPath;
407
408     CStdString dateAdded;
409     ar >> dateAdded;
410     m_dateAdded.SetFromDBDateTime(dateAdded);
411     ar >> m_type;
412     ar >> m_iIdSeason;
413   }
414 }
415
416 void CVideoInfoTag::Serialize(CVariant& value)
417 {
418   /* TODO:
419      All the StringUtils::Join() calls can be removed once backwards-compatibility to
420      JSON-RPC v4 can be broken */
421   value["director"] = StringUtils::Join(m_director, " / ");
422   value["writer"] = StringUtils::Join(m_writingCredits, " / ");
423   value["genre"] = StringUtils::Join(m_genre, " / ");
424   value["country"] = StringUtils::Join(m_country, " / ");
425   value["tagline"] = m_strTagLine;
426   value["plotoutline"] = m_strPlotOutline;
427   value["plot"] = m_strPlot;
428   value["title"] = m_strTitle;
429   value["votes"] = m_strVotes;
430   value["studio"] = StringUtils::Join(m_studio, " / ");
431   value["trailer"] = m_strTrailer;
432   value["cast"] = CVariant(CVariant::VariantTypeArray);
433   for (unsigned int i = 0; i < m_cast.size(); ++i)
434   {
435     CVariant actor;
436     actor["name"] = m_cast[i].strName;
437     actor["role"] = m_cast[i].strRole;
438     if (!m_cast[i].thumb.IsEmpty())
439       actor["thumbnail"] = CTextureCache::GetWrappedImageURL(m_cast[i].thumb);
440     value["cast"].push_back(actor);
441   }
442   value["set"] = m_set;
443   value["setid"] = CVariant(CVariant::VariantTypeArray);
444   for (unsigned int i = 0; i < m_setId.size(); i++)
445     value["setid"].push_back(m_setId[i]);
446   value["runtime"] = m_strRuntime;
447   value["file"] = m_strFile;
448   value["path"] = m_strPath;
449   value["imdbnumber"] = m_strIMDBNumber;
450   value["mpaa"] = m_strMPAARating;
451   value["filenameandpath"] = m_strFileNameAndPath;
452   value["originaltitle"] = m_strOriginalTitle;
453   value["sorttitle"] = m_strSortTitle;
454   value["episodeguide"] = m_strEpisodeGuide;
455   value["premiered"] = m_premiered.IsValid() ? m_premiered.GetAsDBDate() : StringUtils::EmptyString;
456   value["status"] = m_strStatus;
457   value["productioncode"] = m_strProductionCode;
458   value["firstaired"] = m_firstAired.IsValid() ? m_firstAired.GetAsDBDate() : StringUtils::EmptyString;
459   value["showtitle"] = m_strShowTitle;
460   value["album"] = m_strAlbum;
461   value["artist"] = StringUtils::Join(m_artist, " / ");
462   value["playcount"] = m_playCount;
463   value["lastplayed"] = m_lastPlayed.IsValid() ? m_lastPlayed.GetAsDBDateTime() : StringUtils::EmptyString;
464   value["top250"] = m_iTop250;
465   value["year"] = m_iYear;
466   value["season"] = m_iSeason;
467   value["episode"] = m_iEpisode;
468   value["rating"] = m_fRating;
469   value["dbid"] = m_iDbId;
470   value["fileid"] = m_iFileId;
471   value["track"] = m_iTrack;
472   value["showlink"] = StringUtils::Join(m_showLink, " / ");
473   m_streamDetails.Serialize(value["streamdetails"]);
474   CVariant resume = CVariant(CVariant::VariantTypeObject);
475   resume["position"] = (float)m_resumePoint.timeInSeconds;
476   resume["total"] = (float)m_resumePoint.totalTimeInSeconds;
477   value["resume"] = resume;
478   value["tvshowid"] = m_iIdShow;
479   value["tvshowpath"] = m_strShowPath;
480   value["dateadded"] = m_dateAdded.IsValid() ? m_dateAdded.GetAsDBDateTime() : StringUtils::EmptyString;
481   value["type"] = m_type;
482   value["seasonid"] = m_iIdSeason;
483 }
484
485 void CVideoInfoTag::ToSortable(SortItem& sortable)
486 {
487   sortable[FieldDirector] = m_director;
488   sortable[FieldWriter] = m_writingCredits;
489   sortable[FieldGenre] = m_genre;
490   sortable[FieldCountry] = m_country;
491   sortable[FieldTagline] = m_strTagLine;
492   sortable[FieldPlotOutline] = m_strPlotOutline;
493   sortable[FieldPlot] = m_strPlot;
494   sortable[FieldTitle] = m_strTitle;
495   sortable[FieldVotes] = m_strVotes;
496   sortable[FieldStudio] = m_studio;
497   sortable[FieldTrailer] = m_strTrailer;
498   sortable[FieldSet] = m_set;
499   sortable[FieldTime] = m_strRuntime;
500   sortable[FieldFilename] = m_strFile;
501   sortable[FieldMPAA] = m_strMPAARating;
502   sortable[FieldPath] = m_strFileNameAndPath;
503   sortable[FieldSortTitle] = m_strSortTitle;
504   sortable[FieldTvShowStatus] = m_strStatus;
505   sortable[FieldProductionCode] = m_strProductionCode;
506   sortable[FieldAirDate] = m_firstAired.IsValid() ? m_firstAired.GetAsDBDate() : (m_premiered.IsValid() ? m_premiered.GetAsDBDate() : StringUtils::EmptyString);
507   sortable[FieldTvShowTitle] = m_strShowTitle;
508   sortable[FieldAlbum] = m_strAlbum;
509   sortable[FieldArtist] = m_artist;
510   sortable[FieldPlaycount] = m_playCount;
511   sortable[FieldLastPlayed] = m_lastPlayed.IsValid() ? m_lastPlayed.GetAsDBDateTime() : StringUtils::EmptyString;
512   sortable[FieldTop250] = m_iTop250;
513   sortable[FieldYear] = m_iYear;
514   sortable[FieldSeason] = m_iSeason;
515   sortable[FieldEpisodeNumber] = m_iEpisode;
516   sortable[FieldRating] = m_fRating;
517   sortable[FieldId] = m_iDbId;
518   sortable[FieldTrackNumber] = m_iTrack;
519
520   sortable[FieldTime] = m_streamDetails.GetVideoDuration();
521   sortable[FieldVideoResolution] = m_streamDetails.GetVideoHeight();
522   sortable[FieldVideoAspectRatio] = m_streamDetails.GetVideoAspect();
523   sortable[FieldVideoCodec] = m_streamDetails.GetVideoCodec();
524   
525   sortable[FieldAudioChannels] = m_streamDetails.GetAudioChannels();
526   sortable[FieldAudioCodec] = m_streamDetails.GetAudioCodec();
527   sortable[FieldAudioLanguage] = m_streamDetails.GetAudioLanguage();
528   
529   sortable[FieldSubtitleLanguage] = m_streamDetails.GetSubtitleLanguage();
530
531   sortable[FieldInProgress] = m_resumePoint.timeInSeconds > 0 && m_resumePoint.totalTimeInSeconds > 0;
532   sortable[FieldDateAdded] = m_dateAdded.IsValid() ? m_dateAdded.GetAsDBDateTime() : StringUtils::EmptyString;
533   sortable[FieldMediaType] = DatabaseUtils::MediaTypeFromString(m_type);
534 }
535
536 const CStdString CVideoInfoTag::GetCast(bool bIncludeRole /*= false*/) const
537 {
538   CStdString strLabel;
539   for (iCast it = m_cast.begin(); it != m_cast.end(); ++it)
540   {
541     CStdString character;
542     if (it->strRole.IsEmpty() || !bIncludeRole)
543       character.Format("%s\n", it->strName.c_str());
544     else
545       character.Format("%s %s %s\n", it->strName.c_str(), g_localizeStrings.Get(20347).c_str(), it->strRole.c_str());
546     strLabel += character;
547   }
548   return strLabel.TrimRight("\n");
549 }
550
551 void CVideoInfoTag::ParseNative(const TiXmlElement* movie, bool prioritise)
552 {
553   XMLUtils::GetString(movie, "title", m_strTitle);
554   XMLUtils::GetString(movie, "originaltitle", m_strOriginalTitle);
555   XMLUtils::GetString(movie, "showtitle", m_strShowTitle);
556   XMLUtils::GetString(movie, "sorttitle", m_strSortTitle);
557   XMLUtils::GetFloat(movie, "rating", m_fRating);
558   XMLUtils::GetFloat(movie, "epbookmark", m_fEpBookmark);
559   int max_value = 10;
560   const TiXmlElement* rElement = movie->FirstChildElement("rating");
561   if (rElement && (rElement->QueryIntAttribute("max", &max_value) == TIXML_SUCCESS) && max_value>=1)
562   {
563     m_fRating = m_fRating / max_value * 10; // Normalise the Movie Rating to between 1 and 10
564   }
565   XMLUtils::GetInt(movie, "year", m_iYear);
566   XMLUtils::GetInt(movie, "top250", m_iTop250);
567   XMLUtils::GetInt(movie, "season", m_iSeason);
568   XMLUtils::GetInt(movie, "episode", m_iEpisode);
569   XMLUtils::GetInt(movie, "track", m_iTrack);
570   XMLUtils::GetInt(movie, "displayseason", m_iSpecialSortSeason);
571   XMLUtils::GetInt(movie, "displayepisode", m_iSpecialSortEpisode);
572   int after=0;
573   XMLUtils::GetInt(movie, "displayafterseason",after);
574   if (after > 0)
575   {
576     m_iSpecialSortSeason = after;
577     m_iSpecialSortEpisode = 0x1000; // should be more than any realistic episode number
578   }
579   XMLUtils::GetString(movie, "votes", m_strVotes);
580   XMLUtils::GetString(movie, "outline", m_strPlotOutline);
581   XMLUtils::GetString(movie, "plot", m_strPlot);
582   XMLUtils::GetString(movie, "tagline", m_strTagLine);
583   XMLUtils::GetString(movie, "runtime", m_strRuntime);
584   XMLUtils::GetString(movie, "mpaa", m_strMPAARating);
585   XMLUtils::GetInt(movie, "playcount", m_playCount);
586   XMLUtils::GetDate(movie, "lastplayed", m_lastPlayed);
587   XMLUtils::GetString(movie, "file", m_strFile);
588   XMLUtils::GetString(movie, "path", m_strPath);
589   XMLUtils::GetString(movie, "id", m_strIMDBNumber);
590   XMLUtils::GetString(movie, "filenameandpath", m_strFileNameAndPath);
591   XMLUtils::GetDate(movie, "premiered", m_premiered);
592   XMLUtils::GetString(movie, "status", m_strStatus);
593   XMLUtils::GetString(movie, "code", m_strProductionCode);
594   XMLUtils::GetDate(movie, "aired", m_firstAired);
595   XMLUtils::GetString(movie, "album", m_strAlbum);
596   XMLUtils::GetString(movie, "trailer", m_strTrailer);
597   XMLUtils::GetString(movie, "basepath", m_basePath);
598
599   size_t iThumbCount = m_strPictureURL.m_url.size();
600   CStdString xmlAdd = m_strPictureURL.m_xml;
601
602   const TiXmlElement* thumb = movie->FirstChildElement("thumb");
603   while (thumb)
604   {
605     m_strPictureURL.ParseElement(thumb);
606     if (prioritise)
607     {
608       CStdString temp;
609       temp << *thumb;
610       xmlAdd = temp+xmlAdd;
611     }
612     thumb = thumb->NextSiblingElement("thumb");
613   }
614
615   // prioritise thumbs from nfos
616   if (prioritise && iThumbCount && iThumbCount != m_strPictureURL.m_url.size())
617   {
618     rotate(m_strPictureURL.m_url.begin(),
619            m_strPictureURL.m_url.begin()+iThumbCount, 
620            m_strPictureURL.m_url.end());
621     m_strPictureURL.m_xml = xmlAdd;
622   }
623
624   XMLUtils::GetStringArray(movie, "genre", m_genre, prioritise, g_advancedSettings.m_videoItemSeparator);
625   XMLUtils::GetStringArray(movie, "country", m_country, prioritise, g_advancedSettings.m_videoItemSeparator);
626   XMLUtils::GetStringArray(movie, "credits", m_writingCredits, prioritise, g_advancedSettings.m_videoItemSeparator);
627   XMLUtils::GetStringArray(movie, "director", m_director, prioritise, g_advancedSettings.m_videoItemSeparator);
628   XMLUtils::GetStringArray(movie, "showlink", m_showLink, prioritise, g_advancedSettings.m_videoItemSeparator);
629
630   // cast
631   const TiXmlElement* node = movie->FirstChildElement("actor");
632   if (node && node->FirstChild() && prioritise)
633     m_cast.clear();
634   while (node)
635   {
636     const TiXmlNode *actor = node->FirstChild("name");
637     if (actor && actor->FirstChild())
638     {
639       SActorInfo info;
640       info.strName = actor->FirstChild()->Value();
641       const TiXmlNode *roleNode = node->FirstChild("role");
642       if (roleNode && roleNode->FirstChild())
643         info.strRole = roleNode->FirstChild()->Value();
644       const TiXmlElement* thumb = node->FirstChildElement("thumb");
645       while (thumb)
646       {
647         info.thumbUrl.ParseElement(thumb);
648         thumb = thumb->NextSiblingElement("thumb");
649       }
650       const char* clear=node->Attribute("clear");
651       if (clear && stricmp(clear,"true"))
652         m_cast.clear();
653       m_cast.push_back(info);
654     }
655     node = node->NextSiblingElement("actor");
656   }
657
658   XMLUtils::GetStringArray(movie, "set", m_set, prioritise, g_advancedSettings.m_videoItemSeparator);
659   XMLUtils::GetStringArray(movie, "studio", m_studio, prioritise, g_advancedSettings.m_videoItemSeparator);
660   // artists
661   node = movie->FirstChildElement("artist");
662   if (node && node->FirstChild() && prioritise)
663     m_artist.clear();
664   while (node)
665   {
666     const TiXmlNode* pNode = node->FirstChild("name");
667     const char* pValue=NULL;
668     if (pNode && pNode->FirstChild())
669       pValue = pNode->FirstChild()->Value();
670     else if (node->FirstChild())
671       pValue = node->FirstChild()->Value();
672     if (pValue)
673     {
674       const char* clear=node->Attribute("clear");
675       if (clear && stricmp(clear,"true")==0)
676         m_artist.clear();
677       vector<string> artists = StringUtils::Split(pValue, g_advancedSettings.m_videoItemSeparator);
678       m_artist.insert(m_artist.end(), artists.begin(), artists.end());
679     }
680     node = node->NextSiblingElement("artist");
681   }
682
683   node = movie->FirstChildElement("fileinfo");
684   if (node)
685   {
686     // Try to pull from fileinfo/streamdetails/[video|audio|subtitle]
687     const TiXmlNode *nodeStreamDetails = node->FirstChild("streamdetails");
688     if (nodeStreamDetails)
689     {
690       const TiXmlNode *nodeDetail = NULL;
691       while ((nodeDetail = nodeStreamDetails->IterateChildren("audio", nodeDetail)))
692       {
693         CStreamDetailAudio *p = new CStreamDetailAudio();
694         XMLUtils::GetString(nodeDetail, "codec", p->m_strCodec);
695         XMLUtils::GetString(nodeDetail, "language", p->m_strLanguage);
696         XMLUtils::GetInt(nodeDetail, "channels", p->m_iChannels);
697         p->m_strCodec.MakeLower();
698         p->m_strLanguage.MakeLower();
699         m_streamDetails.AddStream(p);
700       }
701       nodeDetail = NULL;
702       while ((nodeDetail = nodeStreamDetails->IterateChildren("video", nodeDetail)))
703       {
704         CStreamDetailVideo *p = new CStreamDetailVideo();
705         XMLUtils::GetString(nodeDetail, "codec", p->m_strCodec);
706         XMLUtils::GetFloat(nodeDetail, "aspect", p->m_fAspect);
707         XMLUtils::GetInt(nodeDetail, "width", p->m_iWidth);
708         XMLUtils::GetInt(nodeDetail, "height", p->m_iHeight);
709         XMLUtils::GetInt(nodeDetail, "durationinseconds", p->m_iDuration);
710         p->m_strCodec.MakeLower();
711         m_streamDetails.AddStream(p);
712       }
713       nodeDetail = NULL;
714       while ((nodeDetail = nodeStreamDetails->IterateChildren("subtitle", nodeDetail)))
715       {
716         CStreamDetailSubtitle *p = new CStreamDetailSubtitle();
717         XMLUtils::GetString(nodeDetail, "language", p->m_strLanguage);
718         p->m_strLanguage.MakeLower();
719         m_streamDetails.AddStream(p);
720       }
721     }
722     m_streamDetails.DetermineBestStreams();
723   }  /* if fileinfo */
724
725   const TiXmlElement *epguide = movie->FirstChildElement("episodeguide");
726   if (epguide)
727   {
728     // DEPRECIATE ME - support for old XML-encoded <episodeguide> blocks.
729     if (epguide->FirstChild() && strnicmp("<episodeguide", epguide->FirstChild()->Value(), 13) == 0)
730       m_strEpisodeGuide = epguide->FirstChild()->Value();
731     else
732     {
733       stringstream stream;
734       stream << *epguide;
735       m_strEpisodeGuide = stream.str();
736     }
737   }
738
739   // fanart
740   const TiXmlElement *fanart = movie->FirstChildElement("fanart");
741   if (fanart)
742   {
743     // we prioritise mixed-mode nfo's with fanart set
744     if (prioritise)
745     {
746       CStdString temp;
747       temp << *fanart;
748       m_fanart.m_xml = temp+m_fanart.m_xml;
749     }
750     else
751       m_fanart.m_xml << *fanart;
752     m_fanart.Unpack();
753   }
754
755   // resumePoint
756   const TiXmlNode *resume = movie->FirstChild("resume");
757   if (resume)
758   {
759     XMLUtils::GetDouble(resume, "position", m_resumePoint.timeInSeconds);
760     XMLUtils::GetDouble(resume, "total", m_resumePoint.totalTimeInSeconds);
761   }
762
763   // dateAdded
764   CStdString dateAdded;
765   XMLUtils::GetString(movie, "dateadded", dateAdded);
766   m_dateAdded.SetFromDBDateTime(dateAdded);
767 }
768
769 bool CVideoInfoTag::HasStreamDetails() const
770 {
771   return m_streamDetails.HasItems();
772 }
773
774 bool CVideoInfoTag::IsEmpty() const
775 {
776   return (m_strTitle.IsEmpty() &&
777           m_strFile.IsEmpty() &&
778           m_strPath.IsEmpty());
779 }