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