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 /*----------------------------------------------------------------------
79 | GetMediaControllerQuirks
80 +---------------------------------------------------------------------*/
81 EMediaControllerQuirks GetMediaControllerQuirks(const PLT_DeviceData *device)
84 return EMEDIACONTROLLERQUIRKS_NONE;
86 unsigned int quirks = 0;
88 if (device->m_Manufacturer.Find("Samsung Electronics") >= 0)
89 quirks |= EMEDIACONTROLLERQUIRKS_X_MKV;
91 return (EMediaControllerQuirks)quirks;
94 /*----------------------------------------------------------------------
96 +---------------------------------------------------------------------*/
98 GetMimeType(const char* filename,
99 const PLT_HttpRequestContext* context /* = NULL */)
101 NPT_String ext = URIUtils::GetExtension(filename).c_str();
103 ext = ext.ToLowercase();
105 return PLT_MimeType::GetMimeTypeFromExtension(ext, context);
108 /*----------------------------------------------------------------------
110 +---------------------------------------------------------------------*/
112 GetMimeType(const CFileItem& item,
113 const PLT_HttpRequestContext* context /* = NULL */)
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();
122 if (URIUtils::IsStack(path))
123 path = XFILE::CStackDirectory::GetFirstStackedFile(path);
125 NPT_String ext = URIUtils::GetExtension(path).c_str();
127 ext = ext.ToLowercase();
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 = "";
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 = "";
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;
155 /* nothing we can figure out */
156 if (mime.IsEmpty()) {
157 mime = "application/octet-stream";
163 /*----------------------------------------------------------------------
165 +---------------------------------------------------------------------*/
167 GetProtocolInfo(const CFileItem& item,
168 const char* protocol,
169 const PLT_HttpRequestContext* context /* = NULL */)
171 NPT_String proto = protocol;
173 /* fixup the protocol just in case nothing was passed */
174 if (proto.IsEmpty()) {
175 proto = item.GetAsUrl().GetProtocol();
179 map protocol to right prefix and use xbmc-get for
180 unsupported UPnP protocols for other xbmc clients
183 if (proto == "http") {
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);
195 /*----------------------------------------------------------------------
197 +---------------------------------------------------------------------*/
198 CResourceFinder::CResourceFinder(const char* protocol, const char* content)
199 : m_Protocol(protocol)
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);
208 return ((resource.m_ProtocolInfo.GetProtocol().Compare(m_Protocol, true) == 0)
209 && resource.m_ProtocolInfo.GetContentType().StartsWith(m_Content, true));
212 /*----------------------------------------------------------------------
213 | PopulateObjectFromTag
214 +---------------------------------------------------------------------*/
216 PopulateObjectFromTag(CMusicInfoTag& tag,
217 PLT_MediaObject& object,
218 NPT_String* file_path, /* = NULL */
219 PLT_MediaItemResource* resource, /* = NULL */
220 EClientQuirks quirks)
222 if (!tag.GetURL().empty() && file_path)
223 *file_path = tag.GetURL();
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++)
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");
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);
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());
244 if (object.m_ReferenceID == object.m_ObjectID)
245 object.m_ReferenceID = "";
247 object.m_MiscInfo.last_time = tag.GetLastPlayed().GetAsDBDate();
248 object.m_MiscInfo.play_count = tag.GetPlayCount();
250 if (resource) resource->m_Duration = tag.GetDuration();
255 /*----------------------------------------------------------------------
256 | PopulateObjectFromTag
257 +---------------------------------------------------------------------*/
259 PopulateObjectFromTag(CVideoInfoTag& tag,
260 PLT_MediaObject& object,
261 NPT_String* file_path, /* = NULL */
262 PLT_MediaItemResource* resource, /* = NULL */
263 EClientQuirks quirks)
265 // some usefull buffers
266 CStdStringArray strings;
268 if (!tag.m_strFileNameAndPath.empty() && file_path)
269 *file_path = tag.m_strFileNameAndPath;
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);
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);
297 if(quirks & ECLIENTQUIRKS_BASICVIDEOCLASS)
298 object.m_ObjectClass.type = "object.item.videoItem";
300 if(object.m_ReferenceID == object.m_ObjectID)
301 object.m_ReferenceID = "";
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());
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());
310 for (unsigned int index = 0; index < tag.m_director.size(); index++)
311 object.m_People.directors.Add(tag.m_director[index].c_str());
313 for (unsigned int index = 0; index < tag.m_writingCredits.size(); index++)
314 object.m_People.authors.Add(tag.m_writingCredits[index].c_str());
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;
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());
333 /*----------------------------------------------------------------------
335 +---------------------------------------------------------------------*/
337 BuildObject(CFileItem& item,
338 NPT_String& file_path,
340 NPT_Reference<CThumbLoader>& thumb_loader,
341 const PLT_HttpRequestContext* context /* = NULL */,
342 CUPnPServer* upnp_server /* = NULL */)
344 PLT_MediaItemResource resource;
345 PLT_MediaObject* object = NULL;
346 std::string thumb, fanart;
348 CLog::Log(LOGDEBUG, "UPnP: Building didl for object '%s'", (const char*)item.GetPath());
350 EClientQuirks quirks = GetClientQuirks(context);
352 // get list of ip addresses
353 NPT_List<NPT_IpAddress> ips;
355 NPT_CHECK_LABEL(PLT_UPnPMessageHelper::GetIPAddresses(ips), failure);
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(), "/");
367 if (!item.m_bIsFolder) {
368 object = new PLT_MediaItem();
369 object->m_ObjectID = item.GetPath();
371 /* Setup object type */
372 if (item.IsMusicDb() || item.IsAudio()) {
373 object->m_ObjectClass.type = "object.item.audioItem.musicTrack";
375 if (item.HasMusicInfoTag()) {
376 CMusicInfoTag *tag = (CMusicInfoTag*)item.GetMusicInfoTag();
377 PopulateObjectFromTag(*tag, *object, &file_path, &resource, quirks);
379 } else if (item.IsVideoDb() || item.IsVideo()) {
380 object->m_ObjectClass.type = "object.item.videoItem";
382 if(quirks & ECLIENTQUIRKS_UNKNOWNSERIES)
383 object->m_Affiliation.album = "[Unknown Series]";
385 if (item.HasVideoInfoTag()) {
386 CVideoInfoTag *tag = (CVideoInfoTag*)item.GetVideoInfoTag();
387 PopulateObjectFromTag(*tag, *object, &file_path, &resource, quirks);
389 } else if (item.IsPicture()) {
390 object->m_ObjectClass.type = "object.item.imageItem.photo";
392 object->m_ObjectClass.type = "object.item";
395 // duration of zero is invalid
396 if (resource.m_Duration == 0) resource.m_Duration = -1;
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;
404 if (object->m_Date.IsEmpty() && item.m_dateTime.IsValid()) {
405 object->m_Date = item.m_dateTime.GetAsDBDate();
409 upnp_server->AddSafeResourceUri(object, rooturi, ips, file_path, GetProtocolInfo(item, "http", context));
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;
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);
422 object->m_Resources.Insert(object->m_Resources.GetFirstItem(), resource);
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;
433 // Some upnp clients expect all audio items to have parent root id 4
434 #ifdef WMP_ID_MAPPING
435 object->m_ParentID = "4";
438 PLT_MediaContainer* container = new PLT_MediaContainer;
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;
446 CStdStringArray strings;
448 /* this might be overkill, but hey */
449 if (item.IsMusicDb()) {
450 MUSICDATABASEDIRECTORY::NODE_TYPE node = CMusicDatabaseDirectory::GetDirectoryType(item.GetPath());
452 case MUSICDATABASEDIRECTORY::NODE_TYPE_ARTIST: {
453 container->m_ObjectClass.type += ".person.musicArtist";
454 CMusicInfoTag *tag = (CMusicInfoTag*)item.GetMusicInfoTag();
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");
461 #ifdef WMP_ID_MAPPING
462 // Some upnp clients expect all artists to have parent root id 107
463 container->m_ParentID = "107";
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();
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();
481 #ifdef WMP_ID_MAPPING
482 // Some upnp clients expect all albums to have parent root id 7
483 container->m_ParentID = "7";
487 case MUSICDATABASEDIRECTORY::NODE_TYPE_GENRE:
488 container->m_ObjectClass.type += ".genre.musicGenre";
493 } else if (item.IsVideoDb()) {
494 VIDEODATABASEDIRECTORY::NODE_TYPE node = CVideoDatabaseDirectory::GetDirectoryType(item.GetPath());
495 CVideoInfoTag &tag = *(CVideoInfoTag*)item.GetVideoInfoTag();
497 case VIDEODATABASEDIRECTORY::NODE_TYPE_GENRE:
498 container->m_ObjectClass.type += ".genre.movieGenre";
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;
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";
515 container->m_Date = tag.m_premiered.GetAsDBDate();
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());
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());
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());
529 container->m_Description.description = tag.m_strTagLine;
530 container->m_Description.long_description = tag.m_strPlot;
534 container->m_ObjectClass.type += ".storageFolder";
537 } else if (item.IsPlayList() || item.IsSmartPlayList()) {
538 container->m_ObjectClass.type += ".playlistContainer";
541 if(quirks & ECLIENTQUIRKS_ONLYSTORAGEFOLDER) {
542 container->m_ObjectClass.type = "object.container.storageFolder";
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;
552 /* this should be a standard path */
553 // TODO - get file count of this directory
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;
567 // determine the correct artwork for this item
568 if (!thumb_loader.IsNull())
569 thumb_loader->LoadItem(&item);
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(
577 (*ips.GetFirstItem()).ToString(),
578 CTextureUtils::GetWrappedImageURL(thumb).c_str());
580 // Set DLNA profileID by extension, defaulting to JPEG.
581 if (URIUtils::HasExtension(thumb, ".png")) {
582 art.dlna_profile = "PNG_TN";
584 art.dlna_profile = "JPEG_TN";
586 object->m_ExtraInfo.album_arts.Add(art);
589 fanart = item.GetArt("fanart");
590 if (upnp_server && !fanart.empty())
591 upnp_server->AddSafeResourceUri(object, rooturi, ips, CTextureUtils::GetWrappedImageURL(fanart), "xbmc.org:*:fanart:*");
600 /*----------------------------------------------------------------------
601 | CUPnPServer::CorrectAllItemsSortHack
602 +---------------------------------------------------------------------*/
604 CorrectAllItemsSortHack(const CStdString &item)
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)
610 if ((item.size() == 1 && item[0] == 0x01) || (item.size() > 1 && ((unsigned char) item[1]) == 0xff))
611 return StringUtils::EmptyString;
617 PopulateTagFromObject(CMusicInfoTag& tag,
618 PLT_MediaObject& object,
619 PLT_MediaItemResource* resource /* = NULL */)
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);
628 tag.SetTrackNumber(object.m_MiscInfo.original_track_number);
630 for (NPT_List<NPT_String>::Iterator it = object.m_Affiliation.genres.GetFirstItem(); it; it++)
631 tag.SetGenre((const char*) *it);
633 tag.SetAlbum((const char*)object.m_Affiliation.album);
635 last.SetFromDateString((const char*)object.m_MiscInfo.last_time);
636 tag.SetLastPlayed(last);
637 tag.SetPlayCount(object.m_MiscInfo.play_count);
639 tag.SetDuration(resource->m_Duration);
645 PopulateTagFromObject(CVideoInfoTag& tag,
646 PLT_MediaObject& object,
647 PLT_MediaItemResource* resource /* = NULL */)
650 date.SetFromW3CDate((const char*)object.m_Date);
652 if(!object.m_Recorded.program_title.IsEmpty())
654 tag.m_type = "episode";
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;
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;
667 tag.m_firstAired = date;
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;
676 else if(object.m_ObjectClass.type == "object.item.videoItem.musicVideoClip") {
677 tag.m_type = "musicvideo";
681 tag.m_type = "movie";
682 tag.m_strTitle = object.m_Title;
683 tag.m_premiered = date;
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;
701 if (resource->m_Duration)
702 tag.m_duration = resource->m_Duration;
703 if (object.m_MiscInfo.last_position > 0 )
705 tag.m_resumePoint.totalTimeInSeconds = resource->m_Duration;
706 tag.m_resumePoint.timeInSeconds = object.m_MiscInfo.last_position;
712 CFileItemPtr BuildObject(PLT_MediaObject* entry)
714 NPT_String ObjectClass = entry->m_ObjectClass.type.ToLowercase();
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();
721 // if it's a container, format a string as upnp://uuid/object_id
722 if (pItem->m_bIsFolder) {
725 if( ObjectClass.StartsWith("object.container.album.videoalbum") ) {
726 pItem->SetLabelPreformated(false);
727 UPNP::PopulateTagFromObject(*pItem->GetVideoInfoTag(), *entry, NULL);
729 } else if( ObjectClass.StartsWith("object.container.album.photoalbum")) {
730 //CPictureInfoTag* tag = pItem->GetPictureInfoTag();
732 } else if( ObjectClass.StartsWith("object.container.album") ) {
733 pItem->SetLabelPreformated(false);
734 UPNP::PopulateTagFromObject(*pItem->GetMusicInfoTag(), *entry, NULL);
741 // set a general content type
742 const char* content = NULL;
743 if (ObjectClass.StartsWith("object.item.videoitem")) {
744 pItem->SetMimeType("video/octet-stream");
748 else if(ObjectClass.StartsWith("object.item.audioitem")) {
749 pItem->SetMimeType("audio/octet-stream");
753 else if(ObjectClass.StartsWith("object.item.imageitem")) {
754 pItem->SetMimeType("image/octet-stream");
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))) {
765 if (resource.m_Size != (NPT_LargeSize)-1) {
766 pItem->m_dwSize = resource.m_Size;
772 pItem->SetLabelPreformated(false);
773 UPNP::PopulateTagFromObject(*pItem->GetVideoInfoTag(), *entry, res);
776 pItem->SetLabelPreformated(false);
777 UPNP::PopulateTagFromObject(*pItem->GetMusicInfoTag(), *entry, res);
780 //CPictureInfoTag* tag = pItem->GetPictureInfoTag();
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;
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);
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);
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;
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);
822 else if (type == "episode" || type == "movie")
823 watched = (played > 0);
824 pItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, watched);
829 struct ResourcePrioritySort
831 ResourcePrioritySort(const PLT_MediaObject* entry)
833 if (entry->m_ObjectClass.type.StartsWith("object.item.audioItem"))
835 else if (entry->m_ObjectClass.type.StartsWith("object.item.imageItem"))
837 else if (entry->m_ObjectClass.type.StartsWith("object.item.videoItem"))
841 int GetPriority(const PLT_MediaItemResource& res) const
845 if (m_content != "" && res.m_ProtocolInfo.GetContentType().StartsWith(m_content))
848 NPT_Url url(res.m_Uri);
849 if (URIUtils::IsHostOnLAN((const char*)url.GetHost(), false))
852 if (res.m_ProtocolInfo.GetProtocol() == "xbmc-get")
854 else if (res.m_ProtocolInfo.GetProtocol() == "http-get")
860 int operator()(const PLT_MediaItemResource& lh, const PLT_MediaItemResource& rh) const
862 if(GetPriority(lh) < GetPriority(rh))
868 NPT_String m_content;
871 bool GetResource(const PLT_MediaObject* entry, CFileItem& item)
873 PLT_MediaItemResource resource;
875 // store original path so we remember it
876 item.SetProperty("original_listitem_url", item.GetPath());
877 item.SetProperty("original_listitem_mime", item.GetMimeType());
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]);
884 sorted.Sort(ResourcePrioritySort(entry));
886 if(sorted.GetItemCount() == 0)
889 resource = *sorted.GetFirstItem();
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);
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()));
901 if (resource.m_ProtocolInfo.GetContentType().Compare("application/octet-stream") != 0) {
902 item.SetMimeType((const char*)resource.m_ProtocolInfo.GetContentType());
905 CLog::Log(LOGERROR, "CUPnPDirectory::GetResource - invalid protocol info '%s'",
906 (const char*)(resource.m_ProtocolInfo.ToString()));
909 // look for subtitles
911 for(unsigned r = 0; r < entry->m_Resources.GetItemCount(); r++)
913 const PLT_MediaItemResource& res = entry->m_Resources[r];
914 const PLT_ProtocolInfo& info = res.m_ProtocolInfo;
915 static const char* allowed[] = { "text/srt"
919 for(unsigned type = 0; type < sizeof(allowed)/sizeof(allowed[0]); type++)
921 if(info.Match(PLT_ProtocolInfo("*", "*", allowed[type], "*")))
923 CStdString prop = StringUtils::Format("upnp:subtitle:%d", ++subs);
924 item.SetProperty(prop, (const char*)res.m_Uri);
932 CFileItemPtr GetFileItem(const NPT_String& uri, const NPT_String& meta)
934 PLT_MediaObjectListReference list;
935 PLT_MediaObject* object = NULL;
938 if (NPT_SUCCEEDED(PLT_Didl::FromDidl(meta, list))) {
939 list->Get(0, object);
943 item = BuildObject(object);
947 item->SetPath((const char*)uri);
948 GetResource(object, *item);
950 item.reset(new CFileItem((const char*)uri, false));
955 } /* namespace UPNP */