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 "TextureCache.h"
40 #include "ThumbLoader.h"
42 using namespace MUSIC_INFO;
43 using namespace XFILE;
48 /*----------------------------------------------------------------------
50 +---------------------------------------------------------------------*/
51 EClientQuirks GetClientQuirks(const PLT_HttpRequestContext* context)
54 return ECLIENTQUIRKS_NONE;
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);
61 if (user_agent->Find("XBox", 0, true) >= 0 ||
62 user_agent->Find("Xenon", 0, true) >= 0)
63 quirks |= ECLIENTQUIRKS_ONLYSTORAGEFOLDER | ECLIENTQUIRKS_BASICVIDEOCLASS;
65 if (user_agent->Find("Windows-Media-Player", 0, true) >= 0)
66 quirks |= ECLIENTQUIRKS_UNKNOWNSERIES;
70 if (server->Find("Xbox", 0, true) >= 0)
71 quirks |= ECLIENTQUIRKS_ONLYSTORAGEFOLDER | ECLIENTQUIRKS_BASICVIDEOCLASS;
74 return (EClientQuirks)quirks;
77 /*----------------------------------------------------------------------
79 +---------------------------------------------------------------------*/
81 GetMimeType(const char* filename,
82 const PLT_HttpRequestContext* context /* = NULL */)
84 NPT_String ext = URIUtils::GetExtension(filename).c_str();
86 ext = ext.ToLowercase();
88 return PLT_MimeType::GetMimeTypeFromExtension(ext, context);
91 /*----------------------------------------------------------------------
93 +---------------------------------------------------------------------*/
95 GetMimeType(const CFileItem& item,
96 const PLT_HttpRequestContext* context /* = NULL */)
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();
105 if (URIUtils::IsStack(path))
106 path = XFILE::CStackDirectory::GetFirstStackedFile(path);
108 NPT_String ext = URIUtils::GetExtension(path).c_str();
110 ext = ext.ToLowercase();
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 = "";
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 = "";
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;
138 /* nothing we can figure out */
139 if (mime.IsEmpty()) {
140 mime = "application/octet-stream";
146 /*----------------------------------------------------------------------
148 +---------------------------------------------------------------------*/
150 GetProtocolInfo(const CFileItem& item,
151 const char* protocol,
152 const PLT_HttpRequestContext* context /* = NULL */)
154 NPT_String proto = protocol;
156 /* fixup the protocol just in case nothing was passed */
157 if (proto.IsEmpty()) {
158 proto = item.GetAsUrl().GetProtocol();
162 map protocol to right prefix and use xbmc-get for
163 unsupported UPnP protocols for other xbmc clients
166 if (proto == "http") {
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);
178 /*----------------------------------------------------------------------
180 +---------------------------------------------------------------------*/
181 CResourceFinder::CResourceFinder(const char* protocol, const char* content)
182 : m_Protocol(protocol)
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);
191 return ((resource.m_ProtocolInfo.GetProtocol().Compare(m_Protocol, true) == 0)
192 && resource.m_ProtocolInfo.GetContentType().StartsWith(m_Content, true));
195 /*----------------------------------------------------------------------
196 | PopulateObjectFromTag
197 +---------------------------------------------------------------------*/
199 PopulateObjectFromTag(CMusicInfoTag& tag,
200 PLT_MediaObject& object,
201 NPT_String* file_path, /* = NULL */
202 PLT_MediaItemResource* resource, /* = NULL */
203 EClientQuirks quirks)
205 if (!tag.GetURL().IsEmpty() && file_path)
206 *file_path = tag.GetURL();
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++)
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");
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);
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://songs/%i%s", tag.GetDatabaseId(), URIUtils::GetExtension(tag.GetURL()).c_str());
227 if (object.m_ReferenceID == object.m_ObjectID)
228 object.m_ReferenceID = "";
230 object.m_MiscInfo.last_time = tag.GetLastPlayed().GetAsDBDate();
231 object.m_MiscInfo.play_count = tag.GetPlayCount();
233 if (resource) resource->m_Duration = tag.GetDuration();
238 /*----------------------------------------------------------------------
239 | PopulateObjectFromTag
240 +---------------------------------------------------------------------*/
242 PopulateObjectFromTag(CVideoInfoTag& tag,
243 PLT_MediaObject& object,
244 NPT_String* file_path, /* = NULL */
245 PLT_MediaItemResource* resource, /* = NULL */
246 EClientQuirks quirks)
248 // some usefull buffers
249 CStdStringArray strings;
251 if (!tag.m_strFileNameAndPath.IsEmpty() && file_path)
252 *file_path = tag.m_strFileNameAndPath;
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://musicvideos/titles/%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://movies/titles/%i", tag.m_iDbId);
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://tvshows/0/%i", tag.m_iDbId);
280 if(quirks & ECLIENTQUIRKS_BASICVIDEOCLASS)
281 object.m_ObjectClass.type = "object.item.videoItem";
283 if(object.m_ReferenceID == object.m_ObjectID)
284 object.m_ReferenceID = "";
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());
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());
293 for (unsigned int index = 0; index < tag.m_director.size(); index++)
294 object.m_People.directors.Add(tag.m_director[index].c_str());
296 for (unsigned int index = 0; index < tag.m_writingCredits.size(); index++)
297 object.m_People.authors.Add(tag.m_writingCredits[index].c_str());
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;
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());
316 /*----------------------------------------------------------------------
318 +---------------------------------------------------------------------*/
320 BuildObject(CFileItem& item,
321 NPT_String& file_path,
323 NPT_Reference<CThumbLoader>& thumb_loader,
324 const PLT_HttpRequestContext* context /* = NULL */,
325 CUPnPServer* upnp_server /* = NULL */)
327 PLT_MediaItemResource resource;
328 PLT_MediaObject* object = NULL;
329 std::string thumb, fanart;
331 CLog::Log(LOGDEBUG, "UPnP: Building didl for object '%s'", (const char*)item.GetPath());
333 EClientQuirks quirks = GetClientQuirks(context);
335 // get list of ip addresses
336 NPT_List<NPT_IpAddress> ips;
338 NPT_CHECK_LABEL(PLT_UPnPMessageHelper::GetIPAddresses(ips), failure);
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(), "/");
350 if (!item.m_bIsFolder) {
351 object = new PLT_MediaItem();
352 object->m_ObjectID = item.GetPath();
354 /* Setup object type */
355 if (item.IsMusicDb() || item.IsAudio()) {
356 object->m_ObjectClass.type = "object.item.audioItem.musicTrack";
358 if (item.HasMusicInfoTag()) {
359 CMusicInfoTag *tag = (CMusicInfoTag*)item.GetMusicInfoTag();
360 PopulateObjectFromTag(*tag, *object, &file_path, &resource, quirks);
362 } else if (item.IsVideoDb() || item.IsVideo()) {
363 object->m_ObjectClass.type = "object.item.videoItem";
365 if(quirks & ECLIENTQUIRKS_UNKNOWNSERIES)
366 object->m_Affiliation.album = "[Unknown Series]";
368 if (item.HasVideoInfoTag()) {
369 CVideoInfoTag *tag = (CVideoInfoTag*)item.GetVideoInfoTag();
370 PopulateObjectFromTag(*tag, *object, &file_path, &resource, quirks);
372 } else if (item.IsPicture()) {
373 object->m_ObjectClass.type = "object.item.imageItem.photo";
375 object->m_ObjectClass.type = "object.item";
378 // duration of zero is invalid
379 if (resource.m_Duration == 0) resource.m_Duration = -1;
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;
387 if (object->m_Date.IsEmpty() && item.m_dateTime.IsValid()) {
388 object->m_Date = item.m_dateTime.GetAsDBDate();
392 upnp_server->AddSafeResourceUri(object, rooturi, ips, file_path, GetProtocolInfo(item, "http", context));
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;
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);
405 object->m_Resources.Insert(object->m_Resources.GetFirstItem(), resource);
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;
416 // Some upnp clients expect all audio items to have parent root id 4
417 #ifdef WMP_ID_MAPPING
418 object->m_ParentID = "4";
421 PLT_MediaContainer* container = new PLT_MediaContainer;
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;
429 CStdStringArray strings;
431 /* this might be overkill, but hey */
432 if (item.IsMusicDb()) {
433 MUSICDATABASEDIRECTORY::NODE_TYPE node = CMusicDatabaseDirectory::GetDirectoryType(item.GetPath());
435 case MUSICDATABASEDIRECTORY::NODE_TYPE_ARTIST: {
436 container->m_ObjectClass.type += ".person.musicArtist";
437 CMusicInfoTag *tag = (CMusicInfoTag*)item.GetMusicInfoTag();
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");
444 #ifdef WMP_ID_MAPPING
445 // Some upnp clients expect all artists to have parent root id 107
446 container->m_ParentID = "107";
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();
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();
464 #ifdef WMP_ID_MAPPING
465 // Some upnp clients expect all albums to have parent root id 7
466 container->m_ParentID = "7";
470 case MUSICDATABASEDIRECTORY::NODE_TYPE_GENRE:
471 container->m_ObjectClass.type += ".genre.musicGenre";
476 } else if (item.IsVideoDb()) {
477 VIDEODATABASEDIRECTORY::NODE_TYPE node = CVideoDatabaseDirectory::GetDirectoryType(item.GetPath());
478 CVideoInfoTag &tag = *(CVideoInfoTag*)item.GetVideoInfoTag();
480 case VIDEODATABASEDIRECTORY::NODE_TYPE_GENRE:
481 container->m_ObjectClass.type += ".genre.movieGenre";
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;
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";
498 container->m_Date = tag.m_premiered.GetAsDBDate();
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());
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());
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());
512 container->m_Description.description = tag.m_strTagLine;
513 container->m_Description.long_description = tag.m_strPlot;
517 container->m_ObjectClass.type += ".storageFolder";
520 } else if (item.IsPlayList() || item.IsSmartPlayList()) {
521 container->m_ObjectClass.type += ".playlistContainer";
524 if(quirks & ECLIENTQUIRKS_ONLYSTORAGEFOLDER) {
525 container->m_ObjectClass.type = "object.container.storageFolder";
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;
535 /* this should be a standard path */
536 // TODO - get file count of this directory
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;
550 // determine the correct artwork for this item
551 if (!thumb_loader.IsNull())
552 thumb_loader->LoadItem(&item);
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(
560 (*ips.GetFirstItem()).ToString(),
561 CTextureCache::GetWrappedImageURL(thumb).c_str());
563 // Set DLNA profileID by extension, defaulting to JPEG.
564 if (URIUtils::HasExtension(thumb, ".png")) {
565 art.dlna_profile = "PNG_TN";
567 art.dlna_profile = "JPEG_TN";
569 object->m_ExtraInfo.album_arts.Add(art);
572 fanart = item.GetArt("fanart");
573 if (upnp_server && !fanart.empty())
574 upnp_server->AddSafeResourceUri(object, rooturi, ips, CTextureCache::GetWrappedImageURL(fanart), "xbmc.org:*:fanart:*");
583 /*----------------------------------------------------------------------
584 | CUPnPServer::CorrectAllItemsSortHack
585 +---------------------------------------------------------------------*/
587 CorrectAllItemsSortHack(const CStdString &item)
589 // This is required as in order for the "* All Albums" etc. items to sort
590 // correctly, they must have fake artist/album etc. information generated.
591 // This looks nasty if we attempt to render it to the GUI, thus this (further)
593 if ((item.size() == 1 && item[0] == 0x01) || (item.size() > 1 && ((unsigned char) item[1]) == 0xff))
594 return StringUtils::EmptyString;
600 PopulateTagFromObject(CMusicInfoTag& tag,
601 PLT_MediaObject& object,
602 PLT_MediaItemResource* resource /* = NULL */)
604 tag.SetTitle((const char*)object.m_Title);
605 tag.SetArtist((const char*)object.m_Creator);
606 for(PLT_PersonRoles::Iterator it = object.m_People.artists.GetFirstItem(); it; it++) {
607 if (it->role == "") tag.SetArtist((const char*)it->name);
608 else if(it->role == "Performer") tag.SetArtist((const char*)it->name);
609 else if(it->role == "AlbumArtist") tag.SetAlbumArtist((const char*)it->name);
611 tag.SetTrackNumber(object.m_MiscInfo.original_track_number);
613 for (NPT_List<NPT_String>::Iterator it = object.m_Affiliation.genres.GetFirstItem(); it; it++)
614 tag.SetGenre((const char*) *it);
616 tag.SetAlbum((const char*)object.m_Affiliation.album);
618 last.SetFromDateString((const char*)object.m_MiscInfo.last_time);
619 tag.SetLastPlayed(last);
620 tag.SetPlayCount(object.m_MiscInfo.play_count);
622 tag.SetDuration(resource->m_Duration);
628 PopulateTagFromObject(CVideoInfoTag& tag,
629 PLT_MediaObject& object,
630 PLT_MediaItemResource* resource /* = NULL */)
633 date.SetFromDateString((const char*)object.m_Date);
635 if(!object.m_Recorded.program_title.IsEmpty())
637 tag.m_type = "episode";
640 int title = object.m_Recorded.program_title.Find(" : ");
641 if(sscanf(object.m_Recorded.program_title, "S%2dE%2d", &season, &episode) == 2 && title >= 0) {
642 tag.m_strTitle = object.m_Recorded.program_title.SubString(title + 3);
643 tag.m_iEpisode = episode;
644 tag.m_iSeason = season;
646 tag.m_strTitle = object.m_Recorded.program_title;
647 tag.m_iSeason = object.m_Recorded.episode_number / 100;
648 tag.m_iEpisode = object.m_Recorded.episode_number % 100;
650 tag.m_firstAired = date;
652 else if (!object.m_Recorded.series_title.IsEmpty()) {
653 tag.m_type= "season";
654 tag.m_strTitle = object.m_Title; // because could be TV show Title, or Season 1 etc
655 tag.m_iSeason = object.m_Recorded.episode_number / 100;
656 tag.m_iEpisode = object.m_Recorded.episode_number % 100;
657 tag.m_premiered = date;
659 else if(object.m_ObjectClass.type == "object.item.videoItem.musicVideoClip") {
660 tag.m_type = "musicvideo";
664 tag.m_type = "movie";
665 tag.m_strTitle = object.m_Title;
666 tag.m_premiered = date;
668 tag.m_iYear = date.GetYear();
669 for (unsigned int index = 0; index < object.m_Affiliation.genres.GetItemCount(); index++)
670 tag.m_genre.push_back(object.m_Affiliation.genres.GetItem(index)->GetChars());
671 for (unsigned int index = 0; index < object.m_People.directors.GetItemCount(); index++)
672 tag.m_director.push_back(object.m_People.directors.GetItem(index)->name.GetChars());
673 for (unsigned int index = 0; index < object.m_People.authors.GetItemCount(); index++)
674 tag.m_writingCredits.push_back(object.m_People.authors.GetItem(index)->name.GetChars());
675 tag.m_strTagLine = object.m_Description.description;
676 tag.m_strPlot = object.m_Description.long_description;
677 tag.m_strMPAARating = object.m_Description.rating;
678 tag.m_strShowTitle = object.m_Recorded.series_title;
679 tag.m_lastPlayed.SetFromDateString((const char*)object.m_MiscInfo.last_time);
680 tag.m_playCount = object.m_MiscInfo.play_count;
684 if (resource->m_Duration)
685 tag.m_duration = resource->m_Duration;
686 if (object.m_MiscInfo.last_position > 0 )
688 tag.m_resumePoint.totalTimeInSeconds = resource->m_Duration;
689 tag.m_resumePoint.timeInSeconds = object.m_MiscInfo.last_position;
695 CFileItemPtr BuildObject(PLT_MediaObject* entry)
697 NPT_String ObjectClass = entry->m_ObjectClass.type.ToLowercase();
699 CFileItemPtr pItem(new CFileItem((const char*)entry->m_Title));
700 pItem->SetLabelPreformated(true);
701 pItem->m_strTitle = (const char*)entry->m_Title;
702 pItem->m_bIsFolder = entry->IsContainer();
704 // if it's a container, format a string as upnp://uuid/object_id
705 if (pItem->m_bIsFolder) {
708 if( ObjectClass.StartsWith("object.container.album.videoalbum") ) {
709 pItem->SetLabelPreformated(false);
710 UPNP::PopulateTagFromObject(*pItem->GetVideoInfoTag(), *entry, NULL);
712 } else if( ObjectClass.StartsWith("object.container.album.photoalbum")) {
713 //CPictureInfoTag* tag = pItem->GetPictureInfoTag();
715 } else if( ObjectClass.StartsWith("object.container.album") ) {
716 pItem->SetLabelPreformated(false);
717 UPNP::PopulateTagFromObject(*pItem->GetMusicInfoTag(), *entry, NULL);
724 // set a general content type
725 const char* content = NULL;
726 if (ObjectClass.StartsWith("object.item.videoitem")) {
727 pItem->SetMimeType("video/octet-stream");
731 else if(ObjectClass.StartsWith("object.item.audioitem")) {
732 pItem->SetMimeType("audio/octet-stream");
736 else if(ObjectClass.StartsWith("object.item.imageitem")) {
737 pItem->SetMimeType("image/octet-stream");
742 // attempt to find a valid resource (may be multiple)
743 PLT_MediaItemResource resource, *res = NULL;
744 if(NPT_SUCCEEDED(NPT_ContainerFind(entry->m_Resources,
745 CResourceFinder("http-get", content), resource))) {
748 if (resource.m_Size != (NPT_LargeSize)-1) {
749 pItem->m_dwSize = resource.m_Size;
755 pItem->SetLabelPreformated(false);
756 UPNP::PopulateTagFromObject(*pItem->GetVideoInfoTag(), *entry, res);
759 pItem->SetLabelPreformated(false);
760 UPNP::PopulateTagFromObject(*pItem->GetMusicInfoTag(), *entry, res);
763 //CPictureInfoTag* tag = pItem->GetPictureInfoTag();
769 if(entry->m_Description.date.GetLength()) {
770 SYSTEMTIME time = {};
771 sscanf(entry->m_Description.date, "%hu-%hu-%huT%hu:%hu:%hu",
772 &time.wYear, &time.wMonth, &time.wDay, &time.wHour, &time.wMinute, &time.wSecond);
773 pItem->m_dateTime = time;
776 // if there is a thumbnail available set it here
777 if(entry->m_ExtraInfo.album_arts.GetItem(0))
778 // only considers first album art
779 pItem->SetArt("thumb", (const char*) entry->m_ExtraInfo.album_arts.GetItem(0)->uri);
780 else if(entry->m_Description.icon_uri.GetLength())
781 pItem->SetArt("thumb", (const char*) entry->m_Description.icon_uri);
783 PLT_ProtocolInfo fanart_mask("xbmc.org", "*", "fanart", "*");
784 for(unsigned i = 0; i < entry->m_Resources.GetItemCount(); ++i) {
785 PLT_MediaItemResource& res = entry->m_Resources[i];
786 if(res.m_ProtocolInfo.Match(fanart_mask)) {
787 pItem->SetArt("fanart", (const char*)res.m_Uri);
791 // set the watched overlay, as this will not be set later due to
792 // content set on file item list
793 if (pItem->HasVideoInfoTag()) {
794 int episodes = pItem->GetVideoInfoTag()->m_iEpisode;
795 int played = pItem->GetVideoInfoTag()->m_playCount;
796 const std::string& type = pItem->GetVideoInfoTag()->m_type;
798 if (type == "tvshow" || type == "season") {
799 pItem->SetProperty("totalepisodes", episodes);
800 pItem->SetProperty("numepisodes", episodes);
801 pItem->SetProperty("watchedepisodes", played);
802 pItem->SetProperty("unwatchedepisodes", episodes - played);
803 watched = (episodes && played == episodes);
805 else if (type == "episode" || type == "movie")
806 watched = (played > 0);
807 pItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, watched);
812 bool GetResource(const PLT_MediaObject* entry, CFileItem& item)
814 PLT_MediaItemResource resource;
816 // store original path so we remember it
817 item.SetProperty("original_listitem_url", item.GetPath());
818 item.SetProperty("original_listitem_mime", item.GetMimeType(false));
820 // look for a resource with "xbmc-get" protocol
821 // if we can't find one, try to find a valid resource
822 if(NPT_FAILED(NPT_ContainerFind(entry->m_Resources,
823 CResourceFinder("xbmc-get"), resource))) {
824 const char* content = NULL;
825 if (entry->m_ObjectClass.type.StartsWith("object.item.audioItem"))
827 else if (entry->m_ObjectClass.type.StartsWith("object.item.imageItem"))
829 else if (entry->m_ObjectClass.type.StartsWith("object.item.videoItem"))
832 if(NPT_FAILED(NPT_ContainerFind(entry->m_Resources,
833 CResourceFinder("http-get", content), resource))) {
834 if(entry->m_Resources.GetItemCount()) {
835 // last attempt to find something suitable
836 resource = entry->m_Resources[0];
839 CLog::Log(LOGERROR, "CUPnPDirectory::GetResource - no resources available for object %s", (const char*)entry->m_ObjectID);
845 // if it's an item, path is the first url to the item
846 // we hope the server made the first one reachable for us
847 // (it could be a format we dont know how to play however)
848 item.SetPath((const char*) resource.m_Uri);
850 // look for content type in protocol info
851 if (resource.m_ProtocolInfo.IsValid()) {
852 CLog::Log(LOGDEBUG, "CUPnPDirectory::GetResource - resource protocol info '%s'",
853 (const char*)(resource.m_ProtocolInfo.ToString()));
855 if (resource.m_ProtocolInfo.GetContentType().Compare("application/octet-stream") != 0) {
856 item.SetMimeType((const char*)resource.m_ProtocolInfo.GetContentType());
859 CLog::Log(LOGERROR, "CUPnPDirectory::GetResource - invalid protocol info '%s'",
860 (const char*)(resource.m_ProtocolInfo.ToString()));
863 // look for subtitles
865 for(unsigned r = 0; r < entry->m_Resources.GetItemCount(); r++)
867 const PLT_MediaItemResource& res = entry->m_Resources[r];
868 const PLT_ProtocolInfo& info = res.m_ProtocolInfo;
869 static const char* allowed[] = { "text/srt"
873 for(unsigned type = 0; type < sizeof(allowed)/sizeof(allowed[0]); type++)
875 if(info.Match(PLT_ProtocolInfo("*", "*", allowed[type], "*")))
878 prop.Format("upnp:subtitle:%d", ++subs);
879 item.SetProperty(prop, (const char*)res.m_Uri);
887 CFileItemPtr GetFileItem(const NPT_String& uri, const NPT_String& meta)
889 PLT_MediaObjectListReference list;
890 PLT_MediaObject* object = NULL;
893 if (NPT_SUCCEEDED(PLT_Didl::FromDidl(meta, list))) {
894 list->Get(0, object);
898 item = BuildObject(object);
902 item->SetPath((const char*)uri);
903 GetResource(object, *item);
905 item.reset(new CFileItem((const char*)uri, false));
910 } /* namespace UPNP */