upnp: smartplaylists had incorrect upnp:class
[vuplus_xbmc] / xbmc / network / upnp / UPnPInternal.cpp
1 /*
2  *      Copyright (C) 2012-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 #include "UPnPInternal.h"
21 #include "UPnP.h"
22 #include "UPnPServer.h"
23 #include "Platinum.h"
24 #include "URL.h"
25 #include "Util.h"
26 #include "settings/AdvancedSettings.h"
27 #include "utils/log.h"
28 #include "utils/StringUtils.h"
29 #include "utils/URIUtils.h"
30 #include "FileItem.h"
31 #include "filesystem/File.h"
32 #include "filesystem/StackDirectory.h"
33 #include "filesystem/MusicDatabaseDirectory.h"
34 #include "filesystem/VideoDatabaseDirectory.h"
35 #include "video/VideoDatabase.h"
36 #include "video/VideoInfoTag.h"
37 #include "music/MusicDatabase.h"
38 #include "music/tags/MusicInfoTag.h"
39 #include "TextureCache.h"
40 #include "ThumbLoader.h"
41
42 using namespace MUSIC_INFO;
43 using namespace XFILE;
44
45 namespace UPNP
46 {
47
48 /*----------------------------------------------------------------------
49 |  GetClientQuirks
50 +---------------------------------------------------------------------*/
51 EClientQuirks GetClientQuirks(const PLT_HttpRequestContext* context)
52 {
53   if(context == NULL)
54       return ECLIENTQUIRKS_NONE;
55
56   unsigned int quirks = 0;
57   const NPT_String* user_agent = context->GetRequest().GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_USER_AGENT);
58   const NPT_String* server     = context->GetRequest().GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_SERVER);
59
60   if (user_agent) {
61       if (user_agent->Find("XBox", 0, true) >= 0 ||
62           user_agent->Find("Xenon", 0, true) >= 0)
63           quirks |= ECLIENTQUIRKS_ONLYSTORAGEFOLDER | ECLIENTQUIRKS_BASICVIDEOCLASS;
64
65       if (user_agent->Find("Windows-Media-Player", 0, true) >= 0)
66           quirks |= ECLIENTQUIRKS_UNKNOWNSERIES;
67
68   }
69   if (server) {
70       if (server->Find("Xbox", 0, true) >= 0)
71           quirks |= ECLIENTQUIRKS_ONLYSTORAGEFOLDER | ECLIENTQUIRKS_BASICVIDEOCLASS;
72   }
73
74   return (EClientQuirks)quirks;
75 }
76
77 /*----------------------------------------------------------------------
78 |   GetMimeType
79 +---------------------------------------------------------------------*/
80 NPT_String
81 GetMimeType(const char* filename,
82             const PLT_HttpRequestContext* context /* = NULL */)
83 {
84     NPT_String ext = URIUtils::GetExtension(filename).c_str();
85     ext.TrimLeft('.');
86     ext = ext.ToLowercase();
87
88     return PLT_MimeType::GetMimeTypeFromExtension(ext, context);
89 }
90
91 /*----------------------------------------------------------------------
92 |   GetMimeType
93 +---------------------------------------------------------------------*/
94 NPT_String
95 GetMimeType(const CFileItem& item,
96             const PLT_HttpRequestContext* context /* = NULL */)
97 {
98     CStdString path = item.GetPath();
99     if (item.HasVideoInfoTag() && !item.GetVideoInfoTag()->GetPath().IsEmpty()) {
100         path = item.GetVideoInfoTag()->GetPath();
101     } else if (item.HasMusicInfoTag() && !item.GetMusicInfoTag()->GetURL().IsEmpty()) {
102         path = item.GetMusicInfoTag()->GetURL();
103     }
104
105     if (URIUtils::IsStack(path))
106         path = XFILE::CStackDirectory::GetFirstStackedFile(path);
107
108     NPT_String ext = URIUtils::GetExtension(path).c_str();
109     ext.TrimLeft('.');
110     ext = ext.ToLowercase();
111
112     NPT_String mime;
113
114     /* We always use Platinum mime type first
115        as it is defined to map extension to DLNA compliant mime type
116        or custom according to context (who asked for it) */
117     if (!ext.IsEmpty()) {
118         mime = PLT_MimeType::GetMimeTypeFromExtension(ext, context);
119         if (mime == "application/octet-stream") mime = "";
120     }
121
122     /* if Platinum couldn't map it, default to XBMC mapping */
123     if (mime.IsEmpty()) {
124         NPT_String mime = item.GetMimeType().c_str();
125         if (mime == "application/octet-stream") mime = "";
126     }
127
128     /* fallback to generic mime type if not found */
129     if (mime.IsEmpty()) {
130         if (item.IsVideo() || item.IsVideoDb() )
131             mime = "video/" + ext;
132         else if (item.IsAudio() || item.IsMusicDb() )
133             mime = "audio/" + ext;
134         else if (item.IsPicture() )
135             mime = "image/" + ext;
136     }
137
138     /* nothing we can figure out */
139     if (mime.IsEmpty()) {
140         mime = "application/octet-stream";
141     }
142
143     return mime;
144 }
145
146 /*----------------------------------------------------------------------
147 |   GetProtocolInfo
148 +---------------------------------------------------------------------*/
149 const NPT_String
150 GetProtocolInfo(const CFileItem&              item,
151                 const char*                   protocol,
152                 const PLT_HttpRequestContext* context /* = NULL */)
153 {
154     NPT_String proto = protocol;
155
156     /* fixup the protocol just in case nothing was passed */
157     if (proto.IsEmpty()) {
158         proto = item.GetAsUrl().GetProtocol();
159     }
160
161     /*
162        map protocol to right prefix and use xbmc-get for
163        unsupported UPnP protocols for other xbmc clients
164        TODO: add rtsp ?
165     */
166     if (proto == "http") {
167         proto = "http-get";
168     } else {
169         proto = "xbmc-get";
170     }
171
172     /* we need a valid extension to retrieve the mimetype for the protocol info */
173     NPT_String mime = GetMimeType(item, context);
174     proto += ":*:" + mime + ":" + PLT_ProtocolInfo::GetDlnaExtension(mime, context);
175     return proto;
176 }
177
178   /*----------------------------------------------------------------------
179    |   CResourceFinder
180    +---------------------------------------------------------------------*/
181 CResourceFinder::CResourceFinder(const char* protocol, const char* content)
182   : m_Protocol(protocol)
183   , m_Content(content)
184 {
185 }
186
187 bool CResourceFinder::operator()(const PLT_MediaItemResource& resource) const {
188     if (m_Content.IsEmpty())
189         return (resource.m_ProtocolInfo.GetProtocol().Compare(m_Protocol, true) == 0);
190     else
191         return ((resource.m_ProtocolInfo.GetProtocol().Compare(m_Protocol, true) == 0)
192               && resource.m_ProtocolInfo.GetContentType().StartsWith(m_Content, true));
193 }
194
195 /*----------------------------------------------------------------------
196 |   PopulateObjectFromTag
197 +---------------------------------------------------------------------*/
198 NPT_Result
199 PopulateObjectFromTag(CMusicInfoTag&         tag,
200                       PLT_MediaObject&       object,
201                       NPT_String*            file_path, /* = NULL */
202                       PLT_MediaItemResource* resource,  /* = NULL */
203                       EClientQuirks          quirks)
204 {
205     if (!tag.GetURL().IsEmpty() && file_path)
206       *file_path = tag.GetURL();
207
208     std::vector<std::string> genres = tag.GetGenre();
209     for (unsigned int index = 0; index < genres.size(); index++)
210       object.m_Affiliation.genres.Add(genres.at(index).c_str());
211     object.m_Title = tag.GetTitle();
212     object.m_Affiliation.album = tag.GetAlbum();
213     for (unsigned int index = 0; index < tag.GetArtist().size(); index++)
214     {
215       object.m_People.artists.Add(tag.GetArtist().at(index).c_str());
216       object.m_People.artists.Add(tag.GetArtist().at(index).c_str(), "Performer");
217     }
218     object.m_People.artists.Add(StringUtils::Join(!tag.GetAlbumArtist().empty() ? tag.GetAlbumArtist() : tag.GetArtist(), g_advancedSettings.m_musicItemSeparator).c_str(), "AlbumArtist");
219     if(tag.GetAlbumArtist().empty())
220         object.m_Creator = StringUtils::Join(tag.GetArtist(), g_advancedSettings.m_musicItemSeparator);
221     else
222         object.m_Creator = StringUtils::Join(tag.GetAlbumArtist(), g_advancedSettings.m_musicItemSeparator);
223     object.m_MiscInfo.original_track_number = tag.GetTrackNumber();
224     if(tag.GetDatabaseId() >= 0) {
225       object.m_ReferenceID = NPT_String::Format("musicdb://4/%i%s", tag.GetDatabaseId(), URIUtils::GetExtension(tag.GetURL()).c_str());
226     }
227     if (object.m_ReferenceID == object.m_ObjectID)
228         object.m_ReferenceID = "";
229
230     object.m_MiscInfo.last_time = tag.GetLastPlayed().GetAsDBDate();
231     object.m_MiscInfo.play_count = tag.GetPlayCount();
232
233     if (resource) resource->m_Duration = tag.GetDuration();
234
235     return NPT_SUCCESS;
236 }
237
238 /*----------------------------------------------------------------------
239 |   PopulateObjectFromTag
240 +---------------------------------------------------------------------*/
241 NPT_Result
242 PopulateObjectFromTag(CVideoInfoTag&         tag,
243                       PLT_MediaObject&       object,
244                       NPT_String*            file_path, /* = NULL */
245                       PLT_MediaItemResource* resource,  /* = NULL */
246                       EClientQuirks          quirks)
247 {
248     // some usefull buffers
249     CStdStringArray strings;
250
251     if (!tag.m_strFileNameAndPath.IsEmpty() && file_path)
252       *file_path = tag.m_strFileNameAndPath;
253
254     if (tag.m_iDbId != -1 ) {
255         if (tag.m_type == "musicvideo") {
256           object.m_ObjectClass.type = "object.item.videoItem.musicVideoClip";
257           object.m_Creator = StringUtils::Join(tag.m_artist, g_advancedSettings.m_videoItemSeparator);
258           object.m_Title = tag.m_strTitle;
259           object.m_ReferenceID = NPT_String::Format("videodb://3/2/%i", tag.m_iDbId);
260         } else if (tag.m_type == "movie") {
261           object.m_ObjectClass.type = "object.item.videoItem.movie";
262           object.m_Title = tag.m_strTitle;
263           object.m_Date = NPT_String::FromInteger(tag.m_iYear) + "-01-01";
264           object.m_ReferenceID = NPT_String::Format("videodb://1/2/%i", tag.m_iDbId);
265         } else {
266           object.m_ObjectClass.type = "object.item.videoItem.videoBroadcast";
267           object.m_Recorded.program_title  = "S" + ("0" + NPT_String::FromInteger(tag.m_iSeason)).Right(2);
268           object.m_Recorded.program_title += "E" + ("0" + NPT_String::FromInteger(tag.m_iEpisode)).Right(2);
269           object.m_Recorded.program_title += " : " + tag.m_strTitle;
270           object.m_Recorded.series_title = tag.m_strShowTitle;
271           int season = tag.m_iSeason > 1 ? tag.m_iSeason : 1;
272           object.m_Recorded.episode_number = season * 100 + tag.m_iEpisode;
273           object.m_Title = object.m_Recorded.series_title + " - " + object.m_Recorded.program_title;
274           object.m_Date = tag.m_firstAired.GetAsDBDate();
275           if(tag.m_iSeason != -1)
276               object.m_ReferenceID = NPT_String::Format("videodb://2/0/%i", tag.m_iDbId);
277         }
278     }
279
280     if(quirks & ECLIENTQUIRKS_BASICVIDEOCLASS)
281         object.m_ObjectClass.type = "object.item.videoItem";
282
283     if(object.m_ReferenceID == object.m_ObjectID)
284         object.m_ReferenceID = "";
285
286     for (unsigned int index = 0; index < tag.m_genre.size(); index++)
287       object.m_Affiliation.genres.Add(tag.m_genre.at(index).c_str());
288
289     for(CVideoInfoTag::iCast it = tag.m_cast.begin();it != tag.m_cast.end();it++) {
290         object.m_People.actors.Add(it->strName.c_str(), it->strRole.c_str());
291     }
292
293     for (unsigned int index = 0; index < tag.m_director.size(); index++)
294       object.m_People.directors.Add(tag.m_director[index].c_str());
295
296     for (unsigned int index = 0; index < tag.m_writingCredits.size(); index++)
297       object.m_People.authors.Add(tag.m_writingCredits[index].c_str());
298
299     object.m_Description.description = tag.m_strTagLine;
300     object.m_Description.long_description = tag.m_strPlot;
301     object.m_Description.rating = tag.m_strMPAARating;
302     object.m_MiscInfo.last_position = (NPT_UInt32)tag.m_resumePoint.timeInSeconds;
303     object.m_MiscInfo.last_time = tag.m_lastPlayed.GetAsDBDate();
304     object.m_MiscInfo.play_count = tag.m_playCount;
305     if (resource) {
306         resource->m_Duration = tag.GetDuration();
307         if (tag.HasStreamDetails()) {
308             const CStreamDetails &details = tag.m_streamDetails;
309             resource->m_Resolution = NPT_String::FromInteger(details.GetVideoWidth()) + "x" + NPT_String::FromInteger(details.GetVideoHeight());
310         }
311     }
312
313     return NPT_SUCCESS;
314 }
315
316 /*----------------------------------------------------------------------
317 |   BuildObject
318 +---------------------------------------------------------------------*/
319 PLT_MediaObject*
320 BuildObject(CFileItem&                    item,
321             NPT_String&                   file_path,
322             bool                          with_count,
323             NPT_Reference<CThumbLoader>&  thumb_loader,
324             const PLT_HttpRequestContext* context /* = NULL */,
325             CUPnPServer*                  upnp_server /* = NULL */)
326 {
327     PLT_MediaItemResource resource;
328     PLT_MediaObject*      object = NULL;
329     std::string thumb, fanart;
330
331     CLog::Log(LOGDEBUG, "UPnP: Building didl for object '%s'", (const char*)item.GetPath());
332
333     EClientQuirks quirks = GetClientQuirks(context);
334
335     // get list of ip addresses
336     NPT_List<NPT_IpAddress> ips;
337     NPT_HttpUrl rooturi;
338     NPT_CHECK_LABEL(PLT_UPnPMessageHelper::GetIPAddresses(ips), failure);
339
340     // if we're passed an interface where we received the request from
341     // move the ip to the top
342     if (context && context->GetLocalAddress().GetIpAddress().ToString() != "0.0.0.0") {
343         rooturi = NPT_HttpUrl(context->GetLocalAddress().GetIpAddress().ToString(), context->GetLocalAddress().GetPort(), "/");
344         ips.Remove(context->GetLocalAddress().GetIpAddress());
345         ips.Insert(ips.GetFirstItem(), context->GetLocalAddress().GetIpAddress());
346     } else if(upnp_server) {
347         rooturi = NPT_HttpUrl("localhost", upnp_server->GetPort(), "/");
348     }
349
350     if (!item.m_bIsFolder) {
351         object = new PLT_MediaItem();
352         object->m_ObjectID = item.GetPath();
353
354         /* Setup object type */
355         if (item.IsMusicDb() || item.IsAudio()) {
356             object->m_ObjectClass.type = "object.item.audioItem.musicTrack";
357
358             if (item.HasMusicInfoTag()) {
359                 CMusicInfoTag *tag = (CMusicInfoTag*)item.GetMusicInfoTag();
360                 PopulateObjectFromTag(*tag, *object, &file_path, &resource, quirks);
361             }
362         } else if (item.IsVideoDb() || item.IsVideo()) {
363             object->m_ObjectClass.type = "object.item.videoItem";
364
365             if(quirks & ECLIENTQUIRKS_UNKNOWNSERIES)
366                 object->m_Affiliation.album = "[Unknown Series]";
367
368             if (item.HasVideoInfoTag()) {
369                 CVideoInfoTag *tag = (CVideoInfoTag*)item.GetVideoInfoTag();
370                 PopulateObjectFromTag(*tag, *object, &file_path, &resource, quirks);
371             }
372         } else if (item.IsPicture()) {
373             object->m_ObjectClass.type = "object.item.imageItem.photo";
374         } else {
375             object->m_ObjectClass.type = "object.item";
376         }
377
378         // duration of zero is invalid
379         if (resource.m_Duration == 0) resource.m_Duration = -1;
380
381         // Set the resource file size
382         resource.m_Size = item.m_dwSize;
383         if(resource.m_Size == 0)
384           resource.m_Size = (NPT_LargeSize)-1;
385
386         // set date
387         if (object->m_Date.IsEmpty() && item.m_dateTime.IsValid()) {
388             object->m_Date = item.m_dateTime.GetAsDBDate();
389         }
390
391         if (upnp_server) {
392             upnp_server->AddSafeResourceUri(object, rooturi, ips, file_path, GetProtocolInfo(item, "http", context));
393         }
394
395         // if the item is remote, add a direct link to the item
396         if (URIUtils::IsRemote((const char*)file_path)) {
397             resource.m_ProtocolInfo = PLT_ProtocolInfo(GetProtocolInfo(item, item.GetAsUrl().GetProtocol(), context));
398             resource.m_Uri = file_path;
399
400             // if the direct link can be served directly using http, then push it in front
401             // otherwise keep the xbmc-get resource last and let a compatible client look for it
402             if (resource.m_ProtocolInfo.ToString().StartsWith("xbmc", true)) {
403                 object->m_Resources.Add(resource);
404             } else {
405                 object->m_Resources.Insert(object->m_Resources.GetFirstItem(), resource);
406             }
407         }
408
409         // copy across the known metadata
410         for(unsigned i=0; i<object->m_Resources.GetItemCount(); i++) {
411             object->m_Resources[i].m_Size       = resource.m_Size;
412             object->m_Resources[i].m_Duration   = resource.m_Duration;
413             object->m_Resources[i].m_Resolution = resource.m_Resolution;
414         }
415
416         // Some upnp clients expect all audio items to have parent root id 4
417 #ifdef WMP_ID_MAPPING
418         object->m_ParentID = "4";
419 #endif
420     } else {
421         PLT_MediaContainer* container = new PLT_MediaContainer;
422         object = container;
423
424         /* Assign a title and id for this container */
425         container->m_ObjectID = item.GetPath();
426         container->m_ObjectClass.type = "object.container";
427         container->m_ChildrenCount = -1;
428
429         CStdStringArray strings;
430
431         /* this might be overkill, but hey */
432         if (item.IsMusicDb()) {
433             MUSICDATABASEDIRECTORY::NODE_TYPE node = CMusicDatabaseDirectory::GetDirectoryType(item.GetPath());
434             switch(node) {
435                 case MUSICDATABASEDIRECTORY::NODE_TYPE_ARTIST: {
436                       container->m_ObjectClass.type += ".person.musicArtist";
437                       CMusicInfoTag *tag = (CMusicInfoTag*)item.GetMusicInfoTag();
438                       if (tag) {
439                           container->m_People.artists.Add(
440                               CorrectAllItemsSortHack(StringUtils::Join(tag->GetArtist(), g_advancedSettings.m_musicItemSeparator)).c_str(), "Performer");
441                           container->m_People.artists.Add(
442                               CorrectAllItemsSortHack(StringUtils::Join(!tag->GetAlbumArtist().empty() ? tag->GetAlbumArtist() : tag->GetArtist(), g_advancedSettings.m_musicItemSeparator)).c_str(), "AlbumArtist");
443                       }
444 #ifdef WMP_ID_MAPPING
445                       // Some upnp clients expect all artists to have parent root id 107
446                       container->m_ParentID = "107";
447 #endif
448                   }
449                   break;
450                 case MUSICDATABASEDIRECTORY::NODE_TYPE_ALBUM:
451                 case MUSICDATABASEDIRECTORY::NODE_TYPE_ALBUM_COMPILATIONS:
452                 case MUSICDATABASEDIRECTORY::NODE_TYPE_ALBUM_RECENTLY_ADDED:
453                 case MUSICDATABASEDIRECTORY::NODE_TYPE_YEAR_ALBUM: {
454                       container->m_ObjectClass.type += ".album.musicAlbum";
455                       // for Sonos to be happy
456                       CMusicInfoTag *tag = (CMusicInfoTag*)item.GetMusicInfoTag();
457                       if (tag) {
458                           container->m_People.artists.Add(
459                               CorrectAllItemsSortHack(StringUtils::Join(tag->GetArtist(), g_advancedSettings.m_musicItemSeparator)).c_str(), "Performer");
460                           container->m_People.artists.Add(
461                               CorrectAllItemsSortHack(StringUtils::Join(!tag->GetAlbumArtist().empty() ? tag->GetAlbumArtist() : tag->GetArtist(), g_advancedSettings.m_musicItemSeparator)).c_str(), "AlbumArtist");
462                           container->m_Affiliation.album = CorrectAllItemsSortHack(tag->GetAlbum()).c_str();
463                       }
464 #ifdef WMP_ID_MAPPING
465                       // Some upnp clients expect all albums to have parent root id 7
466                       container->m_ParentID = "7";
467 #endif
468                   }
469                   break;
470                 case MUSICDATABASEDIRECTORY::NODE_TYPE_GENRE:
471                   container->m_ObjectClass.type += ".genre.musicGenre";
472                   break;
473                 default:
474                   break;
475             }
476         } else if (item.IsVideoDb()) {
477             VIDEODATABASEDIRECTORY::NODE_TYPE node = CVideoDatabaseDirectory::GetDirectoryType(item.GetPath());
478             CVideoInfoTag &tag = *(CVideoInfoTag*)item.GetVideoInfoTag();
479             switch(node) {
480                 case VIDEODATABASEDIRECTORY::NODE_TYPE_GENRE:
481                   container->m_ObjectClass.type += ".genre.movieGenre";
482                   break;
483                 case VIDEODATABASEDIRECTORY::NODE_TYPE_ACTOR:
484                   container->m_ObjectClass.type += ".person.videoArtist";
485                   container->m_Creator = StringUtils::Join(tag.m_artist, g_advancedSettings.m_videoItemSeparator);
486                   container->m_Title   = tag.m_strTitle;
487                   break;
488                 case VIDEODATABASEDIRECTORY::NODE_TYPE_SEASONS:
489                 case VIDEODATABASEDIRECTORY::NODE_TYPE_TITLE_TVSHOWS:
490                   container->m_ObjectClass.type += ".album.videoAlbum";
491                   container->m_Recorded.series_title = tag.m_strShowTitle;
492                   container->m_Recorded.episode_number = tag.m_iEpisode;
493                   container->m_MiscInfo.play_count = tag.m_playCount;
494                   container->m_Title = tag.m_strTitle;
495                   if(!tag.m_premiered.IsValid() && tag.m_iYear)
496                     container->m_Date = NPT_String::FromInteger(tag.m_iYear) + "-01-01";
497                   else
498                     container->m_Date = tag.m_premiered.GetAsDBDate();
499
500                   for (unsigned int index = 0; index < tag.m_genre.size(); index++)
501                     container->m_Affiliation.genres.Add(tag.m_genre.at(index).c_str());
502
503                   for(CVideoInfoTag::iCast it = tag.m_cast.begin();it != tag.m_cast.end();it++) {
504                       container->m_People.actors.Add(it->strName.c_str(), it->strRole.c_str());
505                   }
506
507                   for (unsigned int index = 0; index < tag.m_director.size(); index++)
508                     container->m_People.directors.Add(tag.m_director[index].c_str());
509                   for (unsigned int index = 0; index < tag.m_writingCredits.size(); index++)
510                     container->m_People.authors.Add(tag.m_writingCredits[index].c_str());
511
512                   container->m_Description.description = tag.m_strTagLine;
513                   container->m_Description.long_description = tag.m_strPlot;
514
515                   break;
516                 default:
517                   container->m_ObjectClass.type += ".storageFolder";
518                   break;
519             }
520         } else if (item.IsPlayList() || item.IsSmartPlayList()) {
521             container->m_ObjectClass.type += ".playlistContainer";
522         }
523
524         if(quirks & ECLIENTQUIRKS_ONLYSTORAGEFOLDER) {
525           container->m_ObjectClass.type = "object.container.storageFolder";
526         }
527
528         /* Get the number of children for this container */
529         if (with_count && upnp_server) {
530             if (object->m_ObjectID.StartsWith("virtualpath://")) {
531                 NPT_LargeSize count = 0;
532                 NPT_CHECK_LABEL(NPT_File::GetSize(file_path, count), failure);
533                 container->m_ChildrenCount = (NPT_Int32)count;
534             } else {
535                 /* this should be a standard path */
536                 // TODO - get file count of this directory
537             }
538         }
539     }
540
541     // set a title for the object
542     if (object->m_Title.IsEmpty()) {
543         if (!item.GetLabel().IsEmpty()) {
544             CStdString title = item.GetLabel();
545             if (item.IsPlayList() || !item.m_bIsFolder) URIUtils::RemoveExtension(title);
546             object->m_Title = title;
547         }
548     }
549
550     // determine the correct artwork for this item
551     if (!thumb_loader.IsNull())
552         thumb_loader->LoadItem(&item);
553
554     // finally apply the found artwork
555     thumb = item.GetArt("thumb");
556     if (upnp_server && !thumb.empty()) {
557         PLT_AlbumArtInfo art;
558         art.uri = upnp_server->BuildSafeResourceUri(
559             rooturi,
560             (*ips.GetFirstItem()).ToString(),
561             CTextureCache::GetWrappedImageURL(thumb).c_str());
562
563         // Set DLNA profileID by extension, defaulting to JPEG.
564         NPT_String ext = URIUtils::GetExtension(thumb).c_str();
565         if (strcmp(ext, ".png") == 0) {
566             art.dlna_profile = "PNG_TN";
567         } else {
568             art.dlna_profile = "JPEG_TN";
569         }
570         object->m_ExtraInfo.album_arts.Add(art);
571     }
572
573     fanart = item.GetArt("fanart");
574     if (upnp_server && !fanart.empty())
575         upnp_server->AddSafeResourceUri(object, rooturi, ips, CTextureCache::GetWrappedImageURL(fanart), "xbmc.org:*:fanart:*");
576
577     return object;
578
579 failure:
580     delete object;
581     return NULL;
582 }
583
584 /*----------------------------------------------------------------------
585 |   CUPnPServer::CorrectAllItemsSortHack
586 +---------------------------------------------------------------------*/
587 const CStdString&
588 CorrectAllItemsSortHack(const CStdString &item)
589 {
590     // This is required as in order for the "* All Albums" etc. items to sort
591     // correctly, they must have fake artist/album etc. information generated.
592     // This looks nasty if we attempt to render it to the GUI, thus this (further)
593     // workaround
594     if ((item.size() == 1 && item[0] == 0x01) || (item.size() > 1 && ((unsigned char) item[1]) == 0xff))
595         return StringUtils::EmptyString;
596
597     return item;
598 }
599
600 int
601 PopulateTagFromObject(CMusicInfoTag&          tag,
602                       PLT_MediaObject&       object,
603                       PLT_MediaItemResource* resource /* = NULL */)
604 {
605     tag.SetTitle((const char*)object.m_Title);
606     tag.SetArtist((const char*)object.m_Creator);
607     for(PLT_PersonRoles::Iterator it = object.m_People.artists.GetFirstItem(); it; it++) {
608         if     (it->role == "")            tag.SetArtist((const char*)it->name);
609         else if(it->role == "Performer")   tag.SetArtist((const char*)it->name);
610         else if(it->role == "AlbumArtist") tag.SetAlbumArtist((const char*)it->name);
611     }
612     tag.SetTrackNumber(object.m_MiscInfo.original_track_number);
613
614     for (NPT_List<NPT_String>::Iterator it = object.m_Affiliation.genres.GetFirstItem(); it; it++)
615         tag.SetGenre((const char*) *it);
616
617     tag.SetAlbum((const char*)object.m_Affiliation.album);
618     CDateTime last;
619     last.SetFromDateString((const char*)object.m_MiscInfo.last_time);
620     tag.SetLastPlayed(last);
621     tag.SetPlayCount(object.m_MiscInfo.play_count);
622     if(resource)
623         tag.SetDuration(resource->m_Duration);
624     tag.SetLoaded();
625     return NPT_SUCCESS;
626 }
627
628 int
629 PopulateTagFromObject(CVideoInfoTag&         tag,
630                       PLT_MediaObject&       object,
631                       PLT_MediaItemResource* resource /* = NULL */)
632 {
633     CDateTime date;
634     date.SetFromDateString((const char*)object.m_Date);
635
636     if(!object.m_Recorded.program_title.IsEmpty())
637     {
638         tag.m_type = "episode";
639         int episode;
640         int season;
641         int title = object.m_Recorded.program_title.Find(" : ");
642         if(sscanf(object.m_Recorded.program_title, "S%2dE%2d", &season, &episode) == 2 && title >= 0) {
643             tag.m_strTitle = object.m_Recorded.program_title.SubString(title + 3);
644             tag.m_iEpisode = episode;
645             tag.m_iSeason  = season;
646         } else {
647             tag.m_strTitle = object.m_Recorded.program_title;
648             tag.m_iSeason  = object.m_Recorded.episode_number / 100;
649             tag.m_iEpisode = object.m_Recorded.episode_number % 100;
650         }
651         tag.m_firstAired = date;
652     }
653     else if (!object.m_Recorded.series_title.IsEmpty()) {
654         tag.m_type= "season";
655         tag.m_strTitle = object.m_Title; // because could be TV show Title, or Season 1 etc
656         tag.m_iSeason  = object.m_Recorded.episode_number / 100;
657         tag.m_iEpisode = object.m_Recorded.episode_number % 100;
658         tag.m_premiered = date;
659     }
660     else if(object.m_ObjectClass.type == "object.item.videoItem.musicVideoClip") {
661         tag.m_type = "musicvideo";
662     }
663     else
664     {
665         tag.m_type         = "movie";
666         tag.m_strTitle     = object.m_Title;
667         tag.m_premiered    = date;
668     }
669     tag.m_iYear       = date.GetYear();
670     for (unsigned int index = 0; index < object.m_Affiliation.genres.GetItemCount(); index++)
671       tag.m_genre.push_back(object.m_Affiliation.genres.GetItem(index)->GetChars());
672     for (unsigned int index = 0; index < object.m_People.directors.GetItemCount(); index++)
673       tag.m_director.push_back(object.m_People.directors.GetItem(index)->name.GetChars());
674     for (unsigned int index = 0; index < object.m_People.authors.GetItemCount(); index++)
675       tag.m_writingCredits.push_back(object.m_People.authors.GetItem(index)->name.GetChars());
676     tag.m_strTagLine  = object.m_Description.description;
677     tag.m_strPlot     = object.m_Description.long_description;
678     tag.m_strMPAARating = object.m_Description.rating;
679     tag.m_strShowTitle = object.m_Recorded.series_title;
680     tag.m_lastPlayed.SetFromDateString((const char*)object.m_MiscInfo.last_time);
681     tag.m_playCount = object.m_MiscInfo.play_count;
682
683     if(resource)
684     {
685       if (resource->m_Duration)
686         tag.m_duration = resource->m_Duration;
687       if (object.m_MiscInfo.last_position > 0 )
688       {
689         tag.m_resumePoint.totalTimeInSeconds = resource->m_Duration;
690         tag.m_resumePoint.timeInSeconds = object.m_MiscInfo.last_position;
691       }
692     }
693     return NPT_SUCCESS;
694 }
695
696 CFileItemPtr BuildObject(PLT_MediaObject* entry)
697 {
698   NPT_String ObjectClass = entry->m_ObjectClass.type.ToLowercase();
699
700   CFileItemPtr pItem(new CFileItem((const char*)entry->m_Title));
701   pItem->SetLabelPreformated(true);
702   pItem->m_strTitle = (const char*)entry->m_Title;
703   pItem->m_bIsFolder = entry->IsContainer();
704
705   // if it's a container, format a string as upnp://uuid/object_id
706   if (pItem->m_bIsFolder) {
707
708     // look for metadata
709     if( ObjectClass.StartsWith("object.container.album.videoalbum") ) {
710       pItem->SetLabelPreformated(false);
711       UPNP::PopulateTagFromObject(*pItem->GetVideoInfoTag(), *entry, NULL);
712
713     } else if( ObjectClass.StartsWith("object.container.album.photoalbum")) {
714       //CPictureInfoTag* tag = pItem->GetPictureInfoTag();
715
716     } else if( ObjectClass.StartsWith("object.container.album") ) {
717       pItem->SetLabelPreformated(false);
718       UPNP::PopulateTagFromObject(*pItem->GetMusicInfoTag(), *entry, NULL);
719     }
720
721   } else {
722     bool audio = false
723        , image = false
724        , video = false;
725     // set a general content type
726     const char* content = NULL;
727     if (ObjectClass.StartsWith("object.item.videoitem")) {
728       pItem->SetMimeType("video/octet-stream");
729       content = "video";
730       video = true;
731     }
732     else if(ObjectClass.StartsWith("object.item.audioitem")) {
733       pItem->SetMimeType("audio/octet-stream");
734       content = "audio";
735       audio = true;
736     }
737     else if(ObjectClass.StartsWith("object.item.imageitem")) {
738       pItem->SetMimeType("image/octet-stream");
739       content = "image";
740       image = true;
741     }
742
743     // attempt to find a valid resource (may be multiple)
744     PLT_MediaItemResource resource, *res = NULL;
745     if(NPT_SUCCEEDED(NPT_ContainerFind(entry->m_Resources,
746                                        CResourceFinder("http-get", content), resource))) {
747
748       // set metadata
749       if (resource.m_Size != (NPT_LargeSize)-1) {
750         pItem->m_dwSize  = resource.m_Size;
751       }
752       res = &resource;
753     }
754     // look for metadata
755     if(video) {
756         pItem->SetLabelPreformated(false);
757         UPNP::PopulateTagFromObject(*pItem->GetVideoInfoTag(), *entry, res);
758
759     } else if(audio) {
760         pItem->SetLabelPreformated(false);
761         UPNP::PopulateTagFromObject(*pItem->GetMusicInfoTag(), *entry, res);
762
763     } else if(image) {
764         //CPictureInfoTag* tag = pItem->GetPictureInfoTag();
765
766     }
767   }
768
769   // look for date?
770   if(entry->m_Description.date.GetLength()) {
771     SYSTEMTIME time = {};
772     sscanf(entry->m_Description.date, "%hu-%hu-%huT%hu:%hu:%hu",
773            &time.wYear, &time.wMonth, &time.wDay, &time.wHour, &time.wMinute, &time.wSecond);
774     pItem->m_dateTime = time;
775   }
776
777   // if there is a thumbnail available set it here
778   if(entry->m_ExtraInfo.album_arts.GetItem(0))
779     // only considers first album art
780     pItem->SetArt("thumb", (const char*) entry->m_ExtraInfo.album_arts.GetItem(0)->uri);
781   else if(entry->m_Description.icon_uri.GetLength())
782     pItem->SetArt("thumb", (const char*) entry->m_Description.icon_uri);
783
784   PLT_ProtocolInfo fanart_mask("xbmc.org", "*", "fanart", "*");
785   for(unsigned i = 0; i < entry->m_Resources.GetItemCount(); ++i) {
786     PLT_MediaItemResource& res = entry->m_Resources[i];
787     if(res.m_ProtocolInfo.Match(fanart_mask)) {
788       pItem->SetArt("fanart", (const char*)res.m_Uri);
789       break;
790     }
791   }
792   // set the watched overlay, as this will not be set later due to
793   // content set on file item list
794   if (pItem->HasVideoInfoTag()) {
795     int episodes = pItem->GetVideoInfoTag()->m_iEpisode;
796     int played   = pItem->GetVideoInfoTag()->m_playCount;
797     const std::string& type = pItem->GetVideoInfoTag()->m_type;
798     bool watched(false);
799     if (type == "tvshow" || type == "season") {
800       pItem->SetProperty("totalepisodes", episodes);
801       pItem->SetProperty("numepisodes", episodes);
802       pItem->SetProperty("watchedepisodes", played);
803       pItem->SetProperty("unwatchedepisodes", episodes - played);
804       watched = (episodes && played == episodes);
805     }
806     else if (type == "episode" || type == "movie")
807       watched = (played > 0);
808     pItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, watched);
809   }
810   return pItem;
811 }
812
813 bool GetResource(const PLT_MediaObject* entry, CFileItem& item)
814 {
815   PLT_MediaItemResource resource;
816
817   // store original path so we remember it
818   item.SetProperty("original_listitem_url",  item.GetPath());
819   item.SetProperty("original_listitem_mime", item.GetMimeType(false));
820
821   // look for a resource with "xbmc-get" protocol
822   // if we can't find one, try to find a valid resource
823   if(NPT_FAILED(NPT_ContainerFind(entry->m_Resources,
824                                   CResourceFinder("xbmc-get"), resource))) {
825     const char* content = NULL;
826     if (entry->m_ObjectClass.type.StartsWith("object.item.audioItem"))
827       content = "audio";
828     else if (entry->m_ObjectClass.type.StartsWith("object.item.imageItem"))
829       content = "image";
830     else if (entry->m_ObjectClass.type.StartsWith("object.item.videoItem"))
831       content = "video";
832
833     if(NPT_FAILED(NPT_ContainerFind(entry->m_Resources,
834                                     CResourceFinder("http-get", content), resource))) {
835       if(entry->m_Resources.GetItemCount()) {
836         // last attempt to find something suitable
837         resource = entry->m_Resources[0];
838       }
839       else {
840         CLog::Log(LOGERROR, "CUPnPDirectory::GetResource - no resources available for object %s", (const char*)entry->m_ObjectID);
841         return false;
842       }
843     }
844   }
845
846   // if it's an item, path is the first url to the item
847   // we hope the server made the first one reachable for us
848   // (it could be a format we dont know how to play however)
849   item.SetPath((const char*) resource.m_Uri);
850
851   // look for content type in protocol info
852   if (resource.m_ProtocolInfo.IsValid()) {
853     CLog::Log(LOGDEBUG, "CUPnPDirectory::GetResource - resource protocol info '%s'",
854               (const char*)(resource.m_ProtocolInfo.ToString()));
855
856     if (resource.m_ProtocolInfo.GetContentType().Compare("application/octet-stream") != 0) {
857       item.SetMimeType((const char*)resource.m_ProtocolInfo.GetContentType());
858     }
859   } else {
860     CLog::Log(LOGERROR, "CUPnPDirectory::GetResource - invalid protocol info '%s'",
861               (const char*)(resource.m_ProtocolInfo.ToString()));
862   }
863
864   // look for subtitles
865   unsigned subs = 0;
866   for(unsigned r = 0; r < entry->m_Resources.GetItemCount(); r++)
867   {
868     const PLT_MediaItemResource& res  = entry->m_Resources[r];
869     const PLT_ProtocolInfo&      info = res.m_ProtocolInfo;
870     static const char* allowed[] = { "text/srt"
871       , "text/ssa"
872       , "text/sub"
873       , "text/idx" };
874     for(unsigned type = 0; type < sizeof(allowed)/sizeof(allowed[0]); type++)
875     {
876       if(info.Match(PLT_ProtocolInfo("*", "*", allowed[type], "*")))
877       {
878         CStdString prop;
879         prop.Format("upnp:subtitle:%d", ++subs);
880         item.SetProperty(prop, (const char*)res.m_Uri);
881         break;
882       }
883     }
884   }
885   return true;
886 }
887
888 CFileItemPtr GetFileItem(const NPT_String& uri, const NPT_String& meta)
889 {
890     PLT_MediaObjectListReference list;
891     PLT_MediaObject*             object = NULL;
892     CFileItemPtr                 item;
893
894     if (NPT_SUCCEEDED(PLT_Didl::FromDidl(meta, list))) {
895         list->Get(0, object);
896     }
897
898     if (object) {
899         item = BuildObject(object);
900     }
901
902     if (item) {
903         item->SetPath((const char*)uri);
904         GetResource(object, *item);
905     } else {
906         item.reset(new CFileItem((const char*)uri, false));
907     }
908     return item;
909 }
910
911 } /* namespace UPNP */
912