/xbmc/network/UPnP.cpp
C++ | 2451 lines | 1780 code | 304 blank | 367 comment | 395 complexity | 3f5bbb9a0e0ccfb27cd6b6cfc0fd6cf6 MD5 | raw file
Possible License(s): GPL-2.0, AGPL-1.0
Large files files are truncated, but you can click here to view the full file
- /*
- * UPnP Support for XBMC
- * Copyright (c) 2006 c0diq (Sylvain Rebaud)
- * Portions Copyright (c) by the authors of libPlatinum
- *
- * http://www.plutinosoft.com/blog/category/platinum/
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
- #include "threads/SystemClock.h"
- #include "UPnP.h"
- #include "utils/URIUtils.h"
- #include "Application.h"
- #include "Network.h"
- #include "utils/log.h"
- #include "filesystem/MusicDatabaseDirectory.h"
- #include "filesystem/VideoDatabaseDirectory.h"
- #include "music/MusicDatabase.h"
- #include "video/VideoDatabase.h"
- #include "filesystem/VideoDatabaseDirectory/DirectoryNode.h"
- #include "filesystem/VideoDatabaseDirectory/QueryParams.h"
- #include "filesystem/File.h"
- #include "NptStrings.h"
- #include "Platinum.h"
- #include "PltMediaConnect.h"
- #include "PltMediaRenderer.h"
- #include "PltSyncMediaBrowser.h"
- #include "PltDidl.h"
- #include "NptNetwork.h"
- #include "NptConsole.h"
- #include "music/tags/MusicInfoTag.h"
- #include "pictures/PictureInfoTag.h"
- #include "pictures/GUIWindowSlideShow.h"
- #include "filesystem/Directory.h"
- #include "URL.h"
- #include "settings/GUISettings.h"
- #include "GUIUserMessages.h"
- #include "settings/Settings.h"
- #include "settings/AdvancedSettings.h"
- #include "utils/StringUtils.h"
- #include "FileItem.h"
- #include "guilib/GUIWindowManager.h"
- #include "GUIInfoManager.h"
- #include "utils/TimeUtils.h"
- #include "utils/md5.h"
- #include "guilib/Key.h"
- #include "ThumbLoader.h"
- using namespace std;
- using namespace MUSIC_INFO;
- using namespace XFILE;
- extern CGUIInfoManager g_infoManager;
- NPT_SET_LOCAL_LOGGER("xbmc.upnp")
- #define UPNP_DEFAULT_MAX_RETURNED_ITEMS 200
- #define UPNP_DEFAULT_MIN_RETURNED_ITEMS 30
- /*
- # Play speed
- # 1 normal
- # 0 invalid
- DLNA_ORG_PS = 'DLNA.ORG_PS'
- DLNA_ORG_PS_VAL = '1'
- # Convertion Indicator
- # 1 transcoded
- # 0 not transcoded
- DLNA_ORG_CI = 'DLNA.ORG_CI'
- DLNA_ORG_CI_VAL = '0'
- # Operations
- # 00 not time seek range, not range
- # 01 range supported
- # 10 time seek range supported
- # 11 both supported
- DLNA_ORG_OP = 'DLNA.ORG_OP'
- DLNA_ORG_OP_VAL = '01'
- # Flags
- # senderPaced 80000000 31
- # lsopTimeBasedSeekSupported 40000000 30
- # lsopByteBasedSeekSupported 20000000 29
- # playcontainerSupported 10000000 28
- # s0IncreasingSupported 08000000 27
- # sNIncreasingSupported 04000000 26
- # rtspPauseSupported 02000000 25
- # streamingTransferModeSupported 01000000 24
- # interactiveTransferModeSupported 00800000 23
- # backgroundTransferModeSupported 00400000 22
- # connectionStallingSupported 00200000 21
- # dlnaVersion15Supported 00100000 20
- DLNA_ORG_FLAGS = 'DLNA.ORG_FLAGS'
- DLNA_ORG_FLAGS_VAL = '01500000000000000000000000000000'
- */
- /*----------------------------------------------------------------------
- | static
- +---------------------------------------------------------------------*/
- CUPnP* CUPnP::upnp = NULL;
- // change to false for XBMC_PC if you want real UPnP functionality
- // otherwise keep to true for xbox as it doesn't support multicast
- // don't change unless you know what you're doing!
- bool CUPnP::broadcast = true;
- namespace
- {
- static const NPT_String JoinString(const NPT_List<NPT_String>& array, const NPT_String& delimiter)
- {
- NPT_String result;
- for(NPT_List<NPT_String>::Iterator it = array.GetFirstItem(); it; it++ )
- result += delimiter + (*it);
- if(result.IsEmpty())
- return "";
- else
- return result.SubString(delimiter.GetLength());
- }
- enum EClientQuirks
- {
- ECLIENTQUIRKS_NONE = 0x0
- /* Client requires folder's to be marked as storageFolers as verndor type (360)*/
- , ECLIENTQUIRKS_ONLYSTORAGEFOLDER = 0x01
- /* Client can't handle subtypes for videoItems (360) */
- , ECLIENTQUIRKS_BASICVIDEOCLASS = 0x02
- /* Client requires album to be set to [Unknown Series] to show title (WMP) */
- , ECLIENTQUIRKS_UNKNOWNSERIES = 0x04
- };
- static EClientQuirks GetClientQuirks(const PLT_HttpRequestContext* context)
- {
- if(context == NULL)
- return ECLIENTQUIRKS_NONE;
- unsigned int quirks = 0;
- const NPT_String* user_agent = context->GetRequest().GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_USER_AGENT);
- const NPT_String* server = context->GetRequest().GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_SERVER);
- if (user_agent) {
- if (user_agent->Find("XBox", 0, true) >= 0 ||
- user_agent->Find("Xenon", 0, true) >= 0)
- quirks |= ECLIENTQUIRKS_ONLYSTORAGEFOLDER | ECLIENTQUIRKS_BASICVIDEOCLASS;
- if (user_agent->Find("Windows-Media-Player", 0, true) >= 0)
- quirks |= ECLIENTQUIRKS_UNKNOWNSERIES;
- }
- if (server) {
- if (server->Find("Xbox", 0, true) >= 0)
- quirks |= ECLIENTQUIRKS_ONLYSTORAGEFOLDER | ECLIENTQUIRKS_BASICVIDEOCLASS;
- }
- return (EClientQuirks)quirks;
- }
- }
- /*----------------------------------------------------------------------
- | NPT_Console::Output
- +---------------------------------------------------------------------*/
- void
- NPT_Console::Output(const char* message)
- {
- CLog::Log(LOGDEBUG, "%s", message);
- }
- /*----------------------------------------------------------------------
- | CDeviceHostReferenceHolder class
- +---------------------------------------------------------------------*/
- class CDeviceHostReferenceHolder
- {
- public:
- PLT_DeviceHostReference m_Device;
- };
- /*----------------------------------------------------------------------
- | CCtrlPointReferenceHolder class
- +---------------------------------------------------------------------*/
- class CCtrlPointReferenceHolder
- {
- public:
- PLT_CtrlPointReference m_CtrlPoint;
- };
- /*----------------------------------------------------------------------
- | CUPnPCleaner class
- +---------------------------------------------------------------------*/
- class CUPnPCleaner : public NPT_Thread
- {
- public:
- CUPnPCleaner(CUPnP* upnp) : NPT_Thread(true), m_UPnP(upnp) {}
- void Run() {
- delete m_UPnP;
- }
- CUPnP* m_UPnP;
- };
- /*----------------------------------------------------------------------
- | CUPnP::CUPnP
- +---------------------------------------------------------------------*/
- class CUPnPServer : public PLT_MediaConnect
- {
- public:
- CUPnPServer(const char* friendly_name, const char* uuid = NULL, int port = 0) :
- PLT_MediaConnect("", friendly_name, false, uuid, port) {
- // hack: override path to make sure it's empty
- // urls will contain full paths to local files
- m_Path = "";
- }
- // PLT_MediaServer methods
- virtual NPT_Result OnBrowseMetadata(PLT_ActionReference& action,
- const char* object_id,
- const char* filter,
- NPT_UInt32 starting_index,
- NPT_UInt32 requested_count,
- const NPT_List<NPT_String>& sort_criteria,
- const PLT_HttpRequestContext& context);
- virtual NPT_Result OnBrowseDirectChildren(PLT_ActionReference& action,
- const char* object_id,
- const char* filter,
- NPT_UInt32 starting_index,
- NPT_UInt32 requested_count,
- const NPT_List<NPT_String>& sort_criteria,
- const PLT_HttpRequestContext& context);
- virtual NPT_Result OnSearchContainer(PLT_ActionReference& action,
- const char* container_id,
- const char* search_criteria,
- const char* filter,
- NPT_UInt32 starting_index,
- NPT_UInt32 requested_count,
- const NPT_List<NPT_String>& sort_criteria,
- const PLT_HttpRequestContext& context);
- // PLT_FileMediaServer methods
- virtual NPT_Result ServeFile(NPT_HttpRequest& request,
- const NPT_HttpRequestContext& context,
- NPT_HttpResponse& response,
- const NPT_String& file_path);
- // class methods
- static NPT_Result PopulateObjectFromTag(CMusicInfoTag& tag,
- PLT_MediaObject& object,
- NPT_String* file_path,
- PLT_MediaItemResource* resource,
- EClientQuirks quirks);
- static NPT_Result PopulateObjectFromTag(CVideoInfoTag& tag,
- PLT_MediaObject& object,
- NPT_String* file_path,
- PLT_MediaItemResource* resource,
- EClientQuirks quirks);
- static PLT_MediaObject* BuildObject(const CFileItem& item,
- NPT_String& file_path,
- bool with_count,
- const PLT_HttpRequestContext* context = NULL,
- CUPnPServer* upnp_server = NULL);
- NPT_String BuildSafeResourceUri(const char* host,
- const char* file_path);
- void AddSafeResourceUri(PLT_MediaObject* object, NPT_List<NPT_IpAddress> ips, const char* file_path, const NPT_String& info)
- {
- PLT_MediaItemResource res;
- for(NPT_List<NPT_IpAddress>::Iterator ip = ips.GetFirstItem(); ip; ++ip) {
- res.m_ProtocolInfo = PLT_ProtocolInfo(info);
- res.m_Uri = BuildSafeResourceUri((*ip).ToString(), file_path);
- object->m_Resources.Add(res);
- }
- }
- static const char* GetMimeTypeFromExtension(const char* extension, const PLT_HttpRequestContext* context = NULL);
- static NPT_String GetMimeType(const CFileItem& item, const PLT_HttpRequestContext* context = NULL);
- static NPT_String GetMimeType(const char* filename, const PLT_HttpRequestContext* context = NULL);
- static const CStdString& CorrectAllItemsSortHack(const CStdString &item);
- private:
- PLT_MediaObject* Build(CFileItemPtr item,
- bool with_count,
- const PLT_HttpRequestContext& context,
- const char* parent_id = NULL);
- NPT_Result BuildResponse(PLT_ActionReference& action,
- CFileItemList& items,
- const char* filter,
- NPT_UInt32 starting_index,
- NPT_UInt32 requested_count,
- const NPT_List<NPT_String>& sort_criteria,
- const PLT_HttpRequestContext& context,
- const char* parent_id /* = NULL */);
- // class methods
- static NPT_String GetParentFolder(NPT_String file_path) {
- int index = file_path.ReverseFind("\\");
- if (index == -1) return "";
- return file_path.Left(index);
- }
- static const NPT_String GetProtocolInfo(const CFileItem& item,
- const char* protocol,
- const PLT_HttpRequestContext* context = NULL);
- NPT_Mutex m_FileMutex;
- NPT_Map<NPT_String, NPT_String> m_FileMap;
- public:
- // class members
- static NPT_UInt32 m_MaxReturnedItems;
- };
- NPT_UInt32 CUPnPServer::m_MaxReturnedItems = 0;
- /*----------------------------------------------------------------------
- | CUPnPServer::BuildSafeResourceUri
- +---------------------------------------------------------------------*/
- NPT_String CUPnPServer::BuildSafeResourceUri(const char* host,
- const char* file_path)
- {
- CStdString md5;
- XBMC::XBMC_MD5 md5state;
- md5state.append(file_path);
- md5state.getDigest(md5);
- md5 += "/" + URIUtils::GetFileName(file_path);
- { NPT_AutoLock lock(m_FileMutex);
- NPT_CHECK(m_FileMap.Put(md5.c_str(), file_path));
- }
- return PLT_FileMediaServer::BuildSafeResourceUri(m_FileBaseUri, host, md5);
- }
- /*----------------------------------------------------------------------
- | CUPnPServer::GetMimeType
- +---------------------------------------------------------------------*/
- NPT_String
- CUPnPServer::GetMimeType(const char* filename,
- const PLT_HttpRequestContext* context /* = NULL */)
- {
- NPT_String ext = URIUtils::GetExtension(filename).c_str();
- ext.TrimLeft('.');
- ext = ext.ToLowercase();
- return PLT_MediaObject::GetMimeTypeFromExtension(ext, context);
- }
- /*----------------------------------------------------------------------
- | CUPnPServer::GetMimeType
- +---------------------------------------------------------------------*/
- NPT_String
- CUPnPServer::GetMimeType(const CFileItem& item,
- const PLT_HttpRequestContext* context /* = NULL */)
- {
- CStdString path = item.GetPath();
- if (item.HasVideoInfoTag() && !item.GetVideoInfoTag()->GetPath().IsEmpty()) {
- path = item.GetVideoInfoTag()->GetPath();
- } else if (item.HasMusicInfoTag() && !item.GetMusicInfoTag()->GetURL().IsEmpty()) {
- path = item.GetMusicInfoTag()->GetURL();
- }
- if(path.Left(8).Equals("stack://"))
- return "audio/x-mpegurl";
- NPT_String ext = URIUtils::GetExtension(path).c_str();
- ext.TrimLeft('.');
- ext = ext.ToLowercase();
- NPT_String mime;
- /* We always use Platinum mime type first
- as it is defined to map extension to DLNA compliant mime type
- or custom according to context (who asked for it) */
- if (!ext.IsEmpty()) {
- mime = PLT_MediaObject::GetMimeTypeFromExtension(ext, context);
- if (mime == "application/octet-stream") mime = "";
- }
- /* if Platinum couldn't map it, default to XBMC mapping */
- if (mime.IsEmpty()) {
- NPT_String mime = item.GetMimeType().c_str();
- if (mime == "application/octet-stream") mime = "";
- }
- /* fallback to generic mime type if not found */
- if (mime.IsEmpty()) {
- if (item.IsVideo() || item.IsVideoDb() )
- mime = "video/" + ext;
- else if (item.IsAudio() || item.IsMusicDb() )
- mime = "audio/" + ext;
- else if (item.IsPicture() )
- mime = "image/" + ext;
- }
- /* nothing we can figure out */
- if (mime.IsEmpty()) {
- mime = "application/octet-stream";
- }
- return mime;
- }
- /*----------------------------------------------------------------------
- | CUPnPServer::GetProtocolInfo
- +---------------------------------------------------------------------*/
- const NPT_String
- CUPnPServer::GetProtocolInfo(const CFileItem& item,
- const char* protocol,
- const PLT_HttpRequestContext* context /* = NULL */)
- {
- NPT_String proto = protocol;
- /* fixup the protocol just in case nothing was passed */
- if (proto.IsEmpty()) {
- proto = item.GetAsUrl().GetProtocol();
- }
- /*
- map protocol to right prefix and use xbmc-get for
- unsupported UPnP protocols for other xbmc clients
- TODO: add rtsp ?
- */
- if (proto == "http") {
- proto = "http-get";
- } else {
- proto = "xbmc-get";
- }
- /* we need a valid extension to retrieve the mimetype for the protocol info */
- NPT_String mime = GetMimeType(item, context);
- proto += ":*:" + mime + ":" + PLT_MediaObject::GetDlnaExtension(mime, context);
- return proto;
- }
- /*----------------------------------------------------------------------
- | CUPnPServer::PopulateObjectFromTag
- +---------------------------------------------------------------------*/
- NPT_Result
- CUPnPServer::PopulateObjectFromTag(CMusicInfoTag& tag,
- PLT_MediaObject& object,
- NPT_String* file_path, /* = NULL */
- PLT_MediaItemResource* resource, /* = NULL */
- EClientQuirks quirks)
- {
- if (!tag.GetURL().IsEmpty() && file_path)
- *file_path = tag.GetURL();
- std::vector<std::string> genres = tag.GetGenre();
- for (unsigned int index = 0; index < genres.size(); index++)
- object.m_Affiliation.genre.Add(genres.at(index).c_str());
- object.m_Title = tag.GetTitle();
- object.m_Affiliation.album = tag.GetAlbum();
- for (unsigned int index = 0; index < tag.GetArtist().size(); index++)
- {
- object.m_People.artists.Add(tag.GetArtist().at(index).c_str());
- object.m_People.artists.Add(tag.GetArtist().at(index).c_str(), "Performer");
- }
- object.m_People.artists.Add(StringUtils::Join(!tag.GetAlbumArtist().empty() ? tag.GetAlbumArtist() : tag.GetArtist(), g_advancedSettings.m_musicItemSeparator).c_str(), "AlbumArtist");
- if(tag.GetAlbumArtist().empty())
- object.m_Creator = StringUtils::Join(tag.GetArtist(), g_advancedSettings.m_musicItemSeparator);
- else
- object.m_Creator = StringUtils::Join(tag.GetAlbumArtist(), g_advancedSettings.m_musicItemSeparator);
- object.m_MiscInfo.original_track_number = tag.GetTrackNumber();
- if(tag.GetDatabaseId() >= 0) {
- object.m_ReferenceID = NPT_String::Format("musicdb://4/%i%s", tag.GetDatabaseId(), URIUtils::GetExtension(tag.GetURL()).c_str());
- }
- if (object.m_ReferenceID == object.m_ObjectID)
- object.m_ReferenceID = "";
- if (resource) resource->m_Duration = tag.GetDuration();
- return NPT_SUCCESS;
- }
- /*----------------------------------------------------------------------
- | CUPnPServer::PopulateObjectFromTag
- +---------------------------------------------------------------------*/
- NPT_Result
- CUPnPServer::PopulateObjectFromTag(CVideoInfoTag& tag,
- PLT_MediaObject& object,
- NPT_String* file_path, /* = NULL */
- PLT_MediaItemResource* resource, /* = NULL */
- EClientQuirks quirks)
- {
- // some usefull buffers
- CStdStringArray strings;
- if (!tag.m_strFileNameAndPath.IsEmpty() && file_path)
- *file_path = tag.m_strFileNameAndPath;
- if (tag.m_iDbId != -1 ) {
- if (!tag.m_artist.empty()) {
- object.m_ObjectClass.type = "object.item.videoItem.musicVideoClip";
- object.m_Creator = StringUtils::Join(tag.m_artist, g_advancedSettings.m_videoItemSeparator);
- object.m_Title = tag.m_strTitle;
- object.m_ReferenceID = NPT_String::Format("videodb://3/2/%i", tag.m_iDbId);
- } else if (!tag.m_strShowTitle.IsEmpty()) {
- object.m_ObjectClass.type = "object.item.videoItem.videoBroadcast";
- object.m_Recorded.program_title = "S" + ("0" + NPT_String::FromInteger(tag.m_iSeason)).Right(2);
- object.m_Recorded.program_title += "E" + ("0" + NPT_String::FromInteger(tag.m_iEpisode)).Right(2);
- object.m_Recorded.program_title += " : " + tag.m_strTitle;
- object.m_Recorded.series_title = tag.m_strShowTitle;
- object.m_Recorded.episode_number = tag.m_iSeason * 100 + tag.m_iEpisode;
- object.m_Title = object.m_Recorded.series_title + " - " + object.m_Recorded.program_title;
- object.m_Date = tag.m_firstAired.GetAsLocalizedDate();
- if(tag.m_iSeason != -1)
- object.m_ReferenceID = NPT_String::Format("videodb://2/0/%i", tag.m_iDbId);
- } else {
- object.m_ObjectClass.type = "object.item.videoItem.movie";
- object.m_Title = tag.m_strTitle;
- object.m_Date = NPT_String::FromInteger(tag.m_iYear) + "-01-01";
- object.m_ReferenceID = NPT_String::Format("videodb://1/2/%i", tag.m_iDbId);
- }
- }
- if(quirks & ECLIENTQUIRKS_BASICVIDEOCLASS)
- object.m_ObjectClass.type = "object.item.videoItem";
- if(object.m_ReferenceID == object.m_ObjectID)
- object.m_ReferenceID = "";
- for (unsigned int index = 0; index < tag.m_genre.size(); index++)
- object.m_Affiliation.genre.Add(tag.m_genre.at(index).c_str());
- for(CVideoInfoTag::iCast it = tag.m_cast.begin();it != tag.m_cast.end();it++) {
- object.m_People.actors.Add(it->strName.c_str(), it->strRole.c_str());
- }
- object.m_People.director = StringUtils::Join(tag.m_director, g_advancedSettings.m_videoItemSeparator);
- for (unsigned int index = 0; index < tag.m_writingCredits.size(); index++)
- object.m_People.authors.Add(tag.m_writingCredits[index].c_str());
- object.m_Description.description = tag.m_strTagLine;
- object.m_Description.long_description = tag.m_strPlot;
- if (resource) resource->m_Duration = tag.m_streamDetails.GetVideoDuration();
- if (resource) resource->m_Resolution = NPT_String::FromInteger(tag.m_streamDetails.GetVideoWidth()) + "x" + NPT_String::FromInteger(tag.m_streamDetails.GetVideoHeight());
- return NPT_SUCCESS;
- }
- /*----------------------------------------------------------------------
- | CUPnPServer::CorrectAllItemsSortHack
- +---------------------------------------------------------------------*/
- const CStdString&
- CUPnPServer::CorrectAllItemsSortHack(const CStdString &item)
- {
- // This is required as in order for the "* All Albums" etc. items to sort
- // correctly, they must have fake artist/album etc. information generated.
- // This looks nasty if we attempt to render it to the GUI, thus this (further)
- // workaround
- if ((item.size() == 1 && item[0] == 0x01) || (item.size() > 1 && ((unsigned char) item[1]) == 0xff))
- return StringUtils::EmptyString;
- return item;
- }
- /*----------------------------------------------------------------------
- | CUPnPServer::BuildObject
- +---------------------------------------------------------------------*/
- PLT_MediaObject*
- CUPnPServer::BuildObject(const CFileItem& item,
- NPT_String& file_path,
- bool with_count,
- const PLT_HttpRequestContext* context /* = NULL */,
- CUPnPServer* upnp_server /* = NULL */)
- {
- PLT_MediaItemResource resource;
- PLT_MediaObject* object = NULL;
- CLog::Log(LOGDEBUG, "Building didl for object '%s'", (const char*)item.GetPath());
- EClientQuirks quirks = GetClientQuirks(context);
- // get list of ip addresses
- NPT_List<NPT_IpAddress> ips;
- NPT_CHECK_LABEL(PLT_UPnPMessageHelper::GetIPAddresses(ips), failure);
- // if we're passed an interface where we received the request from
- // move the ip to the top
- if (context && context->GetLocalAddress().GetIpAddress().ToString() != "0.0.0.0") {
- ips.Remove(context->GetLocalAddress().GetIpAddress());
- ips.Insert(ips.GetFirstItem(), context->GetLocalAddress().GetIpAddress());
- }
- if (!item.m_bIsFolder) {
- object = new PLT_MediaItem();
- object->m_ObjectID = item.GetPath();
- /* Setup object type */
- if (item.IsMusicDb() || item.IsAudio()) {
- object->m_ObjectClass.type = "object.item.audioItem.musicTrack";
- if (item.HasMusicInfoTag()) {
- CMusicInfoTag *tag = (CMusicInfoTag*)item.GetMusicInfoTag();
- PopulateObjectFromTag(*tag, *object, &file_path, &resource, quirks);
- }
- } else if (item.IsVideoDb() || item.IsVideo()) {
- object->m_ObjectClass.type = "object.item.videoItem";
- if(quirks & ECLIENTQUIRKS_UNKNOWNSERIES)
- object->m_Affiliation.album = "[Unknown Series]";
- if (item.HasVideoInfoTag()) {
- CVideoInfoTag *tag = (CVideoInfoTag*)item.GetVideoInfoTag();
- PopulateObjectFromTag(*tag, *object, &file_path, &resource, quirks);
- }
- } else if (item.IsPicture()) {
- object->m_ObjectClass.type = "object.item.imageItem.photo";
- } else {
- object->m_ObjectClass.type = "object.item";
- }
- // duration of zero is invalid
- if (resource.m_Duration == 0) resource.m_Duration = -1;
- // Set the resource file size
- resource.m_Size = item.m_dwSize;
- if (resource.m_Size == 0) {
- struct __stat64 info;
- if(CFile::Stat((const char*)file_path, &info) == 0 && info.st_size >= 0)
- resource.m_Size = info.st_size;
- }
- if(resource.m_Size == 0)
- resource.m_Size = (NPT_LargeSize)-1;
- // set date
- if (object->m_Date.IsEmpty() && item.m_dateTime.IsValid()) {
- object->m_Date = item.m_dateTime.GetAsLocalizedDate();
- }
- if (upnp_server) {
- upnp_server->AddSafeResourceUri(object, ips, file_path, GetProtocolInfo(item, "http", context));
- }
- // if the item is remote, add a direct link to the item
- if (URIUtils::IsRemote((const char*)file_path)) {
- resource.m_ProtocolInfo = PLT_ProtocolInfo(CUPnPServer::GetProtocolInfo(item, item.GetAsUrl().GetProtocol(), context));
- resource.m_Uri = file_path;
- // if the direct link can be served directly using http, then push it in front
- // otherwise keep the xbmc-get resource last and let a compatible client look for it
- if (resource.m_ProtocolInfo.ToString().StartsWith("xbmc", true)) {
- object->m_Resources.Add(resource);
- } else {
- object->m_Resources.Insert(object->m_Resources.GetFirstItem(), resource);
- }
- }
- // Some upnp clients expect all audio items to have parent root id 4
- #ifdef WMP_ID_MAPPING
- object->m_ParentID = "4";
- #endif
- } else {
- PLT_MediaContainer* container = new PLT_MediaContainer;
- object = container;
- /* Assign a title and id for this container */
- container->m_ObjectID = item.GetPath();
- container->m_ObjectClass.type = "object.container";
- container->m_ChildrenCount = -1;
- CStdStringArray strings;
- /* this might be overkill, but hey */
- if (item.IsMusicDb()) {
- MUSICDATABASEDIRECTORY::NODE_TYPE node = CMusicDatabaseDirectory::GetDirectoryType(item.GetPath());
- switch(node) {
- case MUSICDATABASEDIRECTORY::NODE_TYPE_ARTIST: {
- container->m_ObjectClass.type += ".person.musicArtist";
- CMusicInfoTag *tag = (CMusicInfoTag*)item.GetMusicInfoTag();
- if (tag) {
- container->m_People.artists.Add(
- CorrectAllItemsSortHack(StringUtils::Join(tag->GetArtist(), g_advancedSettings.m_musicItemSeparator)).c_str(), "Performer");
- container->m_People.artists.Add(
- CorrectAllItemsSortHack(StringUtils::Join(!tag->GetAlbumArtist().empty() ? tag->GetAlbumArtist() : tag->GetArtist(), g_advancedSettings.m_musicItemSeparator)).c_str(), "AlbumArtist");
- }
- #ifdef WMP_ID_MAPPING
- // Some upnp clients expect all artists to have parent root id 107
- container->m_ParentID = "107";
- #endif
- }
- break;
- case MUSICDATABASEDIRECTORY::NODE_TYPE_ALBUM:
- case MUSICDATABASEDIRECTORY::NODE_TYPE_ALBUM_COMPILATIONS:
- case MUSICDATABASEDIRECTORY::NODE_TYPE_ALBUM_RECENTLY_ADDED:
- case MUSICDATABASEDIRECTORY::NODE_TYPE_YEAR_ALBUM: {
- container->m_ObjectClass.type += ".album.musicAlbum";
- // for Sonos to be happy
- CMusicInfoTag *tag = (CMusicInfoTag*)item.GetMusicInfoTag();
- if (tag) {
- container->m_People.artists.Add(
- CorrectAllItemsSortHack(StringUtils::Join(tag->GetArtist(), g_advancedSettings.m_musicItemSeparator)).c_str(), "Performer");
- container->m_People.artists.Add(
- CorrectAllItemsSortHack(StringUtils::Join(!tag->GetAlbumArtist().empty() ? tag->GetAlbumArtist() : tag->GetArtist(), g_advancedSettings.m_musicItemSeparator)).c_str(), "AlbumArtist");
- container->m_Affiliation.album = CorrectAllItemsSortHack(tag->GetAlbum()).c_str();
- }
- #ifdef WMP_ID_MAPPING
- // Some upnp clients expect all albums to have parent root id 7
- container->m_ParentID = "7";
- #endif
- }
- break;
- case MUSICDATABASEDIRECTORY::NODE_TYPE_GENRE:
- container->m_ObjectClass.type += ".genre.musicGenre";
- break;
- default:
- break;
- }
- } else if (item.IsVideoDb()) {
- VIDEODATABASEDIRECTORY::NODE_TYPE node = CVideoDatabaseDirectory::GetDirectoryType(item.GetPath());
- CVideoInfoTag &tag = *(CVideoInfoTag*)item.GetVideoInfoTag();
- switch(node) {
- case VIDEODATABASEDIRECTORY::NODE_TYPE_GENRE:
- container->m_ObjectClass.type += ".genre.movieGenre";
- break;
- case VIDEODATABASEDIRECTORY::NODE_TYPE_ACTOR:
- container->m_ObjectClass.type += ".person.videoArtist";
- container->m_Creator = StringUtils::Join(tag.m_artist, g_advancedSettings.m_videoItemSeparator);
- container->m_Title = tag.m_strTitle;
- break;
- case VIDEODATABASEDIRECTORY::NODE_TYPE_TITLE_TVSHOWS:
- container->m_ObjectClass.type += ".album.videoAlbum";
- container->m_Recorded.program_title = "S" + ("0" + NPT_String::FromInteger(tag.m_iSeason)).Right(2);
- container->m_Recorded.program_title += "E" + ("0" + NPT_String::FromInteger(tag.m_iEpisode)).Right(2);
- container->m_Recorded.program_title += " : " + tag.m_strTitle;
- container->m_Recorded.series_title = tag.m_strShowTitle;
- container->m_Recorded.episode_number = tag.m_iSeason * 100 + tag.m_iEpisode;
- container->m_Title = container->m_Recorded.series_title + " - " + container->m_Recorded.program_title;
- container->m_Title = tag.m_strTitle;
- if(!tag.m_firstAired.IsValid() && tag.m_iYear)
- container->m_Date = NPT_String::FromInteger(tag.m_iYear) + "-01-01";
- else
- container->m_Date = tag.m_firstAired.GetAsLocalizedDate();
- for (unsigned int index = 0; index < tag.m_genre.size(); index++)
- container->m_Affiliation.genre.Add(tag.m_genre.at(index).c_str());
- for(CVideoInfoTag::iCast it = tag.m_cast.begin();it != tag.m_cast.end();it++) {
- container->m_People.actors.Add(it->strName.c_str(), it->strRole.c_str());
- }
- container->m_People.director = StringUtils::Join(tag.m_director, g_advancedSettings.m_videoItemSeparator);;
- for (unsigned int index = 0; index < tag.m_writingCredits.size(); index++)
- container->m_People.authors.Add(tag.m_writingCredits[index].c_str());
- container->m_Description.description = tag.m_strTagLine;
- container->m_Description.long_description = tag.m_strPlot;
- break;
- default:
- container->m_ObjectClass.type += ".storageFolder";
- break;
- }
- } else if (item.IsPlayList()) {
- container->m_ObjectClass.type += ".playlistContainer";
- }
- if(quirks & ECLIENTQUIRKS_ONLYSTORAGEFOLDER) {
- container->m_ObjectClass.type = "object.container.storageFolder";
- }
- /* Get the number of children for this container */
- if (with_count && upnp_server) {
- if (object->m_ObjectID.StartsWith("virtualpath://")) {
- NPT_Cardinal count = 0;
- NPT_CHECK_LABEL(NPT_File::GetCount(file_path, count), failure);
- container->m_ChildrenCount = count;
- } else {
- /* this should be a standard path */
- // TODO - get file count of this directory
- }
- }
- }
- // set a title for the object
- if (object->m_Title.IsEmpty()) {
- if (!item.GetLabel().IsEmpty()) {
- CStdString title = item.GetLabel();
- if (item.IsPlayList() || !item.m_bIsFolder) URIUtils::RemoveExtension(title);
- object->m_Title = title;
- } else {
- CStdString title, volumeNumber;
- CUtil::GetVolumeFromFileName(item.GetPath(), title, volumeNumber);
- if (!item.m_bIsFolder) URIUtils::RemoveExtension(title);
- object->m_Title = title;
- }
- }
- // set a thumbnail if we have one
- if (item.HasThumbnail() && upnp_server) {
- object->m_ExtraInfo.album_art_uri = upnp_server->BuildSafeResourceUri(
- (*ips.GetFirstItem()).ToString(),
- item.GetThumbnailImage());
- // Set DLNA profileID by extension, defaulting to JPEG.
- NPT_String ext = URIUtils::GetExtension(item.GetThumbnailImage()).c_str();
- if (strcmp(ext, ".png") == 0) {
- object->m_ExtraInfo.album_art_uri_dlna_profile = "PNG_TN";
- } else {
- object->m_ExtraInfo.album_art_uri_dlna_profile = "JPEG_TN";
- }
- }
- if (upnp_server) {
- CStdString fanart(item.GetCachedFanart());
- if (CFile::Exists(fanart)) {
- upnp_server->AddSafeResourceUri(object, ips, fanart, "xbmc.org:*:fanart:*");
- }
- }
- return object;
- failure:
- delete object;
- return NULL;
- }
- /*----------------------------------------------------------------------
- | CUPnPServer::Build
- +---------------------------------------------------------------------*/
- PLT_MediaObject*
- CUPnPServer::Build(CFileItemPtr item,
- bool with_count,
- const PLT_HttpRequestContext& context,
- const char* parent_id /* = NULL */)
- {
- PLT_MediaObject* object = NULL;
- NPT_String path = item->GetPath().c_str();
- //HACK: temporary disabling count as it thrashes HDD
- with_count = false;
- CLog::Log(LOGDEBUG, "Preparing upnp object for item '%s'", (const char*)path);
- if (path == "virtualpath://upnproot") {
- path.TrimRight("/");
- if (path.StartsWith("virtualpath://")) {
- object = new PLT_MediaContainer;
- object->m_Title = item->GetLabel();
- object->m_ObjectClass.type = "object.container";
- object->m_ObjectID = path;
- // root
- object->m_ObjectID = "0";
- object->m_ParentID = "-1";
- // root has 5 children
- if (with_count) {
- ((PLT_MediaContainer*)object)->m_ChildrenCount = 5;
- }
- } else {
- goto failure;
- }
- } else {
- // db path handling
- NPT_String file_path, share_name;
- file_path = item->GetPath();
- share_name = "";
- if (path.StartsWith("musicdb://")) {
- if (path == "musicdb://" ) {
- item->SetLabel("Music Library");
- item->SetLabelPreformated(true);
- } else {
- if (!item->HasMusicInfoTag() || !item->GetMusicInfoTag()->Loaded() )
- item->LoadMusicTag();
- if (!item->HasThumbnail() )
- item->SetCachedMusicThumb();
- if (item->GetLabel().IsEmpty()) {
- /* if no label try to grab it from node type */
- CStdString label;
- if (CMusicDatabaseDirectory::GetLabel((const char*)path, label)) {
- item->SetLabel(label);
- item->SetLabelPreformated(true);
- }
- }
- }
- } else if (file_path.StartsWith("videodb://")) {
- if (path == "videodb://" ) {
- item->SetLabel("Video Library");
- item->SetLabelPreformated(true);
- } else {
- if (!item->HasVideoInfoTag()) {
- XFILE::VIDEODATABASEDIRECTORY::CQueryParams params;
- XFILE::VIDEODATABASEDIRECTORY::CDirectoryNode::GetDatabaseInfo((const char*)path, params);
- CVideoDatabase db;
- if (!db.Open() ) return NULL;
- if (params.GetMovieId() >= 0 )
- db.GetMovieInfo((const char*)path, *item->GetVideoInfoTag(), params.GetMovieId());
- else if (params.GetEpisodeId() >= 0 )
- db.GetEpisodeInfo((const char*)path, *item->GetVideoInfoTag(), params.GetEpisodeId());
- else if (params.GetTvShowId() >= 0 )
- db.GetTvShowInfo((const char*)path, *item->GetVideoInfoTag(), params.GetTvShowId());
- }
- // try to grab title from tag
- if (item->HasVideoInfoTag() && !item->GetVideoInfoTag()->m_strTitle.IsEmpty()) {
- item->SetLabel(item->GetVideoInfoTag()->m_strTitle);
- item->SetLabelPreformated(true);
- }
- // try to grab it from the folder
- if (item->GetLabel().IsEmpty()) {
- CStdString label;
- if (CVideoDatabaseDirectory::GetLabel((const char*)path, label)) {
- item->SetLabel(label);
- item->SetLabelPreformated(true);
- }
- }
- if (!item->HasThumbnail() )
- item->SetThumbnailImage(CThumbLoader::GetCachedImage(*item, "thumb"));
- }
- }
- // not a virtual path directory, new system
- object = BuildObject(*item.get(), file_path, with_count, &context, this);
- // set parent id if passed, otherwise it should have been determined
- if (object && parent_id) {
- object->m_ParentID = parent_id;
- }
- }
- if (object) {
- // remap Root virtualpath://upnproot/ to id "0"
- if (object->m_ObjectID == "virtualpath://upnproot/")
- object->m_ObjectID = "0";
- // remap Parent Root virtualpath://upnproot/ to id "0"
- if (object->m_ParentID == "virtualpath://upnproot/")
- object->m_ParentID = "0";
- }
- return object;
- failure:
- delete object;
- return NULL;
- }
- /*----------------------------------------------------------------------
- | TranslateWMPObjectId
- +---------------------------------------------------------------------*/
- static NPT_String TranslateWMPObjectId(NPT_String id)
- {
- if (id == "0") {
- id = "virtualpath://upnproot/";
- } else if (id == "15") {
- // Xbox 360 asking for videos
- id = "videodb://";
- } else if (id == "16") {
- // Xbox 360 asking for photos
- } else if (id == "107") {
- // Sonos uses 107 for artists root container id
- id = "musicdb://2/";
- } else if (id == "7") {
- // Sonos uses 7 for albums root container id
- id = "musicdb://3/";
- } else if (id == "4") {
- // Sonos uses 4 for tracks root container id
- id = "musicdb://4/";
- }
- CLog::Log(LOGDEBUG, "UPnP Translated id to '%s'", (const char*)id);
- return id;
- }
- /*----------------------------------------------------------------------
- | CUPnPServer::OnBrowseMetadata
- +---------------------------------------------------------------------*/
- NPT_Result
- CUPnPServer::OnBrowseMetadata(PLT_ActionReference& action,
- const char* object_id,
- const char* filter,
- NPT_UInt32 starting_index,
- NPT_UInt32 requested_count,
- const NPT_List<NPT_String>& sort_criteria,
- const PLT_HttpRequestContext& context)
- {
- NPT_COMPILER_UNUSED(sort_criteria);
- NPT_COMPILER_UNUSED(requested_count);
- NPT_COMPILER_UNUSED(starting_index);
- NPT_String didl;
- NPT_Reference<PLT_MediaObject> object;
- NPT_String id = TranslateWMPObjectId(object_id);
- vector<CStdString> paths;
- CFileItemPtr item;
- CLog::Log(LOGINFO, "Received UPnP Browse Metadata request for object '%s'", (const char*)object_id);
- if (id.StartsWith("virtualpath://")) {
- id.TrimRight("/");
- if (id == "virtualpath://upnproot") {
- id += "/";
- item.reset(new CFileItem((const char*)id, true));
- item->SetLabel("Root");
- item->SetLabelPreformated(true);
- object = Build(item, true, context);
- } else {
- return NPT_FAILURE;
- }
- } else {
- // determine if it's a container by calling CDirectory::Exists
- item.reset(new CFileItem((const char*)id, CDirectory::Exists((const char*)id)));
- // determine parent id for shared paths only
- // otherwise let db find out
- CStdString parent;
- if (!URIUtils::GetParentPath((const char*)id, parent)) parent = "0";
- //#ifdef WMP_ID_MAPPING
- // if (!id.StartsWith("musicdb://") && !id.StartsWith("videodb://")) {
- // parent = "";
- // }
- //#endif
- object = Build(item, true, context, parent.empty()?NULL:parent.c_str());
- }
- if (object.IsNull()) {
- /* error */
- NPT_LOG_WARNING_1("CUPnPServer::OnBrowseMetadata - Object null (%s)", object_id);
- action->SetError(701, "No Such Object.");
- return NPT_FAILURE;
- }
- NPT_String tmp;
- NPT_CHECK(PLT_Didl::ToDidl(*object.AsPointer(), filter, tmp));
- /* add didl header and footer */
- didl = didl_header + tmp + didl_footer;
- NPT_CHECK(action->SetArgumentValue("Result", didl));
- NPT_CHECK(action->SetArgumentValue("NumberReturned", "1"));
- NPT_CHECK(action->SetArgumentValue("TotalMatches", "1"));
- // update ID may be wrong here, it should be the one of the container?
- NPT_CHECK(action->SetArgumentValue("UpdateId", "0"));
- // TODO: We need to keep track of the overall SystemUpdateID of the CDS
- return NPT_SUCCESS;
- }
- /*----------------------------------------------------------------------
- | CUPnPServer::OnBrowseDirectChildren
- +---------------------------------------------------------------------*/
- NPT_Result
- CUPnPServer::OnBrowseDirectChildren(PLT_ActionReference& action,
- const char* object_id,
- const char* filter,
- NPT_UInt32 starting_index,
- NPT_UInt32 requested_count,
- const NPT_List<NPT_String>& sort_criteria,
- const PLT_HttpRequestContext& context)
- {
- CFileItemList items;
- NPT_String parent_id = TranslateWMPObjectId(object_id);
- CLog::Log(LOGINFO, "Received UPnP Browse DirectChildren request for object '%s'", (const char*)object_id);
- items.SetPath(CStdString(parent_id));
- if (!items.Load()) {
- // cache anything that takes more than a second to retrieve
- unsigned int time = XbmcThreads::SystemClockMillis();
- if (parent_id.StartsWith("virtualpath://upnproot")) {
- CFileItemPtr item;
- // music library
- item.reset(new CFileItem("musicdb://", true));
- item->SetLabel("Music Library");
- item->SetLabelPreformated(true);
- items.Add(item);
- // video library
- item.reset(new CFileItem("videodb://", true));
- item->SetLabel("Video Library");
- item->SetLabelPreformated(true);
- items.Add(item);
- } else {
- CDirectory::GetDirectory((const char*)parent_id, items);
- }
- if (items.CacheToDiscAlways() || (items.CacheToDiscIfSlow() && (XbmcThreads::SystemClockMillis() - time) > 1000 )) {
- items.Save();
- }
- }
- // Always sort by label
- items.Sort(SORT_METHOD_LABEL, SortOrderAscending);
- // Don't pass parent_id if action is Search not BrowseDirectChildren, as
- // we want the engine to determine the best parent id, not necessarily the one
- // passed
- NPT_String action_name = action->GetActionDesc().GetName();
- return BuildResponse(
- action,
- items,
- filter,
- starting_index,
- requested_count,
- sort_criteria,
- context,
- (action_name.Compare("Search", true)==0)?NULL:parent_id.GetChars());
- }
- /*----------------------------------------------------------------------
- | CUPnPServer::BuildResponse
- +---------------------------------------------------------------------*/
- NPT_Result
- CUPnPServer::BuildResponse(PLT_ActionReference& action,
- CFileItemList& items,
- const char* filter,
- NPT_UInt32 starting_index,
- NPT_UInt32 requested_count,
- const NPT_List<NPT_String>& sort_criteria,
- const PLT_HttpRequestContext& context,
- const char* parent_id /* = NULL */)
- {
- NPT_COMPILER_UNUSED(sort_criteria);
- CLog::Log(LOGDEBUG, "Building UPnP response with filter '%s', starting @ %d with %d requested",
- (const char*)filter,
- starting_index,
- requested_count);
- // won't return more than UPNP_MAX_RETURNED_ITEMS items at a time to keep things smooth
- // 0 requested means as many as possible
- NPT_UInt32 max_count = (requested_count == 0)?m_MaxReturnedItems:min((unsigned long)requested_count, (unsigned long)m_MaxReturnedItems);
- NPT_UInt32 stop_index = min((unsigned long)(starting_index + max_count), (unsigned long)items.Size()); // don't return more than we can
- NPT_Cardinal count = 0;
- NPT_String didl = didl_header;
- PLT_MediaObjectReference object;
- for (unsigned long i=starting_index; i<stop_index; ++i) {
- object = Build(items[i], true, context, parent_id);
- if (object.IsNull()) {
- continue;
- }
- NPT_String tmp;
- NPT_CHECK(PLT_Didl::ToDidl(*object.AsPointer(), filter, tmp));
- // Neptunes string growing is dead slow for small additions
- if (didl.GetCapacity() < tmp.GetLength() + didl.GetLength()) {
- didl.Reserve((tmp.GetLength() + didl.GetLength())*2);
- }
- didl += tmp;
- ++count;
- }
- didl += didl_footer;
- CLog::Log(LOGDEBUG, "Returning UPnP response with %d items out of %d total matches",
- count,
- items.Size());
- NPT_CHECK(action->SetArgumentValue("Result", didl));
- NPT_CHECK(action->SetArgumentValue("NumberReturned", NPT_String::FromInteger(count)));
- NPT_CHECK(action->SetArgumentValue("TotalMatches", NPT_String::FromInteger(items.Size())));
- NPT_CHECK(action->SetArgumentValue("UpdateId", "0"));
- return NPT_SUCCESS;
- }
- /*----------------------------------------------------------------------
- | FindSubCriteria
- +---------------------------------------------------------------------*/
- static
- NPT_String
- FindSubCriteria(NPT_String criteria, const char* name)
- {
- NPT_String result;
- int search = criteria.Find(name);
- if (search >= 0) {
- criteria = criteria.Right(criteria.GetLength() - search - NPT_StringLength(name));
- criteria.TrimLeft(" ");
- if (criteria.GetLength()>0 && criteria[0] == '=') {
- criteria.TrimLeft("= ");
- if (criteria.GetLength()>0 && criteria[0] == '\"') {
- search = criteria.Find("\"", 1);
- …
Large files files are truncated, but you can click here to view the full file