2 * Copyright (C) 2012-2013 Team XBMC
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)
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.
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/>.
20 #include "UPnPInternal.h"
22 #include "UPnPServer.h"
26 #include "settings/AdvancedSettings.h"
27 #include "utils/log.h"
28 #include "utils/StringUtils.h"
29 #include "utils/URIUtils.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"
43 using namespace MUSIC_INFO;
44 using namespace XFILE;
49 /*----------------------------------------------------------------------
51 +---------------------------------------------------------------------*/
52 EClientQuirks GetClientQuirks(const PLT_HttpRequestContext* context)
55 return ECLIENTQUIRKS_NONE;
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);
62 if (user_agent->Find("XBox", 0, true) >= 0 ||
63 user_agent->Find("Xenon", 0, true) >= 0)
64 quirks |= ECLIENTQUIRKS_ONLYSTORAGEFOLDER | ECLIENTQUIRKS_BASICVIDEOCLASS;
66 if (user_agent->Find("Windows-Media-Player", 0, true) >= 0)
67 quirks |= ECLIENTQUIRKS_UNKNOWNSERIES;
71 if (server->Find("Xbox", 0, true) >= 0)
72 quirks |= ECLIENTQUIRKS_ONLYSTORAGEFOLDER | ECLIENTQUIRKS_BASICVIDEOCLASS;
75 return (EClientQuirks)quirks;
78 /*----------------------------------------------------------------------
80 +---------------------------------------------------------------------*/
82 GetMimeType(const char* filename,
83 const PLT_HttpRequestContext* context /* = NULL */)
85 NPT_String ext = URIUtils::GetExtension(filename).c_str();
87 ext = ext.ToLowercase();
89 return PLT_MimeType::GetMimeTypeFromExtension(ext, context);
92 /*----------------------------------------------------------------------
94 +---------------------------------------------------------------------*/
96 GetMimeType(const CFileItem& item,
97 const PLT_HttpRequestContext* context /* = NULL */)
99 CStdString path = item.GetPath();
100 if (item.HasVideoInfoTag() && !item.GetVideoInfoTag()->GetPath().IsEmpty()) {
101 path = item.GetVideoInfoTag()->GetPath();
102 } else if (item.HasMusicInfoTag() && !item.GetMusicInfoTag()->GetURL().IsEmpty()) {
103 path = item.GetMusicInfoTag()->GetURL();
106 if (URIUtils::IsStack(path))
107 path = XFILE::CStackDirectory::GetFirstStackedFile(path);
109 NPT_String ext = URIUtils::GetExtension(path).c_str();
111 ext = ext.ToLowercase();
115 /* We always use Platinum mime type first
116 as it is defined to map extension to DLNA compliant mime type
117 or custom according to context (who asked for it) */
118 if (!ext.IsEmpty()) {
119 mime = PLT_MimeType::GetMimeTypeFromExtension(ext, context);
120 if (mime == "application/octet-stream") mime = "";
123 /* if Platinum couldn't map it, default to XBMC mapping */
124 if (mime.IsEmpty()) {
125 NPT_String mime = item.GetMimeType().c_str();
126 if (mime == "application/octet-stream") mime = "";
129 /* fallback to generic mime type if not found */
130 if (mime.IsEmpty()) {
131 if (item.IsVideo() || item.IsVideoDb() )
132 mime = "video/" + ext;
133 else if (item.IsAudio() || item.IsMusicDb() )
134 mime = "audio/" + ext;
135 else if (item.IsPicture() )
136 mime = "image/" + ext;
139 /* nothing we can figure out */
140 if (mime.IsEmpty()) {
141 mime = "application/octet-stream";
147 /*----------------------------------------------------------------------
149 +---------------------------------------------------------------------*/
151 GetProtocolInfo(const CFileItem& item,
152 const char* protocol,
153 const PLT_HttpRequestContext* context /* = NULL */)
155 NPT_String proto = protocol;
157 /* fixup the protocol just in case nothing was passed */
158 if (proto.IsEmpty()) {
159 proto = item.GetAsUrl().GetProtocol();
163 map protocol to right prefix and use xbmc-get for
164 unsupported UPnP protocols for other xbmc clients
167 if (proto == "http") {
173 /* we need a valid extension to retrieve the mimetype for the protocol info */
174 NPT_String mime = GetMimeType(item, context);
175 proto += ":*:" + mime + ":" + PLT_ProtocolInfo::GetDlnaExtension(mime, context);
179 /*----------------------------------------------------------------------
181 +---------------------------------------------------------------------*/
182 CResourceFinder::CResourceFinder(const char* protocol, const char* content)
183 : m_Protocol(protocol)
188 bool CResourceFinder::operator()(const PLT_MediaItemResource& resource) const {
189 if (m_Content.IsEmpty())
190 return (resource.m_ProtocolInfo.GetProtocol().Compare(m_Protocol, true) == 0);
192 return ((resource.m_ProtocolInfo.GetProtocol().Compare(m_Protocol, true) == 0)
193 && resource.m_ProtocolInfo.GetContentType().StartsWith(m_Content, true));
196 /*----------------------------------------------------------------------
197 | PopulateObjectFromTag
198 +---------------------------------------------------------------------*/
200 PopulateObjectFromTag(CMusicInfoTag& tag,
201 PLT_MediaObject& object,
202 NPT_String* file_path, /* = NULL */
203 PLT_MediaItemResource* resource, /* = NULL */
204 EClientQuirks quirks)
206 if (!tag.GetURL().IsEmpty() && file_path)
207 *file_path = tag.GetURL();
209 std::vector<std::string> genres = tag.GetGenre();
210 for (unsigned int index = 0; index < genres.size(); index++)
211 object.m_Affiliation.genres.Add(genres.at(index).c_str());
212 object.m_Title = tag.GetTitle();
213 object.m_Affiliation.album = tag.GetAlbum();
214 for (unsigned int index = 0; index < tag.GetArtist().size(); index++)
216 object.m_People.artists.Add(tag.GetArtist().at(index).c_str());
217 object.m_People.artists.Add(tag.GetArtist().at(index).c_str(), "Performer");
219 object.m_People.artists.Add(StringUtils::Join(!tag.GetAlbumArtist().empty() ? tag.GetAlbumArtist() : tag.GetArtist(), g_advancedSettings.m_musicItemSeparator).c_str(), "AlbumArtist");
220 if(tag.GetAlbumArtist().empty())
221 object.m_Creator = StringUtils::Join(tag.GetArtist(), g_advancedSettings.m_musicItemSeparator);
223 object.m_Creator = StringUtils::Join(tag.GetAlbumArtist(), g_advancedSettings.m_musicItemSeparator);
224 object.m_MiscInfo.original_track_number = tag.GetTrackNumber();
225 if(tag.GetDatabaseId() >= 0) {
226 object.m_ReferenceID = NPT_String::Format("musicdb://songs/%i%s", tag.GetDatabaseId(), URIUtils::GetExtension(tag.GetURL()).c_str());
228 if (object.m_ReferenceID == object.m_ObjectID)
229 object.m_ReferenceID = "";
231 object.m_MiscInfo.last_time = tag.GetLastPlayed().GetAsDBDate();
232 object.m_MiscInfo.play_count = tag.GetPlayCount();
234 if (resource) resource->m_Duration = tag.GetDuration();
239 /*----------------------------------------------------------------------
240 | PopulateObjectFromTag
241 +---------------------------------------------------------------------*/
243 PopulateObjectFromTag(CVideoInfoTag& tag,
244 PLT_MediaObject& object,
245 NPT_String* file_path, /* = NULL */
246 PLT_MediaItemResource* resource, /* = NULL */
247 EClientQuirks quirks)
249 // some usefull buffers
250 CStdStringArray strings;
252 if (!tag.m_strFileNameAndPath.IsEmpty() && file_path)
253 *file_path = tag.m_strFileNameAndPath;
255 if (tag.m_iDbId != -1 ) {
256 if (tag.m_type == "musicvideo") {
257 object.m_ObjectClass.type = "object.item.videoItem.musicVideoClip";
258 object.m_Creator = StringUtils::Join(tag.m_artist, g_advancedSettings.m_videoItemSeparator);
259 object.m_Title = tag.m_strTitle;
260 object.m_ReferenceID = NPT_String::Format("videodb://musicvideos/titles/%i", tag.m_iDbId);
261 } else if (tag.m_type == "movie") {
262 object.m_ObjectClass.type = "object.item.videoItem.movie";
263 object.m_Title = tag.m_strTitle;
264 object.m_Date = NPT_String::FromInteger(tag.m_iYear) + "-01-01";
265 object.m_ReferenceID = NPT_String::Format("videodb://movies/titles/%i", tag.m_iDbId);
267 object.m_ObjectClass.type = "object.item.videoItem.videoBroadcast";
268 object.m_Recorded.program_title = "S" + ("0" + NPT_String::FromInteger(tag.m_iSeason)).Right(2);
269 object.m_Recorded.program_title += "E" + ("0" + NPT_String::FromInteger(tag.m_iEpisode)).Right(2);
270 object.m_Recorded.program_title += " : " + tag.m_strTitle;
271 object.m_Recorded.series_title = tag.m_strShowTitle;
272 int season = tag.m_iSeason > 1 ? tag.m_iSeason : 1;
273 object.m_Recorded.episode_number = season * 100 + tag.m_iEpisode;
274 object.m_Title = object.m_Recorded.series_title + " - " + object.m_Recorded.program_title;
275 object.m_Date = tag.m_firstAired.GetAsDBDate();
276 if(tag.m_iSeason != -1)
277 object.m_ReferenceID = NPT_String::Format("videodb://tvshows/0/%i", tag.m_iDbId);
281 if(quirks & ECLIENTQUIRKS_BASICVIDEOCLASS)
282 object.m_ObjectClass.type = "object.item.videoItem";
284 if(object.m_ReferenceID == object.m_ObjectID)
285 object.m_ReferenceID = "";
287 for (unsigned int index = 0; index < tag.m_genre.size(); index++)
288 object.m_Affiliation.genres.Add(tag.m_genre.at(index).c_str());
290 for(CVideoInfoTag::iCast it = tag.m_cast.begin();it != tag.m_cast.end();it++) {
291 object.m_People.actors.Add(it->strName.c_str(), it->strRole.c_str());
294 for (unsigned int index = 0; index < tag.m_director.size(); index++)
295 object.m_People.directors.Add(tag.m_director[index].c_str());
297 for (unsigned int index = 0; index < tag.m_writingCredits.size(); index++)
298 object.m_People.authors.Add(tag.m_writingCredits[index].c_str());
300 object.m_Description.description = tag.m_strTagLine;
301 object.m_Description.long_description = tag.m_strPlot;
302 object.m_Description.rating = tag.m_strMPAARating;
303 object.m_MiscInfo.last_position = (NPT_UInt32)tag.m_resumePoint.timeInSeconds;
304 object.m_MiscInfo.last_time = tag.m_lastPlayed.GetAsDBDate();
305 object.m_MiscInfo.play_count = tag.m_playCount;
307 resource->m_Duration = tag.GetDuration();
308 if (tag.HasStreamDetails()) {
309 const CStreamDetails &details = tag.m_streamDetails;
310 resource->m_Resolution = NPT_String::FromInteger(details.GetVideoWidth()) + "x" + NPT_String::FromInteger(details.GetVideoHeight());
317 /*----------------------------------------------------------------------
319 +---------------------------------------------------------------------*/
321 BuildObject(CFileItem& item,
322 NPT_String& file_path,
324 NPT_Reference<CThumbLoader>& thumb_loader,
325 const PLT_HttpRequestContext* context /* = NULL */,
326 CUPnPServer* upnp_server /* = NULL */)
328 PLT_MediaItemResource resource;
329 PLT_MediaObject* object = NULL;
330 std::string thumb, fanart;
332 CLog::Log(LOGDEBUG, "UPnP: Building didl for object '%s'", (const char*)item.GetPath());
334 EClientQuirks quirks = GetClientQuirks(context);
336 // get list of ip addresses
337 NPT_List<NPT_IpAddress> ips;
339 NPT_CHECK_LABEL(PLT_UPnPMessageHelper::GetIPAddresses(ips), failure);
341 // if we're passed an interface where we received the request from
342 // move the ip to the top
343 if (context && context->GetLocalAddress().GetIpAddress().ToString() != "0.0.0.0") {
344 rooturi = NPT_HttpUrl(context->GetLocalAddress().GetIpAddress().ToString(), context->GetLocalAddress().GetPort(), "/");
345 ips.Remove(context->GetLocalAddress().GetIpAddress());
346 ips.Insert(ips.GetFirstItem(), context->GetLocalAddress().GetIpAddress());
347 } else if(upnp_server) {
348 rooturi = NPT_HttpUrl("localhost", upnp_server->GetPort(), "/");
351 if (!item.m_bIsFolder) {
352 object = new PLT_MediaItem();
353 object->m_ObjectID = item.GetPath();
355 /* Setup object type */
356 if (item.IsMusicDb() || item.IsAudio()) {
357 object->m_ObjectClass.type = "object.item.audioItem.musicTrack";
359 if (item.HasMusicInfoTag()) {
360 CMusicInfoTag *tag = (CMusicInfoTag*)item.GetMusicInfoTag();
361 PopulateObjectFromTag(*tag, *object, &file_path, &resource, quirks);
363 } else if (item.IsVideoDb() || item.IsVideo()) {
364 object->m_ObjectClass.type = "object.item.videoItem";
366 if(quirks & ECLIENTQUIRKS_UNKNOWNSERIES)
367 object->m_Affiliation.album = "[Unknown Series]";
369 if (item.HasVideoInfoTag()) {
370 CVideoInfoTag *tag = (CVideoInfoTag*)item.GetVideoInfoTag();
371 PopulateObjectFromTag(*tag, *object, &file_path, &resource, quirks);
373 } else if (item.IsPicture()) {
374 object->m_ObjectClass.type = "object.item.imageItem.photo";
376 object->m_ObjectClass.type = "object.item";
379 // duration of zero is invalid
380 if (resource.m_Duration == 0) resource.m_Duration = -1;
382 // Set the resource file size
383 resource.m_Size = item.m_dwSize;
384 if(resource.m_Size == 0)
385 resource.m_Size = (NPT_LargeSize)-1;
388 if (object->m_Date.IsEmpty() && item.m_dateTime.IsValid()) {
389 object->m_Date = item.m_dateTime.GetAsDBDate();
393 upnp_server->AddSafeResourceUri(object, rooturi, ips, file_path, GetProtocolInfo(item, "http", context));
396 // if the item is remote, add a direct link to the item
397 if (URIUtils::IsRemote((const char*)file_path)) {
398 resource.m_ProtocolInfo = PLT_ProtocolInfo(GetProtocolInfo(item, item.GetAsUrl().GetProtocol(), context));
399 resource.m_Uri = file_path;
401 // if the direct link can be served directly using http, then push it in front
402 // otherwise keep the xbmc-get resource last and let a compatible client look for it
403 if (resource.m_ProtocolInfo.ToString().StartsWith("xbmc", true)) {
404 object->m_Resources.Add(resource);
406 object->m_Resources.Insert(object->m_Resources.GetFirstItem(), resource);
410 // copy across the known metadata
411 for(unsigned i=0; i<object->m_Resources.GetItemCount(); i++) {
412 object->m_Resources[i].m_Size = resource.m_Size;
413 object->m_Resources[i].m_Duration = resource.m_Duration;
414 object->m_Resources[i].m_Resolution = resource.m_Resolution;
417 // Some upnp clients expect all audio items to have parent root id 4
418 #ifdef WMP_ID_MAPPING
419 object->m_ParentID = "4";
422 PLT_MediaContainer* container = new PLT_MediaContainer;
425 /* Assign a title and id for this container */
426 container->m_ObjectID = item.GetPath();
427 container->m_ObjectClass.type = "object.container";
428 container->m_ChildrenCount = -1;
430 CStdStringArray strings;
432 /* this might be overkill, but hey */
433 if (item.IsMusicDb()) {
434 MUSICDATABASEDIRECTORY::NODE_TYPE node = CMusicDatabaseDirectory::GetDirectoryType(item.GetPath());
436 case MUSICDATABASEDIRECTORY::NODE_TYPE_ARTIST: {
437 container->m_ObjectClass.type += ".person.musicArtist";
438 CMusicInfoTag *tag = (CMusicInfoTag*)item.GetMusicInfoTag();
440 container->m_People.artists.Add(
441 CorrectAllItemsSortHack(StringUtils::Join(tag->GetArtist(), g_advancedSettings.m_musicItemSeparator)).c_str(), "Performer");
442 container->m_People.artists.Add(
443 CorrectAllItemsSortHack(StringUtils::Join(!tag->GetAlbumArtist().empty() ? tag->GetAlbumArtist() : tag->GetArtist(), g_advancedSettings.m_musicItemSeparator)).c_str(), "AlbumArtist");
445 #ifdef WMP_ID_MAPPING
446 // Some upnp clients expect all artists to have parent root id 107
447 container->m_ParentID = "107";
451 case MUSICDATABASEDIRECTORY::NODE_TYPE_ALBUM:
452 case MUSICDATABASEDIRECTORY::NODE_TYPE_ALBUM_COMPILATIONS:
453 case MUSICDATABASEDIRECTORY::NODE_TYPE_ALBUM_RECENTLY_ADDED:
454 case MUSICDATABASEDIRECTORY::NODE_TYPE_YEAR_ALBUM: {
455 container->m_ObjectClass.type += ".album.musicAlbum";
456 // for Sonos to be happy
457 CMusicInfoTag *tag = (CMusicInfoTag*)item.GetMusicInfoTag();
459 container->m_People.artists.Add(
460 CorrectAllItemsSortHack(StringUtils::Join(tag->GetArtist(), g_advancedSettings.m_musicItemSeparator)).c_str(), "Performer");
461 container->m_People.artists.Add(
462 CorrectAllItemsSortHack(StringUtils::Join(!tag->GetAlbumArtist().empty() ? tag->GetAlbumArtist() : tag->GetArtist(), g_advancedSettings.m_musicItemSeparator)).c_str(), "AlbumArtist");
463 container->m_Affiliation.album = CorrectAllItemsSortHack(tag->GetAlbum()).c_str();
465 #ifdef WMP_ID_MAPPING
466 // Some upnp clients expect all albums to have parent root id 7
467 container->m_ParentID = "7";
471 case MUSICDATABASEDIRECTORY::NODE_TYPE_GENRE:
472 container->m_ObjectClass.type += ".genre.musicGenre";
477 } else if (item.IsVideoDb()) {
478 VIDEODATABASEDIRECTORY::NODE_TYPE node = CVideoDatabaseDirectory::GetDirectoryType(item.GetPath());
479 CVideoInfoTag &tag = *(CVideoInfoTag*)item.GetVideoInfoTag();
481 case VIDEODATABASEDIRECTORY::NODE_TYPE_GENRE:
482 container->m_ObjectClass.type += ".genre.movieGenre";
484 case VIDEODATABASEDIRECTORY::NODE_TYPE_ACTOR:
485 container->m_ObjectClass.type += ".person.videoArtist";
486 container->m_Creator = StringUtils::Join(tag.m_artist, g_advancedSettings.m_videoItemSeparator);
487 container->m_Title = tag.m_strTitle;
489 case VIDEODATABASEDIRECTORY::NODE_TYPE_SEASONS:
490 case VIDEODATABASEDIRECTORY::NODE_TYPE_TITLE_TVSHOWS:
491 container->m_ObjectClass.type += ".album.videoAlbum";
492 container->m_Recorded.series_title = tag.m_strShowTitle;
493 container->m_Recorded.episode_number = tag.m_iEpisode;
494 container->m_MiscInfo.play_count = tag.m_playCount;
495 container->m_Title = tag.m_strTitle;
496 if(!tag.m_premiered.IsValid() && tag.m_iYear)
497 container->m_Date = NPT_String::FromInteger(tag.m_iYear) + "-01-01";
499 container->m_Date = tag.m_premiered.GetAsDBDate();
501 for (unsigned int index = 0; index < tag.m_genre.size(); index++)
502 container->m_Affiliation.genres.Add(tag.m_genre.at(index).c_str());
504 for(CVideoInfoTag::iCast it = tag.m_cast.begin();it != tag.m_cast.end();it++) {
505 container->m_People.actors.Add(it->strName.c_str(), it->strRole.c_str());
508 for (unsigned int index = 0; index < tag.m_director.size(); index++)
509 container->m_People.directors.Add(tag.m_director[index].c_str());
510 for (unsigned int index = 0; index < tag.m_writingCredits.size(); index++)
511 container->m_People.authors.Add(tag.m_writingCredits[index].c_str());
513 container->m_Description.description = tag.m_strTagLine;
514 container->m_Description.long_description = tag.m_strPlot;
518 container->m_ObjectClass.type += ".storageFolder";
521 } else if (item.IsPlayList() || item.IsSmartPlayList()) {
522 container->m_ObjectClass.type += ".playlistContainer";
525 if(quirks & ECLIENTQUIRKS_ONLYSTORAGEFOLDER) {
526 container->m_ObjectClass.type = "object.container.storageFolder";
529 /* Get the number of children for this container */
530 if (with_count && upnp_server) {
531 if (object->m_ObjectID.StartsWith("virtualpath://")) {
532 NPT_LargeSize count = 0;
533 NPT_CHECK_LABEL(NPT_File::GetSize(file_path, count), failure);
534 container->m_ChildrenCount = (NPT_Int32)count;
536 /* this should be a standard path */
537 // TODO - get file count of this directory
542 // set a title for the object
543 if (object->m_Title.IsEmpty()) {
544 if (!item.GetLabel().IsEmpty()) {
545 CStdString title = item.GetLabel();
546 if (item.IsPlayList() || !item.m_bIsFolder) URIUtils::RemoveExtension(title);
547 object->m_Title = title;
551 // determine the correct artwork for this item
552 if (!thumb_loader.IsNull())
553 thumb_loader->LoadItem(&item);
555 // finally apply the found artwork
556 thumb = item.GetArt("thumb");
557 if (upnp_server && !thumb.empty()) {
558 PLT_AlbumArtInfo art;
559 art.uri = upnp_server->BuildSafeResourceUri(
561 (*ips.GetFirstItem()).ToString(),
562 CTextureUtils::GetWrappedImageURL(thumb).c_str());
564 // Set DLNA profileID by extension, defaulting to JPEG.
565 if (URIUtils::HasExtension(thumb, ".png")) {
566 art.dlna_profile = "PNG_TN";
568 art.dlna_profile = "JPEG_TN";
570 object->m_ExtraInfo.album_arts.Add(art);
573 fanart = item.GetArt("fanart");
574 if (upnp_server && !fanart.empty())
575 upnp_server->AddSafeResourceUri(object, rooturi, ips, CTextureUtils::GetWrappedImageURL(fanart), "xbmc.org:*:fanart:*");
584 /*----------------------------------------------------------------------
585 | CUPnPServer::CorrectAllItemsSortHack
586 +---------------------------------------------------------------------*/
588 CorrectAllItemsSortHack(const CStdString &item)
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)
594 if ((item.size() == 1 && item[0] == 0x01) || (item.size() > 1 && ((unsigned char) item[1]) == 0xff))
595 return StringUtils::EmptyString;
601 PopulateTagFromObject(CMusicInfoTag& tag,
602 PLT_MediaObject& object,
603 PLT_MediaItemResource* resource /* = NULL */)
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);
612 tag.SetTrackNumber(object.m_MiscInfo.original_track_number);
614 for (NPT_List<NPT_String>::Iterator it = object.m_Affiliation.genres.GetFirstItem(); it; it++)
615 tag.SetGenre((const char*) *it);
617 tag.SetAlbum((const char*)object.m_Affiliation.album);
619 last.SetFromDateString((const char*)object.m_MiscInfo.last_time);
620 tag.SetLastPlayed(last);
621 tag.SetPlayCount(object.m_MiscInfo.play_count);
623 tag.SetDuration(resource->m_Duration);
629 PopulateTagFromObject(CVideoInfoTag& tag,
630 PLT_MediaObject& object,
631 PLT_MediaItemResource* resource /* = NULL */)
634 date.SetFromDateString((const char*)object.m_Date);
636 if(!object.m_Recorded.program_title.IsEmpty())
638 tag.m_type = "episode";
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;
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;
651 tag.m_firstAired = date;
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;
660 else if(object.m_ObjectClass.type == "object.item.videoItem.musicVideoClip") {
661 tag.m_type = "musicvideo";
665 tag.m_type = "movie";
666 tag.m_strTitle = object.m_Title;
667 tag.m_premiered = date;
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;
685 if (resource->m_Duration)
686 tag.m_duration = resource->m_Duration;
687 if (object.m_MiscInfo.last_position > 0 )
689 tag.m_resumePoint.totalTimeInSeconds = resource->m_Duration;
690 tag.m_resumePoint.timeInSeconds = object.m_MiscInfo.last_position;
696 CFileItemPtr BuildObject(PLT_MediaObject* entry)
698 NPT_String ObjectClass = entry->m_ObjectClass.type.ToLowercase();
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();
705 // if it's a container, format a string as upnp://uuid/object_id
706 if (pItem->m_bIsFolder) {
709 if( ObjectClass.StartsWith("object.container.album.videoalbum") ) {
710 pItem->SetLabelPreformated(false);
711 UPNP::PopulateTagFromObject(*pItem->GetVideoInfoTag(), *entry, NULL);
713 } else if( ObjectClass.StartsWith("object.container.album.photoalbum")) {
714 //CPictureInfoTag* tag = pItem->GetPictureInfoTag();
716 } else if( ObjectClass.StartsWith("object.container.album") ) {
717 pItem->SetLabelPreformated(false);
718 UPNP::PopulateTagFromObject(*pItem->GetMusicInfoTag(), *entry, NULL);
725 // set a general content type
726 const char* content = NULL;
727 if (ObjectClass.StartsWith("object.item.videoitem")) {
728 pItem->SetMimeType("video/octet-stream");
732 else if(ObjectClass.StartsWith("object.item.audioitem")) {
733 pItem->SetMimeType("audio/octet-stream");
737 else if(ObjectClass.StartsWith("object.item.imageitem")) {
738 pItem->SetMimeType("image/octet-stream");
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))) {
749 if (resource.m_Size != (NPT_LargeSize)-1) {
750 pItem->m_dwSize = resource.m_Size;
756 pItem->SetLabelPreformated(false);
757 UPNP::PopulateTagFromObject(*pItem->GetVideoInfoTag(), *entry, res);
760 pItem->SetLabelPreformated(false);
761 UPNP::PopulateTagFromObject(*pItem->GetMusicInfoTag(), *entry, res);
764 //CPictureInfoTag* tag = pItem->GetPictureInfoTag();
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;
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);
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);
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;
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);
806 else if (type == "episode" || type == "movie")
807 watched = (played > 0);
808 pItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, watched);
813 struct ResourcePrioritySort
815 ResourcePrioritySort(const PLT_MediaObject* entry)
817 if (entry->m_ObjectClass.type.StartsWith("object.item.audioItem"))
819 else if (entry->m_ObjectClass.type.StartsWith("object.item.imageItem"))
821 else if (entry->m_ObjectClass.type.StartsWith("object.item.videoItem"))
825 int GetPriority(const PLT_MediaItemResource& res) const
829 if (m_content != "" && res.m_ProtocolInfo.GetContentType().StartsWith(m_content))
832 NPT_Url url(res.m_Uri);
833 if (URIUtils::IsHostOnLAN((const char*)url.GetHost(), false))
836 if (res.m_ProtocolInfo.GetProtocol() == "xbmc-get")
838 else if (res.m_ProtocolInfo.GetProtocol() == "http-get")
844 int operator()(const PLT_MediaItemResource& lh, const PLT_MediaItemResource& rh) const
846 if(GetPriority(lh) < GetPriority(rh))
852 NPT_String m_content;
855 bool GetResource(const PLT_MediaObject* entry, CFileItem& item)
857 PLT_MediaItemResource resource;
859 // store original path so we remember it
860 item.SetProperty("original_listitem_url", item.GetPath());
861 item.SetProperty("original_listitem_mime", item.GetMimeType());
863 // get a sorted list based on our preference
864 NPT_List<PLT_MediaItemResource> sorted;
865 for (NPT_Cardinal i = 0; i < entry->m_Resources.GetItemCount(); ++i) {
866 sorted.Add(entry->m_Resources[i]);
868 sorted.Sort(ResourcePrioritySort(entry));
870 if(sorted.GetItemCount() == 0)
873 resource = *sorted.GetFirstItem();
875 // if it's an item, path is the first url to the item
876 // we hope the server made the first one reachable for us
877 // (it could be a format we dont know how to play however)
878 item.SetPath((const char*) resource.m_Uri);
880 // look for content type in protocol info
881 if (resource.m_ProtocolInfo.IsValid()) {
882 CLog::Log(LOGDEBUG, "CUPnPDirectory::GetResource - resource protocol info '%s'",
883 (const char*)(resource.m_ProtocolInfo.ToString()));
885 if (resource.m_ProtocolInfo.GetContentType().Compare("application/octet-stream") != 0) {
886 item.SetMimeType((const char*)resource.m_ProtocolInfo.GetContentType());
889 CLog::Log(LOGERROR, "CUPnPDirectory::GetResource - invalid protocol info '%s'",
890 (const char*)(resource.m_ProtocolInfo.ToString()));
893 // look for subtitles
895 for(unsigned r = 0; r < entry->m_Resources.GetItemCount(); r++)
897 const PLT_MediaItemResource& res = entry->m_Resources[r];
898 const PLT_ProtocolInfo& info = res.m_ProtocolInfo;
899 static const char* allowed[] = { "text/srt"
903 for(unsigned type = 0; type < sizeof(allowed)/sizeof(allowed[0]); type++)
905 if(info.Match(PLT_ProtocolInfo("*", "*", allowed[type], "*")))
908 prop.Format("upnp:subtitle:%d", ++subs);
909 item.SetProperty(prop, (const char*)res.m_Uri);
917 CFileItemPtr GetFileItem(const NPT_String& uri, const NPT_String& meta)
919 PLT_MediaObjectListReference list;
920 PLT_MediaObject* object = NULL;
923 if (NPT_SUCCEEDED(PLT_Didl::FromDidl(meta, list))) {
924 list->Get(0, object);
928 item = BuildObject(object);
932 item->SetPath((const char*)uri);
933 GetResource(object, *item);
935 item.reset(new CFileItem((const char*)uri, false));
940 } /* namespace UPNP */