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