/*---------------------------------------------------------------------- | PLT_MediaItemResource::PLT_MediaItemResource +---------------------------------------------------------------------*/ PLT_MediaItemResource::PLT_MediaItemResource() { m_Uri = ""; m_ProtocolInfo = PLT_ProtocolInfo(); m_Duration = (NPT_UInt32)-1; m_Size = (NPT_LargeSize)-1; m_Protection = ""; m_Bitrate = (NPT_UInt32)-1; m_BitsPerSample = (NPT_UInt32)-1; m_SampleFrequency = (NPT_UInt32)-1; m_NbAudioChannels = (NPT_UInt32)-1; m_Resolution = ""; m_ColorDepth = (NPT_UInt32)-1; }
int CUPnPPlayer::PlayFile(const CFileItem& file, const CPlayerOptions& options, CGUIDialogBusy*& dialog, XbmcThreads::EndTime& timeout) { CFileItem item(file); NPT_Reference<CThumbLoader> thumb_loader; NPT_Reference<PLT_MediaObject> obj; NPT_String path(file.GetPath().c_str()); NPT_String tmp, resource; EMediaControllerQuirks quirks = EMEDIACONTROLLERQUIRKS_NONE; NPT_CHECK_POINTER_LABEL_SEVERE(m_delegate, failed); if (file.IsVideoDb()) thumb_loader = NPT_Reference<CThumbLoader>(new CVideoThumbLoader()); else if (item.IsMusicDb()) thumb_loader = NPT_Reference<CThumbLoader>(new CMusicThumbLoader()); obj = BuildObject(item, path, false, thumb_loader, NULL, CUPnP::GetServer(), UPnPPlayer); if(obj.IsNull()) goto failed; NPT_CHECK_LABEL_SEVERE(PLT_Didl::ToDidl(*obj, "", tmp), failed_todidl); tmp.Insert(didl_header, 0); tmp.Append(didl_footer); quirks = GetMediaControllerQuirks(m_delegate->m_device.AsPointer()); if (quirks & EMEDIACONTROLLERQUIRKS_X_MKV) { for (NPT_Cardinal i=0; i< obj->m_Resources.GetItemCount(); i++) { if (obj->m_Resources[i].m_ProtocolInfo.GetContentType().Compare("video/x-matroska") == 0) { CLog::Log(LOGDEBUG, "CUPnPPlayer::PlayFile(%s): applying video/x-mkv quirk", file.GetPath().c_str()); NPT_String protocolInfo = obj->m_Resources[i].m_ProtocolInfo.ToString(); protocolInfo.Replace(":video/x-matroska:", ":video/x-mkv:"); obj->m_Resources[i].m_ProtocolInfo = PLT_ProtocolInfo(protocolInfo); } } } /* The resource uri's are stored in the Didl. We must choose the best resource * for the playback device */ NPT_Cardinal res_index; NPT_CHECK_LABEL_SEVERE(m_control->FindBestResource(m_delegate->m_device, *obj, res_index), failed_findbestresource); // get the transport info to evaluate the TransportState to be able to // determine whether we first need to call Stop() timeout.Set(timeout.GetInitialTimeoutValue()); NPT_CHECK_LABEL_SEVERE(m_control->GetTransportInfo(m_delegate->m_device , m_delegate->m_instance , m_delegate), failed_gettransportinfo); NPT_CHECK_LABEL_SEVERE(WaitOnEvent(m_delegate->m_traevnt, timeout, dialog), failed_gettransportinfo); if (m_delegate->m_trainfo.cur_transport_state != "NO_MEDIA_PRESENT" && m_delegate->m_trainfo.cur_transport_state != "STOPPED") { timeout.Set(timeout.GetInitialTimeoutValue()); NPT_CHECK_LABEL_SEVERE(m_control->Stop(m_delegate->m_device , m_delegate->m_instance , m_delegate), failed_stop); NPT_CHECK_LABEL_SEVERE(WaitOnEvent(m_delegate->m_resevent, timeout, dialog), failed_stop); NPT_CHECK_LABEL_SEVERE(m_delegate->m_resstatus, failed_stop); } timeout.Set(timeout.GetInitialTimeoutValue()); NPT_CHECK_LABEL_SEVERE(m_control->SetAVTransportURI(m_delegate->m_device , m_delegate->m_instance , obj->m_Resources[res_index].m_Uri , (const char*)tmp , m_delegate), failed_setavtransporturi); NPT_CHECK_LABEL_SEVERE(WaitOnEvent(m_delegate->m_resevent, timeout, dialog), failed_setavtransporturi); NPT_CHECK_LABEL_SEVERE(m_delegate->m_resstatus, failed_setavtransporturi); timeout.Set(timeout.GetInitialTimeoutValue()); NPT_CHECK_LABEL_SEVERE(m_control->Play(m_delegate->m_device , m_delegate->m_instance , "1" , m_delegate), failed_play); NPT_CHECK_LABEL_SEVERE(WaitOnEvent(m_delegate->m_resevent, timeout, dialog), failed_play); NPT_CHECK_LABEL_SEVERE(m_delegate->m_resstatus, failed_play); /* wait for PLAYING state */ timeout.Set(timeout.GetInitialTimeoutValue()); do { NPT_CHECK_LABEL_SEVERE(m_control->GetTransportInfo(m_delegate->m_device , m_delegate->m_instance , m_delegate), failed_waitplaying); { CSingleLock lock(m_delegate->m_section); if(m_delegate->m_trainfo.cur_transport_state == "PLAYING" || m_delegate->m_trainfo.cur_transport_state == "PAUSED_PLAYBACK") break; if(m_delegate->m_trainfo.cur_transport_state == "STOPPED" && m_delegate->m_trainfo.cur_transport_status != "OK") { CLog::Log(LOGERROR, "UPNP: CUPnPPlayer::OpenFile - remote player signalled error %s", file.GetPath().c_str()); return NPT_FAILURE; } } NPT_CHECK_LABEL_SEVERE(WaitOnEvent(m_delegate->m_traevnt, timeout, dialog), failed_waitplaying); } while(!timeout.IsTimePast()); if(options.starttime > 0) { /* many upnp units won't load file properly until after play (including xbmc) */ NPT_CHECK_LABEL(m_control->Seek(m_delegate->m_device , m_delegate->m_instance , "REL_TIME" , PLT_Didl::FormatTimeStamp((NPT_UInt32)options.starttime) , m_delegate), failed_seek); } return NPT_SUCCESS; failed_todidl: CLog::Log(LOGERROR, "CUPnPPlayer::PlayFile(%s) failed to serialize item into DIDL-Lite", file.GetPath().c_str()); return NPT_FAILURE; failed_findbestresource: CLog::Log(LOGERROR, "CUPnPPlayer::PlayFile(%s) failed to find a matching resource", file.GetPath().c_str()); return NPT_FAILURE; failed_gettransportinfo: CLog::Log(LOGERROR, "CUPnPPlayer::PlayFile(%s): call to GetTransportInfo failed", file.GetPath().c_str()); return NPT_FAILURE; failed_stop: CLog::Log(LOGERROR, "CUPnPPlayer::PlayFile(%s) failed to stop current playback", file.GetPath().c_str()); return NPT_FAILURE; failed_setavtransporturi: CLog::Log(LOGERROR, "CUPnPPlayer::PlayFile(%s) failed to set the playback URI", file.GetPath().c_str()); return NPT_FAILURE; failed_play: CLog::Log(LOGERROR, "CUPnPPlayer::PlayFile(%s) failed to start playback", file.GetPath().c_str()); return NPT_FAILURE; failed_waitplaying: CLog::Log(LOGERROR, "CUPnPPlayer::PlayFile(%s) failed to wait for PLAYING state", file.GetPath().c_str()); return NPT_FAILURE; failed_seek: CLog::Log(LOGERROR, "CUPnPPlayer::PlayFile(%s) failed to seek to start offset", file.GetPath().c_str()); return NPT_FAILURE; failed: CLog::Log(LOGERROR, "CUPnPPlayer::PlayFile(%s) failed", file.GetPath().c_str()); return NPT_FAILURE; }
/*---------------------------------------------------------------------- | CUPnPDirectory::GetDirectory +---------------------------------------------------------------------*/ bool CUPnPDirectory::GetResource(const CURL& path, CFileItem &item) { if(path.GetProtocol() != "upnp") return false; CUPnP* upnp = CUPnP::GetInstance(); if(!upnp) return false; CStdString uuid = path.GetHostName(); CStdString object = path.GetFileName(); object.TrimRight("/"); CURL::Decode(object); PLT_DeviceDataReference device; if(!FindDeviceWait(upnp, uuid.c_str(), device)) return false; PLT_MediaObjectListReference list; if (NPT_FAILED(upnp->m_MediaBrowser->BrowseSync(device, object.c_str(), list, true))) return false; PLT_MediaObjectList::Iterator entry = list->GetFirstItem(); if (entry == 0) return false; PLT_MediaItemResource resource; // look for a resource with "xbmc-get" protocol // if we can't find one, keep the first resource if(NPT_FAILED(NPT_ContainerFind((*entry)->m_Resources, CProtocolFinder("xbmc-get"), resource))) { if((*entry)->m_Resources.GetItemCount()) resource = (*entry)->m_Resources[0]; else return false; } // store original path so we remember it item.SetProperty("original_listitem_url", item.GetPath()); item.SetProperty("original_listitem_mime", item.GetMimeType(false)); // if it's an item, path is the first url to the item // we hope the server made the first one reachable for us // (it could be a format we dont know how to play however) item.SetPath((const char*) resource.m_Uri); // look for content type in protocol info if (resource.m_ProtocolInfo.IsValid()) { CLog::Log(LOGDEBUG, "CUPnPDirectory::GetResource - resource protocol info '%s'", (const char*)(resource.m_ProtocolInfo.ToString())); if (resource.m_ProtocolInfo.GetContentType().Compare("application/octet-stream") != 0) { item.SetMimeType((const char*)resource.m_ProtocolInfo.GetContentType()); } } else { CLog::Log(LOGERROR, "CUPnPDirectory::GetResource - invalid protocol info '%s'", (const char*)(resource.m_ProtocolInfo.ToString())); } // look for subtitles unsigned subs = 0; for(unsigned r = 0; r < (*entry)->m_Resources.GetItemCount(); r++) { PLT_MediaItemResource& res = (*entry)->m_Resources[r]; PLT_ProtocolInfo& info = res.m_ProtocolInfo; static const char* allowed[] = { "text/srt" , "text/ssa" , "text/sub" , "text/idx" }; for(unsigned type = 0; type < sizeof(allowed)/sizeof(allowed[0]); type++) { if(info.Match(PLT_ProtocolInfo("*", "*", allowed[type], "*"))) { CStdString prop; prop.Format("upnp:subtitle:%d", ++subs); item.SetProperty(prop, (const char*)res.m_Uri); break; } } } return true; }
/*---------------------------------------------------------------------- | PLT_MediaObject::FromDidl +---------------------------------------------------------------------*/ NPT_Result PLT_MediaObject::FromDidl(NPT_XmlElementNode* entry) { NPT_String str, xml; NPT_Array<NPT_XmlElementNode*> children; NPT_Result res; // check if item is restricted (is default true?) if (NPT_SUCCEEDED(PLT_XmlHelper::GetAttribute(entry, "restricted", str, "", 5))) { m_Restricted = PLT_Service::IsTrue(str); } // read non-required elements PLT_XmlHelper::GetChildText(entry, "creator", m_Creator, didl_namespace_dc, 256); PLT_XmlHelper::GetChildText(entry, "date", m_Date, didl_namespace_dc, 256); // parse date and make sure it's valid NPT_String parsed_date; for (int format=0; format<=NPT_DateTime::FORMAT_RFC_1036; format++) { NPT_DateTime date; if (NPT_SUCCEEDED(date.FromString(m_Date, (NPT_DateTime::Format)format))) { parsed_date = date.ToString((NPT_DateTime::Format)format); break; } } m_Date = parsed_date; res = PLT_XmlHelper::GetAttribute(entry, "id", m_ObjectID); NPT_CHECK_SEVERE(res); res = PLT_XmlHelper::GetAttribute(entry, "parentID", m_ParentID); NPT_CHECK_SEVERE(res); PLT_XmlHelper::GetAttribute(entry, "refID", m_ReferenceID); res = PLT_XmlHelper::GetChildText(entry, "title", m_Title, didl_namespace_dc); NPT_CHECK_SEVERE(res); res = PLT_XmlHelper::GetChildText(entry, "class", m_ObjectClass.type, didl_namespace_upnp); NPT_CHECK_SEVERE(res); // DLNA 7.3.17.3 max bytes for dc:title and upnp:class is 256 bytes m_Title = m_Title.SubString(0, 256); m_ObjectClass.type = m_ObjectClass.type.SubString(0, 256); children.Clear(); PLT_XmlHelper::GetChildren(entry, children, "artist", didl_namespace_upnp); m_People.artists.FromDidl(children); children.Clear(); PLT_XmlHelper::GetChildren(entry, children, "author", didl_namespace_upnp); m_People.authors.FromDidl(children); children.Clear(); PLT_XmlHelper::GetChildren(entry, children, "actors", didl_namespace_upnp); m_People.actors.FromDidl(children); children.Clear(); PLT_XmlHelper::GetChildren(entry, children, "director", didl_namespace_upnp); m_People.directors.FromDidl(children); PLT_XmlHelper::GetChildText(entry, "album", m_Affiliation.album, didl_namespace_upnp, 256); PLT_XmlHelper::GetChildText(entry, "programTitle", m_Recorded.program_title, didl_namespace_upnp); PLT_XmlHelper::GetChildText(entry, "seriesTitle", m_Recorded.series_title, didl_namespace_upnp); PLT_XmlHelper::GetChildText(entry, "episodeNumber", str, didl_namespace_upnp); NPT_UInt32 value; if (NPT_FAILED(str.ToInteger(value))) value = 0; m_Recorded.episode_number = value; children.Clear(); PLT_XmlHelper::GetChildren(entry, children, "genre", didl_namespace_upnp); for (NPT_Cardinal i=0; i<children.GetItemCount(); i++) { if (children[i]->GetText()) { m_Affiliation.genres.Add(children[i]->GetText()->SubString(0, 256)); } } PLT_XmlHelper::GetChildText(entry, "description", m_Description.description, didl_namespace_dc); PLT_XmlHelper::GetChildText(entry, "longDescription", m_Description.long_description, didl_namespace_upnp); PLT_XmlHelper::GetChildText(entry, "icon", m_Description.icon_uri, didl_namespace_upnp); PLT_XmlHelper::GetChildText(entry, "rating", m_Description.rating, didl_namespace_upnp); PLT_XmlHelper::GetChildText(entry, "toc", m_MiscInfo.toc, didl_namespace_upnp); // album arts children.Clear(); PLT_XmlHelper::GetChildren(entry, children, "albumArtURI", didl_namespace_upnp); for (NPT_Cardinal i=0; i<children.GetItemCount(); i++) { if (children[i]->GetText()) { PLT_AlbumArtInfo info; info.uri = children[i]->GetText()->SubString(0, 1024); PLT_XmlHelper::GetAttribute(children[i], "profileID", info.dlna_profile, didl_namespace_dlna); m_ExtraInfo.album_arts.Add(info); } } PLT_XmlHelper::GetChildText(entry, "originalTrackNumber", str, didl_namespace_upnp); if (NPT_FAILED(str.ToInteger(value))) value = 0; m_MiscInfo.original_track_number = value; PLT_XmlHelper::GetChildText(entry, "lastPlaybackPosition", str, didl_namespace_upnp); if (NPT_FAILED(str.ToInteger(value))) value = 0; m_MiscInfo.last_position = value; PLT_XmlHelper::GetChildText(entry, "lastPlaybackTime", m_MiscInfo.last_time, didl_namespace_dc, 256); NPT_String parsed_last_time; for (int format=0; format<=NPT_DateTime::FORMAT_RFC_1036; format++) { NPT_DateTime date; if (NPT_SUCCEEDED(date.FromString(m_MiscInfo.last_time, (NPT_DateTime::Format)format))) { parsed_last_time = date.ToString((NPT_DateTime::Format)format); break; } } m_MiscInfo.last_time = parsed_last_time; PLT_XmlHelper::GetChildText(entry, "playbackCount", str, didl_namespace_upnp); if (NPT_FAILED(str.ToInteger(value))) value = -1; m_MiscInfo.play_count = value; children.Clear(); PLT_XmlHelper::GetChildren(entry, children, "res"); for (NPT_Cardinal i=0; i<children.GetItemCount(); i++) { PLT_MediaItemResource resource; // extract url if (children[i]->GetText() == NULL) { NPT_LOG_WARNING_1("No resource text found in: %s", (const char*)PLT_XmlHelper::Serialize(*children[i])); } else { resource.m_Uri = children[i]->GetText()->SubString(0, 1024); // basic uri validation, ignoring scheme (could be rtsp) NPT_HttpUrl url(resource.m_Uri, true); if (!url.IsValid()) { NPT_LOG_WARNING_1("Invalid resource uri: %s", (const char*)resource.m_Uri); continue; } } // extract protocol info NPT_String protocol_info; res = PLT_XmlHelper::GetAttribute(children[i], "protocolInfo", protocol_info, "", 256); if (NPT_FAILED(res)) { NPT_LOG_WARNING_1("No protocol info found in: %s", (const char*)PLT_XmlHelper::Serialize(*children[i])); } else { resource.m_ProtocolInfo = PLT_ProtocolInfo(protocol_info); if (!resource.m_ProtocolInfo.IsValid()) { NPT_LOG_WARNING_1("Invalid resource protocol info: %s", (const char*)protocol_info); } } // extract known attributes PLT_XmlHelper::GetAttribute(children[i], "protection", resource.m_Protection, "", 256); PLT_XmlHelper::GetAttribute(children[i], "resolution", resource.m_Resolution, "", 256); if (NPT_SUCCEEDED(PLT_XmlHelper::GetAttribute(children[i], "size", str, "", 256))) { if (NPT_FAILED(str.ToInteger64(resource.m_Size))) resource.m_Size = (NPT_Size)-1; } if (NPT_SUCCEEDED(PLT_XmlHelper::GetAttribute(children[i], "duration", str, "", 256))) { if (NPT_FAILED(PLT_Didl::ParseTimeStamp(str, resource.m_Duration))) { // if error while converting, ignore and set to -1 to indicate we don't know the duration resource.m_Duration = (NPT_UInt32)-1; PLT_XmlHelper::RemoveAttribute(children[i], "duration"); } else { // DLNA: reformat duration in case it was not compliant str = PLT_Didl::FormatTimeStamp(resource.m_Duration); PLT_XmlHelper::SetAttribute(children[i], "duration", str); } } m_Resources.Add(resource); } // re serialize the entry didl as a we might need to pass it to a renderer // we may have modified the tree to "fix" issues, so as not to break a renderer // (don't write xml prefix as this didl could be part of a larger document) //res = PLT_XmlHelper::Serialize(*entry, xml, false); m_Didl = ""; res = ToDidl(PLT_FILTER_MASK_ALL, m_Didl); NPT_CHECK_SEVERE(res); m_Didl = didl_header + m_Didl + didl_footer; return NPT_SUCCESS; }
/*---------------------------------------------------------------------- | CUPnPDirectory::GetDirectory +---------------------------------------------------------------------*/ bool CUPnPDirectory::GetDirectory(const CStdString& strPath, CFileItemList &items) { CUPnP* upnp = CUPnP::GetInstance(); /* upnp should never be cached, it has internal cache */ items.SetCacheToDisc(CFileItemList::CACHE_NEVER); // start client if it hasn't been done yet bool client_started = upnp->IsClientStarted(); upnp->StartClient(); // We accept upnp://devuuid/[item_id/] NPT_String path = strPath.c_str(); if (!path.StartsWith("upnp://", true)) { return false; } if (path.Compare("upnp://", true) == 0) { // root -> get list of devices const NPT_Lock<PLT_DeviceDataReferenceList>& devices = upnp->m_MediaBrowser->GetMediaServers(); NPT_List<PLT_DeviceDataReference>::Iterator device = devices.GetFirstItem(); while (device) { NPT_String name = (*device)->GetFriendlyName(); NPT_String uuid = (*device)->GetUUID(); CFileItemPtr pItem(new CFileItem((const char*)name)); pItem->m_strPath = (const char*) "upnp://" + uuid + "/"; pItem->m_bIsFolder = true; pItem->SetThumbnailImage((const char*)(*device)->GetIconUrl("image/jpeg")); items.Add(pItem); ++device; } } else { if (!path.EndsWith("/")) path += "/"; // look for nextslash int next_slash = path.Find('/', 7); NPT_String uuid = (next_slash==-1)?path.SubString(7):path.SubString(7, next_slash-7); NPT_String object_id = (next_slash==-1)?"":path.SubString(next_slash+1); object_id.TrimRight("/"); if (object_id.GetLength()) { CStdString tmp = (char*) object_id; CUtil::URLDecode(tmp); object_id = tmp; } // look for device in our list // (and wait for it to respond for 5 secs if we're just starting upnp client) NPT_TimeStamp watchdog; NPT_System::GetCurrentTimeStamp(watchdog); watchdog += 5.f; PLT_DeviceDataReference device; for (;;) { if (NPT_SUCCEEDED(upnp->m_MediaBrowser->FindServer(uuid, device)) && !device.IsNull()) break; // fail right away if device not found and upnp client was already running if (client_started) goto failure; // otherwise check if we've waited long enough without success NPT_TimeStamp now; NPT_System::GetCurrentTimeStamp(now); if (now > watchdog) goto failure; // sleep a bit and try again NPT_System::Sleep(NPT_TimeInterval(1, 0)); } // issue a browse request with object_id // if object_id is empty use "0" for root object_id = object_id.IsEmpty()?"0":object_id; // just a guess as to what types of files we want bool video = true; bool audio = true; bool image = true; m_strFileMask.TrimLeft("/"); if (!m_strFileMask.IsEmpty()) { video = m_strFileMask.Find(".wmv") >= 0; audio = m_strFileMask.Find(".wma") >= 0; image = m_strFileMask.Find(".jpg") >= 0; } // special case for Windows Media Connect and WMP11 when looking for root // We can target which root subfolder we want based on directory mask if (object_id == "0" && ((device->GetFriendlyName().Find("Windows Media Connect", 0, true) >= 0) || (device->m_ModelName == "Windows Media Player Sharing"))) { // look for a specific type to differentiate which folder we want if (audio && !video && !image) { // music object_id = "1"; } else if (!audio && video && !image) { // video object_id = "2"; } else if (!audio && !video && image) { // pictures object_id = "3"; } } #ifdef DISABLE_SPECIALCASE // same thing but special case for XBMC if (object_id == "0" && ((device->m_ModelName.Find("XBMC", 0, true) >= 0) || (device->m_ModelName.Find("Xbox Media Center", 0, true) >= 0))) { // look for a specific type to differentiate which folder we want if (audio && !video && !image) { // music object_id = "virtualpath://upnpmusic"; } else if (!audio && video && !image) { // video object_id = "virtualpath://upnpvideo"; } else if (!audio && !video && image) { // pictures object_id = "virtualpath://upnppictures"; } } #endif // if error, return now, the device could have gone away // this will make us go back to the sources list PLT_MediaObjectListReference list; NPT_Result res = upnp->m_MediaBrowser->BrowseSync(device, object_id, list); if (NPT_FAILED(res)) goto failure; // empty list is ok if (list.IsNull()) goto cleanup; PLT_MediaObjectList::Iterator entry = list->GetFirstItem(); while (entry) { // disregard items with wrong class/type if( (!video && (*entry)->m_ObjectClass.type.CompareN("object.item.videoitem", 21,true) == 0) || (!audio && (*entry)->m_ObjectClass.type.CompareN("object.item.audioitem", 21,true) == 0) || (!image && (*entry)->m_ObjectClass.type.CompareN("object.item.imageitem", 21,true) == 0) ) { ++entry; continue; } // never show empty containers in media views if((*entry)->IsContainer()) { if( (audio || video || image) && ((PLT_MediaContainer*)(*entry))->m_ChildrenCount == 0) { ++entry; continue; } } CFileItemPtr pItem(new CFileItem((const char*)(*entry)->m_Title)); pItem->SetLabelPreformated(true); pItem->m_strTitle = (const char*)(*entry)->m_Title; pItem->m_bIsFolder = (*entry)->IsContainer(); // if it's a container, format a string as upnp://uuid/object_id if (pItem->m_bIsFolder) { CStdString id = (char*) (*entry)->m_ObjectID; CUtil::URLEncode(id); pItem->m_strPath = (const char*) "upnp://" + uuid + "/" + id.c_str() + "/"; } else { if ((*entry)->m_Resources.GetItemCount()) { PLT_MediaItemResource& resource = (*entry)->m_Resources[0]; // look for a resource with "xbmc-get" protocol // if we can't find one, keep the first resource NPT_ContainerFind((*entry)->m_Resources, CProtocolFinder("xbmc-get"), resource); CLog::Log(LOGDEBUG, "CUPnPDirectory::GetDirectory - resource protocol info '%s'", (const char*)(resource.m_ProtocolInfo.ToString())); // if it's an item, path is the first url to the item // we hope the server made the first one reachable for us // (it could be a format we dont know how to play however) pItem->m_strPath = (const char*) resource.m_Uri; // set metadata if (resource.m_Size != (NPT_LargeSize)-1) { pItem->m_dwSize = resource.m_Size; } // set a general content type CStdString type = (const char*)(*entry)->m_ObjectClass.type.Left(21); if (type.Equals("object.item.videoitem")) pItem->SetMimeType("video/octet-stream"); else if(type.Equals("object.item.audioitem")) pItem->SetMimeType("audio/octet-stream"); else if(type.Equals("object.item.imageitem")) pItem->SetMimeType("image/octet-stream"); // look for content type in protocol info if (resource.m_ProtocolInfo.IsValid()) { if (resource.m_ProtocolInfo.GetContentType().Compare("application/octet-stream") != 0) { pItem->SetMimeType((const char*)resource.m_ProtocolInfo.GetContentType()); } } else { CLog::Log(LOGERROR, "CUPnPDirectory::GetDirectory - invalid protocol info '%s'", (const char*)(resource.m_ProtocolInfo.ToString())); } // look for date? if((*entry)->m_Description.date.GetLength()) { SYSTEMTIME time = {}; sscanf((*entry)->m_Description.date, "%hu-%hu-%huT%hu:%hu:%hu", &time.wYear, &time.wMonth, &time.wDay, &time.wHour, &time.wMinute, &time.wSecond); pItem->m_dateTime = time; } // look for metadata if( (*entry)->m_ObjectClass.type.CompareN("object.item.videoitem", 21,true) == 0 ) { pItem->SetLabelPreformated(false); CUPnP::PopulateTagFromObject(*pItem->GetVideoInfoTag(), *(*entry), &resource); } else if( (*entry)->m_ObjectClass.type.CompareN("object.item.audioitem", 21,true) == 0 ) { pItem->SetLabelPreformated(false); CUPnP::PopulateTagFromObject(*pItem->GetMusicInfoTag(), *(*entry), &resource); } else if( (*entry)->m_ObjectClass.type.CompareN("object.item.imageitem", 21,true) == 0 ) { //CPictureInfoTag* tag = pItem->GetPictureInfoTag(); } } // look for subtitles unsigned subs = 0; for(unsigned r = 0; r < (*entry)->m_Resources.GetItemCount(); r++) { PLT_MediaItemResource& res = (*entry)->m_Resources[r]; PLT_ProtocolInfo& info = res.m_ProtocolInfo; static const char* allowed[] = { "text/srt" , "text/ssa" , "text/sub" , "text/idx" }; for(unsigned type = 0; type < sizeof(allowed)/sizeof(allowed[0]); type++) { if(info.Match(PLT_ProtocolInfo("*", "*", allowed[type], "*"))) { CStdString prop; prop.Format("upnp:subtitle:%d", ++subs); pItem->SetProperty(prop, (const char*)res.m_Uri); break; } } } } // if there is a thumbnail available set it here if((*entry)->m_ExtraInfo.album_art_uri.GetLength()) pItem->SetThumbnailImage((const char*) (*entry)->m_ExtraInfo.album_art_uri); else if((*entry)->m_Description.icon_uri.GetLength()) pItem->SetThumbnailImage((const char*) (*entry)->m_Description.icon_uri); items.Add(pItem); ++entry; } } cleanup: return true; failure: return false; }
/*---------------------------------------------------------------------- | PLT_MediaObject::FromDidl +---------------------------------------------------------------------*/ NPT_Result PLT_MediaObject::FromDidl(NPT_XmlElementNode* entry) { NPT_String str, xml; NPT_Array<NPT_XmlElementNode*> children; NPT_Result res; // check if item is restricted (is default true?) if (NPT_SUCCEEDED(PLT_XmlHelper::GetAttribute(entry, "restricted", str))) { m_Restricted = PLT_Service::IsTrue(str); } res = PLT_XmlHelper::GetAttribute(entry, "id", m_ObjectID); NPT_CHECK_LABEL_SEVERE(res, cleanup); res = PLT_XmlHelper::GetAttribute(entry, "parentID", m_ParentID); NPT_CHECK_LABEL_SEVERE(res, cleanup); res = PLT_XmlHelper::GetAttribute(entry, "refID", m_ReferenceID); res = PLT_XmlHelper::GetChildText(entry, "title", m_Title, didl_namespace_dc); NPT_CHECK_LABEL_SEVERE(res, cleanup); res = PLT_XmlHelper::GetChildText(entry, "class", m_ObjectClass.type, didl_namespace_upnp); NPT_CHECK_LABEL_SEVERE(res, cleanup); // read non-required elements PLT_XmlHelper::GetChildText(entry, "creator", m_Creator, didl_namespace_dc); PLT_XmlHelper::GetChildText(entry, "date", m_Date, didl_namespace_dc); PLT_XmlHelper::GetChildren(entry, children, "artist", didl_namespace_upnp); m_People.artists.FromDidl(children); PLT_XmlHelper::GetChildText(entry, "album", m_Affiliation.album, didl_namespace_upnp); PLT_XmlHelper::GetChildText(entry, "programTitle", m_Recorded.program_title, didl_namespace_upnp); PLT_XmlHelper::GetChildText(entry, "seriesTitle", m_Recorded.series_title, didl_namespace_upnp); PLT_XmlHelper::GetChildText(entry, "episodeNumber", str, didl_namespace_upnp); NPT_UInt32 value; if (NPT_FAILED(str.ToInteger(value))) value = 0; m_Recorded.episode_number = value; children.Clear(); PLT_XmlHelper::GetChildren(entry, children, "genre", didl_namespace_upnp); for (NPT_Cardinal i=0; i<children.GetItemCount(); i++) { if (children[i]->GetText()) { m_Affiliation.genres.Add(*children[i]->GetText()); } } PLT_XmlHelper::GetChildText(entry, "albumArtURI", m_ExtraInfo.album_art_uri, didl_namespace_upnp); PLT_XmlHelper::GetChildText(entry, "longDescription", m_Description.long_description, didl_namespace_upnp); PLT_XmlHelper::GetChildText(entry, "toc", m_MiscInfo.toc, didl_namespace_upnp); PLT_XmlHelper::GetChildText(entry, "originalTrackNumber", str, didl_namespace_upnp); if (NPT_FAILED(str.ToInteger(value))) value = 0; m_MiscInfo.original_track_number = value; children.Clear(); PLT_XmlHelper::GetChildren(entry, children, "res"); for (NPT_Cardinal i=0; i<children.GetItemCount(); i++) { PLT_MediaItemResource resource; // extract url if (children[i]->GetText() == NULL) { NPT_LOG_WARNING_1("No resource text found in: %s", (const char*)PLT_XmlHelper::Serialize(*children[i])); } else { resource.m_Uri = *children[i]->GetText(); // basic uri validation, ignoring scheme (could be rtsp) NPT_HttpUrl url(resource.m_Uri, true); if (!url.IsValid()) { NPT_LOG_WARNING_1("Invalid resource uri: %s", (const char*)resource.m_Uri); } } // extract protocol info NPT_String protocol_info; res = PLT_XmlHelper::GetAttribute(children[i], "protocolInfo", protocol_info); if (NPT_FAILED(res)) { NPT_LOG_WARNING_1("No protocol info found in: %s", (const char*)PLT_XmlHelper::Serialize(*children[i])); } else { resource.m_ProtocolInfo = PLT_ProtocolInfo(protocol_info); if (!resource.m_ProtocolInfo.IsValid()) { NPT_LOG_WARNING_1("Invalid resource protocol info: %s", (const char*)protocol_info); } } // extract known attributes PLT_XmlHelper::GetAttribute(children[i], "protection", resource.m_Protection); PLT_XmlHelper::GetAttribute(children[i], "resolution", resource.m_Resolution); if (NPT_SUCCEEDED(PLT_XmlHelper::GetAttribute(children[i], "size", str))) { if (NPT_FAILED(str.ToInteger64(resource.m_Size))) resource.m_Size = (NPT_Size)-1; } if (NPT_SUCCEEDED(PLT_XmlHelper::GetAttribute(children[i], "duration", str))) { if (NPT_FAILED(PLT_Didl::ParseTimeStamp(str, resource.m_Duration))) { // if error while converting, ignore and set to -1 to indicate we don't know the duration resource.m_Duration = (NPT_UInt32)-1; PLT_XmlHelper::RemoveAttribute(children[i], "duration"); } else { // DLNA: reformat duration in case it was not compliant str = PLT_Didl::FormatTimeStamp(resource.m_Duration); PLT_XmlHelper::SetAttribute(children[i], "duration", str); } } m_Resources.Add(resource); } // re serialize the entry didl as a we might need to pass it to a renderer // we may have modified the tree to "fix" issues, so as not to break a renderer // (don't write xml prefix as this didl could be part of a larger document) res = PLT_XmlHelper::Serialize(*entry, xml, false); NPT_CHECK_LABEL_SEVERE(res, cleanup); m_Didl = didl_header + xml + didl_footer; return NPT_SUCCESS; cleanup: return res; }