Merge pull request #116 from IronTetsubo/master
[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/Variant.h"
29 #include "utils/CharsetConverter.h"
30
31 #include <sstream>
32
33 using namespace std;
34
35 void CVideoInfoTag::Reset()
36 {
37   m_strDirector = "";
38   m_strWritingCredits = "";
39   m_strGenre = "";
40   m_strCountry = "";
41   m_strTagLine = "";
42   m_strPlotOutline = "";
43   m_strPlot = "";
44   m_strPictureURL.Clear();
45   m_strTitle = "";
46   m_strOriginalTitle = "";
47   m_strSortTitle = "";
48   m_strVotes = "";
49   m_cast.clear();
50   m_strSet = "";
51   m_strFile = "";
52   m_strPath = "";
53   m_strIMDBNumber = "";
54   m_strMPAARating = "";
55   m_strPremiered= "";
56   m_strStatus= "";
57   m_strProductionCode= "";
58   m_strFirstAired= "";
59   m_strStudio = "";
60   m_strAlbum = "";
61   m_strArtist = "";
62   m_strTrailer = "";
63   m_iTop250 = 0;
64   m_iYear = 0;
65   m_iSeason = -1;
66   m_iEpisode = -1;
67   m_iSpecialSortSeason = -1;
68   m_iSpecialSortEpisode = -1;
69   m_fRating = 0.0f;
70   m_iDbId = -1;
71   m_iFileId = -1;
72   m_iBookmarkId = -1;
73   m_iTrack = -1;
74   m_fanart.m_xml = "";
75   m_strRuntime = "";
76   m_lastPlayed = "";
77   m_strShowLink = "";
78   m_streamDetails.Reset();
79   m_playCount = 0;
80   m_fEpBookmark = 0;
81   m_basePath = "";
82 }
83
84 bool CVideoInfoTag::Save(TiXmlNode *node, const CStdString &tag, bool savePathInfo)
85 {
86   if (!node) return false;
87
88   // we start with a <tag> tag
89   TiXmlElement movieElement(tag.c_str());
90   TiXmlNode *movie = node->InsertEndChild(movieElement);
91
92   if (!movie) return false;
93
94   XMLUtils::SetString(movie, "title", m_strTitle);
95   if (!m_strOriginalTitle.IsEmpty())
96     XMLUtils::SetString(movie, "originaltitle", m_strOriginalTitle);
97   if (!m_strSortTitle.IsEmpty())
98     XMLUtils::SetString(movie, "sorttitle", m_strSortTitle);
99   XMLUtils::SetFloat(movie, "rating", m_fRating);
100   XMLUtils::SetFloat(movie, "epbookmark", m_fEpBookmark);
101   XMLUtils::SetInt(movie, "year", m_iYear);
102   XMLUtils::SetInt(movie, "top250", m_iTop250);
103   if (tag == "episodedetails" || tag == "tvshow")
104   {
105     XMLUtils::SetInt(movie, "season", m_iSeason);
106     XMLUtils::SetInt(movie, "episode", m_iEpisode);
107     XMLUtils::SetInt(movie, "displayseason",m_iSpecialSortSeason);
108     XMLUtils::SetInt(movie, "displayepisode",m_iSpecialSortEpisode);
109   }
110   if (tag == "musicvideo")
111   {
112     XMLUtils::SetInt(movie, "track", m_iTrack);
113     XMLUtils::SetString(movie, "album", m_strAlbum);
114   }
115   XMLUtils::SetString(movie, "votes", m_strVotes);
116   XMLUtils::SetString(movie, "outline", m_strPlotOutline);
117   XMLUtils::SetString(movie, "plot", m_strPlot);
118   XMLUtils::SetString(movie, "tagline", m_strTagLine);
119   XMLUtils::SetString(movie, "runtime", m_strRuntime);
120   if (!m_strPictureURL.m_xml.empty())
121   {
122     TiXmlDocument doc;
123     doc.Parse(m_strPictureURL.m_xml);
124     const TiXmlNode* thumb = doc.FirstChild("thumb");
125     while (thumb)
126     {
127       movie->InsertEndChild(*thumb);
128       thumb = thumb->NextSibling("thumb");
129     }
130   }
131   if (m_fanart.m_xml.size())
132   {
133     TiXmlDocument doc;
134     doc.Parse(m_fanart.m_xml);
135     movie->InsertEndChild(*doc.RootElement());
136   }
137   XMLUtils::SetString(movie, "mpaa", m_strMPAARating);
138   XMLUtils::SetInt(movie, "playcount", m_playCount);
139   XMLUtils::SetString(movie, "lastplayed", m_lastPlayed);
140   if (savePathInfo)
141   {
142     XMLUtils::SetString(movie, "file", m_strFile);
143     XMLUtils::SetString(movie, "path", m_strPath);
144     XMLUtils::SetString(movie, "filenameandpath", m_strFileNameAndPath);
145     XMLUtils::SetString(movie, "basepath", m_basePath);
146   }
147   if (!m_strEpisodeGuide.IsEmpty())
148   {
149     TiXmlDocument doc;
150     doc.Parse(m_strEpisodeGuide);
151     if (doc.RootElement())
152       movie->InsertEndChild(*doc.RootElement());
153     else
154       XMLUtils::SetString(movie, "episodeguide", m_strEpisodeGuide);
155   }
156
157   XMLUtils::SetString(movie, "id", m_strIMDBNumber);
158   XMLUtils::SetAdditiveString(movie, "genre",
159                           g_advancedSettings.m_videoItemSeparator, m_strGenre);
160   XMLUtils::SetAdditiveString(movie, "country",
161                           g_advancedSettings.m_videoItemSeparator, m_strCountry);
162   XMLUtils::SetAdditiveString(movie, "set",
163                           g_advancedSettings.m_videoItemSeparator, m_strSet);
164   XMLUtils::SetAdditiveString(movie, "credits",
165                           g_advancedSettings.m_videoItemSeparator, m_strWritingCredits);
166   XMLUtils::SetAdditiveString(movie, "director",
167                           g_advancedSettings.m_videoItemSeparator, m_strDirector);
168   XMLUtils::SetString(movie, "premiered", m_strPremiered);
169   XMLUtils::SetString(movie, "status", m_strStatus);
170   XMLUtils::SetString(movie, "code", m_strProductionCode);
171   XMLUtils::SetString(movie, "aired", m_strFirstAired);
172   XMLUtils::SetAdditiveString(movie, "studio",
173                           g_advancedSettings.m_videoItemSeparator, m_strStudio);
174   XMLUtils::SetString(movie, "trailer", m_strTrailer);
175
176   if (m_streamDetails.HasItems())
177   {
178     // it goes fileinfo/streamdetails/[video|audio|subtitle]
179     TiXmlElement fileinfo("fileinfo");
180     TiXmlElement streamdetails("streamdetails");
181     for (int iStream=1; iStream<=m_streamDetails.GetVideoStreamCount(); iStream++)
182     {
183       TiXmlElement stream("video");
184       XMLUtils::SetString(&stream, "codec", m_streamDetails.GetVideoCodec(iStream));
185       XMLUtils::SetFloat(&stream, "aspect", m_streamDetails.GetVideoAspect(iStream));
186       XMLUtils::SetInt(&stream, "width", m_streamDetails.GetVideoWidth(iStream));
187       XMLUtils::SetInt(&stream, "height", m_streamDetails.GetVideoHeight(iStream));
188       XMLUtils::SetInt(&stream, "durationinseconds", m_streamDetails.GetVideoDuration(iStream));
189       streamdetails.InsertEndChild(stream);
190     }
191     for (int iStream=1; iStream<=m_streamDetails.GetAudioStreamCount(); iStream++)
192     {
193       TiXmlElement stream("audio");
194       XMLUtils::SetString(&stream, "codec", m_streamDetails.GetAudioCodec(iStream));
195       XMLUtils::SetString(&stream, "language", m_streamDetails.GetAudioLanguage(iStream));
196       XMLUtils::SetInt(&stream, "channels", m_streamDetails.GetAudioChannels(iStream));
197       streamdetails.InsertEndChild(stream);
198     }
199     for (int iStream=1; iStream<=m_streamDetails.GetSubtitleStreamCount(); iStream++)
200     {
201       TiXmlElement stream("subtitle");
202       XMLUtils::SetString(&stream, "language", m_streamDetails.GetSubtitleLanguage(iStream));
203       streamdetails.InsertEndChild(stream);
204     }
205     fileinfo.InsertEndChild(streamdetails);
206     movie->InsertEndChild(fileinfo);
207   }  /* if has stream details */
208
209   // cast
210   for (iCast it = m_cast.begin(); it != m_cast.end(); ++it)
211   {
212     // add a <actor> tag
213     TiXmlElement cast("actor");
214     TiXmlNode *node = movie->InsertEndChild(cast);
215     TiXmlElement actor("name");
216     TiXmlNode *actorNode = node->InsertEndChild(actor);
217     TiXmlText name(it->strName);
218     actorNode->InsertEndChild(name);
219     TiXmlElement role("role");
220     TiXmlNode *roleNode = node->InsertEndChild(role);
221     TiXmlText character(it->strRole);
222     roleNode->InsertEndChild(character);
223     TiXmlElement thumb("thumb");
224     TiXmlNode *thumbNode = node->InsertEndChild(thumb);
225     TiXmlText th(it->thumbUrl.GetFirstThumb().m_url);
226     thumbNode->InsertEndChild(th);
227   }
228   XMLUtils::SetAdditiveString(movie, "artist",
229                          g_advancedSettings.m_videoItemSeparator, m_strArtist);
230   XMLUtils::SetAdditiveString(movie, "showlink",
231                          g_advancedSettings.m_videoItemSeparator, m_strShowLink);
232
233   return true;
234 }
235
236 bool CVideoInfoTag::Load(const TiXmlElement *movie, bool chained /* = false */)
237 {
238   if (!movie) return false;
239
240   // reset our details if we aren't chained.
241   if (!chained) Reset();
242
243   ParseNative(movie);
244
245   return true;
246 }
247
248 void CVideoInfoTag::Archive(CArchive& ar)
249 {
250   if (ar.IsStoring())
251   {
252     ar << m_strDirector;
253     ar << m_strWritingCredits;
254     ar << m_strGenre;
255     ar << m_strCountry;
256     ar << m_strTagLine;
257     ar << m_strPlotOutline;
258     ar << m_strPlot;
259     ar << m_strPictureURL.m_spoof;
260     ar << m_strPictureURL.m_xml;
261     ar << m_fanart.m_xml;
262     ar << m_strTitle;
263     ar << m_strSortTitle;
264     ar << m_strVotes;
265     ar << m_strStudio;
266     ar << m_strTrailer;
267     ar << (int)m_cast.size();
268     for (unsigned int i=0;i<m_cast.size();++i)
269     {
270       ar << m_cast[i].strName;
271       ar << m_cast[i].strRole;
272       ar << m_cast[i].thumbUrl.m_xml;
273     }
274
275     ar << m_strSet;
276     ar << m_strRuntime;
277     ar << m_strFile;
278     ar << m_strPath;
279     ar << m_strIMDBNumber;
280     ar << m_strMPAARating;
281     ar << m_strFileNameAndPath;
282     ar << m_strOriginalTitle;
283     ar << m_strEpisodeGuide;
284     ar << m_strPremiered;
285     ar << m_strStatus;
286     ar << m_strProductionCode;
287     ar << m_strFirstAired;
288     ar << m_strShowTitle;
289     ar << m_strAlbum;
290     ar << m_strArtist;
291     ar << m_playCount;
292     ar << m_lastPlayed;
293     ar << m_iTop250;
294     ar << m_iYear;
295     ar << m_iSeason;
296     ar << m_iEpisode;
297     ar << m_fRating;
298     ar << m_iDbId;
299     ar << m_iFileId;
300     ar << m_iSpecialSortSeason;
301     ar << m_iSpecialSortEpisode;
302     ar << m_iBookmarkId;
303     ar << m_iTrack;
304     ar << dynamic_cast<IArchivable&>(m_streamDetails);
305     ar << m_strShowLink;
306     ar << m_fEpBookmark;
307     ar << m_basePath;
308   }
309   else
310   {
311     ar >> m_strDirector;
312     ar >> m_strWritingCredits;
313     ar >> m_strGenre;
314     ar >> m_strCountry;
315     ar >> m_strTagLine;
316     ar >> m_strPlotOutline;
317     ar >> m_strPlot;
318     ar >> m_strPictureURL.m_spoof;
319     ar >> m_strPictureURL.m_xml;
320     m_strPictureURL.Parse();
321     ar >> m_fanart.m_xml;
322     m_fanart.Unpack();
323     ar >> m_strTitle;
324     ar >> m_strSortTitle;
325     ar >> m_strVotes;
326     ar >> m_strStudio;
327     ar >> m_strTrailer;
328     int iCastSize;
329     ar >> iCastSize;
330     for (int i=0;i<iCastSize;++i)
331     {
332       SActorInfo info;
333       ar >> info.strName;
334       ar >> info.strRole;
335       CStdString strXml;
336       ar >> strXml;
337       info.thumbUrl.ParseString(strXml);
338       m_cast.push_back(info);
339     }
340
341     ar >> m_strSet;
342     ar >> m_strRuntime;
343     ar >> m_strFile;
344     ar >> m_strPath;
345     ar >> m_strIMDBNumber;
346     ar >> m_strMPAARating;
347     ar >> m_strFileNameAndPath;
348     ar >> m_strOriginalTitle;
349     ar >> m_strEpisodeGuide;
350     ar >> m_strPremiered;
351     ar >> m_strStatus;
352     ar >> m_strProductionCode;
353     ar >> m_strFirstAired;
354     ar >> m_strShowTitle;
355     ar >> m_strAlbum;
356     ar >> m_strArtist;
357     ar >> m_playCount;
358     ar >> m_lastPlayed;
359     ar >> m_iTop250;
360     ar >> m_iYear;
361     ar >> m_iSeason;
362     ar >> m_iEpisode;
363     ar >> m_fRating;
364     ar >> m_iDbId;
365     ar >> m_iFileId;
366     ar >> m_iSpecialSortSeason;
367     ar >> m_iSpecialSortEpisode;
368     ar >> m_iBookmarkId;
369     ar >> m_iTrack;
370     ar >> dynamic_cast<IArchivable&>(m_streamDetails);
371     ar >> m_strShowLink;
372     ar >> m_fEpBookmark;
373     ar >> m_basePath;
374   }
375 }
376
377 void CVideoInfoTag::Serialize(CVariant& value)
378 {
379   value["director"] = m_strDirector;
380   value["writingcredits"] = m_strWritingCredits;
381   value["genre"] = m_strGenre;
382   value["country"] = m_strCountry;
383   value["tagline"] = m_strTagLine;
384   value["plotoutline"] = m_strPlotOutline;
385   value["plot"] = m_strPlot;
386   value["title"] = m_strTitle;
387   value["votes"] = m_strVotes;
388   value["studio"] = m_strStudio;
389   value["trailer"] = m_strTrailer;
390   value["cast"] = CVariant(CVariant::VariantTypeArray);
391   for (unsigned int i = 0; i < m_cast.size(); ++i)
392   {
393     CVariant actor;
394     actor["name"] = m_cast[i].strName;
395     actor["role"] = m_cast[i].strRole;
396     value["cast"].push_back(actor);
397   }
398   value["set"] = m_strSet;
399   value["runtime"] = m_strRuntime;
400   value["file"] = m_strFile;
401   value["path"] = m_strPath;
402   value["imdbnumber"] = m_strIMDBNumber;
403   value["mpaa"] = m_strMPAARating;
404   value["filenameandpath"] = m_strFileNameAndPath;
405   value["originaltitle"] = m_strOriginalTitle;
406   value["sorttitle"] = m_strSortTitle;
407   value["episodeguide"] = m_strEpisodeGuide;
408   value["premiered"] = m_strPremiered;
409   value["status"] = m_strStatus;
410   value["productioncode"] = m_strProductionCode;
411   value["firstaired"] = m_strFirstAired;
412   value["showtitle"] = m_strShowTitle;
413   value["album"] = m_strAlbum;
414   value["artist"] = m_strArtist;
415   value["playcount"] = m_playCount;
416   value["lastPlayed"] = m_lastPlayed;
417   value["top250"] = m_iTop250;
418   value["year"] = m_iYear;
419   value["season"] = m_iSeason;
420   value["episode"] = m_iEpisode;
421   value["rating"] = m_fRating;
422   value["dbid"] = m_iDbId;
423   value["fileid"] = m_iFileId;
424   value["track"] = m_iTrack;
425   value["showlink"] = m_strShowLink;
426   m_streamDetails.Serialize(value["streamDetails"]);
427 }
428
429 const CStdString CVideoInfoTag::GetCast(bool bIncludeRole /*= false*/) const
430 {
431   CStdString strLabel;
432   for (iCast it = m_cast.begin(); it != m_cast.end(); ++it)
433   {
434     CStdString character;
435     if (it->strRole.IsEmpty() || !bIncludeRole)
436       character.Format("%s\n", it->strName.c_str());
437     else
438       character.Format("%s %s %s\n", it->strName.c_str(), g_localizeStrings.Get(20347).c_str(), it->strRole.c_str());
439     strLabel += character;
440   }
441   return strLabel.TrimRight("\n");
442 }
443
444 void CVideoInfoTag::ParseNative(const TiXmlElement* movie)
445 {
446   XMLUtils::GetString(movie, "title", m_strTitle);
447   XMLUtils::GetString(movie, "originaltitle", m_strOriginalTitle);
448   XMLUtils::GetString(movie, "sorttitle", m_strSortTitle);
449   XMLUtils::GetFloat(movie, "rating", m_fRating);
450   XMLUtils::GetFloat(movie, "epbookmark", m_fEpBookmark);
451   int max_value = 10;
452   const TiXmlElement* rElement = movie->FirstChildElement("rating");
453   if (rElement && (rElement->QueryIntAttribute("max", &max_value) == TIXML_SUCCESS) && max_value>=1)
454   {
455     m_fRating = m_fRating / max_value * 10; // Normalise the Movie Rating to between 1 and 10
456   }
457   XMLUtils::GetInt(movie, "year", m_iYear);
458   XMLUtils::GetInt(movie, "top250", m_iTop250);
459   XMLUtils::GetInt(movie, "season", m_iSeason);
460   XMLUtils::GetInt(movie, "episode", m_iEpisode);
461   XMLUtils::GetInt(movie, "track", m_iTrack);
462   XMLUtils::GetInt(movie, "displayseason", m_iSpecialSortSeason);
463   XMLUtils::GetInt(movie, "displayepisode", m_iSpecialSortEpisode);
464   int after=0;
465   XMLUtils::GetInt(movie, "displayafterseason",after);
466   if (after > 0)
467   {
468     m_iSpecialSortSeason = after;
469     m_iSpecialSortEpisode = 0x1000; // should be more than any realistic episode number
470   }
471   XMLUtils::GetString(movie, "votes", m_strVotes);
472   XMLUtils::GetString(movie, "outline", m_strPlotOutline);
473   XMLUtils::GetString(movie, "plot", m_strPlot);
474   XMLUtils::GetString(movie, "tagline", m_strTagLine);
475   XMLUtils::GetString(movie, "runtime", m_strRuntime);
476   XMLUtils::GetString(movie, "mpaa", m_strMPAARating);
477   XMLUtils::GetInt(movie, "playcount", m_playCount);
478   XMLUtils::GetString(movie, "lastplayed", m_lastPlayed);
479   XMLUtils::GetString(movie, "file", m_strFile);
480   XMLUtils::GetString(movie, "path", m_strPath);
481   XMLUtils::GetString(movie, "id", m_strIMDBNumber);
482   XMLUtils::GetString(movie, "filenameandpath", m_strFileNameAndPath);
483   XMLUtils::GetString(movie, "premiered", m_strPremiered);
484   XMLUtils::GetString(movie, "status", m_strStatus);
485   XMLUtils::GetString(movie, "code", m_strProductionCode);
486   XMLUtils::GetString(movie, "aired", m_strFirstAired);
487   XMLUtils::GetString(movie, "album", m_strAlbum);
488   XMLUtils::GetString(movie, "trailer", m_strTrailer);
489   XMLUtils::GetString(movie, "basepath", m_basePath);
490
491   const TiXmlElement* thumb = movie->FirstChildElement("thumb");
492   while (thumb)
493   {
494     m_strPictureURL.ParseElement(thumb);
495     thumb = thumb->NextSiblingElement("thumb");
496   }
497
498   XMLUtils::GetAdditiveString(movie,"genre",g_advancedSettings.m_videoItemSeparator,m_strGenre);
499   XMLUtils::GetAdditiveString(movie,"country",g_advancedSettings.m_videoItemSeparator,m_strCountry);
500   XMLUtils::GetAdditiveString(movie,"credits",g_advancedSettings.m_videoItemSeparator,m_strWritingCredits);
501   XMLUtils::GetAdditiveString(movie,"director",g_advancedSettings.m_videoItemSeparator,m_strDirector);
502   XMLUtils::GetAdditiveString(movie,"showlink",g_advancedSettings.m_videoItemSeparator,m_strShowLink);
503
504   // cast
505   const TiXmlElement* node = movie->FirstChildElement("actor");
506   while (node)
507   {
508     const TiXmlNode *actor = node->FirstChild("name");
509     if (actor && actor->FirstChild())
510     {
511       SActorInfo info;
512       info.strName = actor->FirstChild()->Value();
513       const TiXmlNode *roleNode = node->FirstChild("role");
514       if (roleNode && roleNode->FirstChild())
515         info.strRole = roleNode->FirstChild()->Value();
516       const TiXmlElement* thumb = node->FirstChildElement("thumb");
517       while (thumb)
518       {
519         info.thumbUrl.ParseElement(thumb);
520         thumb = thumb->NextSiblingElement("thumb");
521       }
522       const char* clear=node->Attribute("clear");
523       if (clear && stricmp(clear,"true"))
524         m_cast.clear();
525       m_cast.push_back(info);
526     }
527     node = node->NextSiblingElement("actor");
528   }
529   XMLUtils::GetAdditiveString(movie,"set",g_advancedSettings.m_videoItemSeparator,m_strSet);
530   XMLUtils::GetAdditiveString(movie,"studio",g_advancedSettings.m_videoItemSeparator,m_strStudio);
531   // artists
532   node = movie->FirstChildElement("artist");
533   while (node)
534   {
535     const TiXmlNode* pNode = node->FirstChild("name");
536     const char* pValue=NULL;
537     if (pNode && pNode->FirstChild())
538       pValue = pNode->FirstChild()->Value();
539     else if (node->FirstChild())
540       pValue = node->FirstChild()->Value();
541     if (pValue)
542     {
543       const char* clear=node->Attribute("clear");
544       if (m_strArtist.IsEmpty() || (clear && stricmp(clear,"true")==0))
545         m_strArtist += pValue;
546       else
547         m_strArtist += g_advancedSettings.m_videoItemSeparator + pValue;
548     }
549     node = node->NextSiblingElement("artist");
550   }
551
552   m_streamDetails.Reset();
553   node = movie->FirstChildElement("fileinfo");
554   if (node)
555   {
556     // Try to pull from fileinfo/streamdetails/[video|audio|subtitle]
557     const TiXmlNode *nodeStreamDetails = node->FirstChild("streamdetails");
558     if (nodeStreamDetails)
559     {
560       const TiXmlNode *nodeDetail = NULL;
561       while ((nodeDetail = nodeStreamDetails->IterateChildren("audio", nodeDetail)))
562       {
563         CStreamDetailAudio *p = new CStreamDetailAudio();
564         XMLUtils::GetString(nodeDetail, "codec", p->m_strCodec);
565         XMLUtils::GetString(nodeDetail, "language", p->m_strLanguage);
566         XMLUtils::GetInt(nodeDetail, "channels", p->m_iChannels);
567         p->m_strCodec.MakeLower();
568         p->m_strLanguage.MakeLower();
569         m_streamDetails.AddStream(p);
570       }
571       nodeDetail = NULL;
572       while ((nodeDetail = nodeStreamDetails->IterateChildren("video", nodeDetail)))
573       {
574         CStreamDetailVideo *p = new CStreamDetailVideo();
575         XMLUtils::GetString(nodeDetail, "codec", p->m_strCodec);
576         XMLUtils::GetFloat(nodeDetail, "aspect", p->m_fAspect);
577         XMLUtils::GetInt(nodeDetail, "width", p->m_iWidth);
578         XMLUtils::GetInt(nodeDetail, "height", p->m_iHeight);
579         XMLUtils::GetInt(nodeDetail, "durationinseconds", p->m_iDuration);
580         p->m_strCodec.MakeLower();
581         m_streamDetails.AddStream(p);
582       }
583       nodeDetail = NULL;
584       while ((nodeDetail = nodeStreamDetails->IterateChildren("subtitle", nodeDetail)))
585       {
586         CStreamDetailSubtitle *p = new CStreamDetailSubtitle();
587         XMLUtils::GetString(nodeDetail, "language", p->m_strLanguage);
588         p->m_strLanguage.MakeLower();
589         m_streamDetails.AddStream(p);
590       }
591     }
592     m_streamDetails.DetermineBestStreams();
593   }  /* if fileinfo */
594
595   const TiXmlElement *epguide = movie->FirstChildElement("episodeguide");
596   if (epguide)
597   {
598     // DEPRECIATE ME - support for old XML-encoded <episodeguide> blocks.
599     if (epguide->FirstChild() && strnicmp("<episodeguide", epguide->FirstChild()->Value(), 13) == 0)
600       m_strEpisodeGuide = epguide->FirstChild()->Value();
601     else
602     {
603       stringstream stream;
604       stream << *epguide;
605       m_strEpisodeGuide = stream.str();
606     }
607   }
608
609   // fanart
610   const TiXmlElement *fanart = movie->FirstChildElement("fanart");
611   if (fanart)
612   {
613     m_fanart.m_xml << *fanart;
614     m_fanart.Unpack();
615   }
616 }
617
618 bool CVideoInfoTag::HasStreamDetails() const
619 {
620   return m_streamDetails.HasItems();
621 }
622
623 bool CVideoInfoTag::IsEmpty() const
624 {
625   return (m_strTitle.IsEmpty() &&
626           m_strFile.IsEmpty() &&
627           m_strPath.IsEmpty());
628 }