ControlHandler.cs 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106
  1. using MediaBrowser.Common.Extensions;
  2. using MediaBrowser.Common.Net;
  3. using MediaBrowser.Controller.Dlna;
  4. using MediaBrowser.Controller.Drawing;
  5. using MediaBrowser.Controller.Dto;
  6. using MediaBrowser.Controller.Entities;
  7. using MediaBrowser.Controller.Entities.Audio;
  8. using MediaBrowser.Controller.Entities.Movies;
  9. using MediaBrowser.Controller.Entities.TV;
  10. using MediaBrowser.Controller.Library;
  11. using MediaBrowser.Model.Dlna;
  12. using MediaBrowser.Model.Drawing;
  13. using MediaBrowser.Model.Entities;
  14. using MediaBrowser.Model.Logging;
  15. using MediaBrowser.Model.Querying;
  16. using System;
  17. using System.Collections.Generic;
  18. using System.Globalization;
  19. using System.Linq;
  20. using System.Text;
  21. using System.Threading;
  22. using System.Xml;
  23. namespace MediaBrowser.Dlna.Server
  24. {
  25. public class ControlHandler
  26. {
  27. private readonly ILogger _logger;
  28. private readonly ILibraryManager _libraryManager;
  29. private readonly DeviceProfile _profile;
  30. private readonly IDtoService _dtoService;
  31. private readonly IImageProcessor _imageProcessor;
  32. private readonly IUserDataManager _userDataManager;
  33. private readonly User _user;
  34. private readonly string _serverAddress;
  35. private const string NS_DC = "http://purl.org/dc/elements/1.1/";
  36. private const string NS_DIDL = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
  37. private const string NS_DLNA = "urn:schemas-dlna-org:metadata-1-0/";
  38. private const string NS_SEC = "http://www.sec.co.kr/";
  39. private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
  40. private const string NS_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/";
  41. private int systemID = 0;
  42. private readonly CultureInfo _usCulture = new CultureInfo("en-US");
  43. public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, IDtoService dtoService, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user)
  44. {
  45. _logger = logger;
  46. _libraryManager = libraryManager;
  47. _profile = profile;
  48. _serverAddress = serverAddress;
  49. _dtoService = dtoService;
  50. _imageProcessor = imageProcessor;
  51. _userDataManager = userDataManager;
  52. _user = user;
  53. }
  54. public ControlResponse ProcessControlRequest(ControlRequest request)
  55. {
  56. try
  57. {
  58. return ProcessControlRequestInternal(request);
  59. }
  60. catch (Exception ex)
  61. {
  62. _logger.ErrorException("Error processing control request", ex);
  63. return GetErrorResponse(ex);
  64. }
  65. }
  66. private ControlResponse ProcessControlRequestInternal(ControlRequest request)
  67. {
  68. var soap = new XmlDocument();
  69. soap.LoadXml(request.InputXml);
  70. var sparams = new Headers();
  71. var body = soap.GetElementsByTagName("Body", NS_SOAPENV).Item(0);
  72. var method = body.FirstChild;
  73. foreach (var p in method.ChildNodes)
  74. {
  75. var e = p as XmlElement;
  76. if (e == null)
  77. {
  78. continue;
  79. }
  80. sparams.Add(e.LocalName, e.InnerText.Trim());
  81. }
  82. var deviceId = "fgd";
  83. var env = new XmlDocument();
  84. env.AppendChild(env.CreateXmlDeclaration("1.0", "utf-8", "yes"));
  85. var envelope = env.CreateElement("SOAP-ENV", "Envelope", NS_SOAPENV);
  86. env.AppendChild(envelope);
  87. envelope.SetAttribute("encodingStyle", NS_SOAPENV, "http://schemas.xmlsoap.org/soap/encoding/");
  88. var rbody = env.CreateElement("SOAP-ENV:Body", NS_SOAPENV);
  89. env.DocumentElement.AppendChild(rbody);
  90. IEnumerable<KeyValuePair<string, string>> result;
  91. _logger.Debug("Received control request {0}", method.Name);
  92. var user = _user;
  93. if (string.Equals(method.LocalName, "GetSearchCapabilities", StringComparison.OrdinalIgnoreCase))
  94. result = HandleGetSearchCapabilities();
  95. else if (string.Equals(method.LocalName, "GetSortCapabilities", StringComparison.OrdinalIgnoreCase))
  96. result = HandleGetSortCapabilities();
  97. else if (string.Equals(method.LocalName, "GetSystemUpdateID", StringComparison.OrdinalIgnoreCase))
  98. result = HandleGetSystemUpdateID();
  99. else if (string.Equals(method.LocalName, "Browse", StringComparison.OrdinalIgnoreCase))
  100. result = HandleBrowse(sparams, user, deviceId);
  101. else if (string.Equals(method.LocalName, "X_GetFeatureList", StringComparison.OrdinalIgnoreCase))
  102. result = HandleXGetFeatureList();
  103. else if (string.Equals(method.LocalName, "X_SetBookmark", StringComparison.OrdinalIgnoreCase))
  104. result = HandleXSetBookmark(sparams, user);
  105. else if (string.Equals(method.LocalName, "Search", StringComparison.OrdinalIgnoreCase))
  106. result = HandleSearch(sparams, user, deviceId);
  107. else
  108. throw new ResourceNotFoundException("Unexpected control request name: " + method.LocalName);
  109. var response = env.CreateElement(String.Format("u:{0}Response", method.LocalName), method.NamespaceURI);
  110. rbody.AppendChild(response);
  111. foreach (var i in result)
  112. {
  113. var ri = env.CreateElement(i.Key);
  114. ri.InnerText = i.Value;
  115. response.AppendChild(ri);
  116. }
  117. var controlResponse = new ControlResponse
  118. {
  119. Xml = env.OuterXml,
  120. IsSuccessful = true
  121. };
  122. controlResponse.Headers.Add("EXT", string.Empty);
  123. return controlResponse;
  124. }
  125. private ControlResponse GetErrorResponse(Exception ex)
  126. {
  127. var env = new XmlDocument();
  128. env.AppendChild(env.CreateXmlDeclaration("1.0", "utf-8", "yes"));
  129. var envelope = env.CreateElement("SOAP-ENV", "Envelope", NS_SOAPENV);
  130. env.AppendChild(envelope);
  131. envelope.SetAttribute("encodingStyle", NS_SOAPENV, "http://schemas.xmlsoap.org/soap/encoding/");
  132. var rbody = env.CreateElement("SOAP-ENV:Body", NS_SOAPENV);
  133. env.DocumentElement.AppendChild(rbody);
  134. var fault = env.CreateElement("SOAP-ENV", "Fault", NS_SOAPENV);
  135. var faultCode = env.CreateElement("faultcode");
  136. faultCode.InnerText = "500";
  137. fault.AppendChild(faultCode);
  138. var faultString = env.CreateElement("faultstring");
  139. faultString.InnerText = ex.ToString();
  140. fault.AppendChild(faultString);
  141. var detail = env.CreateDocumentFragment();
  142. detail.InnerXml = "<detail><UPnPError xmlns=\"urn:schemas-upnp-org:control-1-0\"><errorCode>401</errorCode><errorDescription>Invalid Action</errorDescription></UPnPError></detail>";
  143. fault.AppendChild(detail);
  144. rbody.AppendChild(fault);
  145. return new ControlResponse
  146. {
  147. Xml = env.OuterXml,
  148. IsSuccessful = false
  149. };
  150. }
  151. private IEnumerable<KeyValuePair<string, string>> HandleXSetBookmark(IDictionary<string, string> sparams, User user)
  152. {
  153. var id = sparams["ObjectID"];
  154. var item = GetItemFromObjectId(id, user);
  155. var newbookmark = int.Parse(sparams["PosSecond"], _usCulture);
  156. var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
  157. userdata.PlaybackPositionTicks = TimeSpan.FromSeconds(newbookmark).Ticks;
  158. _userDataManager.SaveUserData(user.Id, item, userdata, UserDataSaveReason.TogglePlayed,
  159. CancellationToken.None);
  160. return new Headers();
  161. }
  162. private IEnumerable<KeyValuePair<string, string>> HandleGetSearchCapabilities()
  163. {
  164. return new Headers { { "SearchCaps", string.Empty } };
  165. }
  166. private IEnumerable<KeyValuePair<string, string>> HandleGetSortCapabilities()
  167. {
  168. return new Headers { { "SortCaps", string.Empty } };
  169. }
  170. private IEnumerable<KeyValuePair<string, string>> HandleGetSystemUpdateID()
  171. {
  172. return new Headers { { "Id", systemID.ToString(_usCulture) } };
  173. }
  174. private IEnumerable<KeyValuePair<string, string>> HandleXGetFeatureList()
  175. {
  176. return new Headers { { "FeatureList", GetFeatureListXml() } };
  177. }
  178. private string GetFeatureListXml()
  179. {
  180. var builder = new StringBuilder();
  181. builder.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
  182. builder.Append("<Features xmlns=\"urn:schemas-upnp-org:av:avs\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"urn:schemas-upnp-org:av:avs http://www.upnp.org/schemas/av/avs.xsd\">");
  183. builder.Append("<Feature name=\"samsung.com_BASICVIEW\" version=\"1\">");
  184. builder.Append("<container id=\"I\" type=\"object.item.imageItem\"/>");
  185. builder.Append("<container id=\"A\" type=\"object.item.audioItem\"/>");
  186. builder.Append("<container id=\"V\" type=\"object.item.videoItem\"/>");
  187. builder.Append("</Feature>");
  188. builder.Append("</Features>");
  189. return builder.ToString();
  190. }
  191. private IEnumerable<KeyValuePair<string, string>> HandleBrowse(Headers sparams, User user, string deviceId)
  192. {
  193. var id = sparams["ObjectID"];
  194. var flag = sparams["BrowseFlag"];
  195. var filter = new Filter(sparams.GetValueOrDefault("Filter", "*"));
  196. var sortCriteria = new SortCriteria(sparams.GetValueOrDefault("SortCriteria", ""));
  197. var provided = 0;
  198. var requested = 0;
  199. var start = 0;
  200. if (sparams.ContainsKey("RequestedCount") && int.TryParse(sparams["RequestedCount"], out requested) && requested <= 0)
  201. {
  202. requested = 0;
  203. }
  204. if (sparams.ContainsKey("StartingIndex") && int.TryParse(sparams["StartingIndex"], out start) && start <= 0)
  205. {
  206. start = 0;
  207. }
  208. //var root = GetItem(id) as IMediaFolder;
  209. var result = new XmlDocument();
  210. var didl = result.CreateElement(string.Empty, "DIDL-Lite", NS_DIDL);
  211. didl.SetAttribute("xmlns:dc", NS_DC);
  212. didl.SetAttribute("xmlns:dlna", NS_DLNA);
  213. didl.SetAttribute("xmlns:upnp", NS_UPNP);
  214. didl.SetAttribute("xmlns:sec", NS_SEC);
  215. result.AppendChild(didl);
  216. var folder = (Folder)GetItemFromObjectId(id, user);
  217. var children = GetChildrenSorted(folder, user, sortCriteria).ToList();
  218. var totalCount = children.Count;
  219. if (string.Equals(flag, "BrowseMetadata"))
  220. {
  221. Browse_AddFolder(result, folder, children.Count, filter);
  222. provided++;
  223. }
  224. else
  225. {
  226. if (start > 0)
  227. {
  228. children = children.Skip(start).ToList();
  229. }
  230. if (requested > 0)
  231. {
  232. children = children.Take(requested).ToList();
  233. }
  234. provided = children.Count;
  235. foreach (var i in children)
  236. {
  237. if (i.IsFolder)
  238. {
  239. var f = (Folder)i;
  240. var childCount = GetChildrenSorted(f, user, sortCriteria).Count();
  241. Browse_AddFolder(result, f, childCount, filter);
  242. }
  243. else
  244. {
  245. Browse_AddItem(result, i, user, deviceId, filter);
  246. }
  247. }
  248. }
  249. var resXML = result.OuterXml;
  250. return new List<KeyValuePair<string, string>>
  251. {
  252. new KeyValuePair<string,string>("Result", resXML),
  253. new KeyValuePair<string,string>("NumberReturned", provided.ToString(_usCulture)),
  254. new KeyValuePair<string,string>("TotalMatches", totalCount.ToString(_usCulture)),
  255. new KeyValuePair<string,string>("UpdateID", systemID.ToString(_usCulture))
  256. };
  257. }
  258. private IEnumerable<KeyValuePair<string, string>> HandleSearch(Headers sparams, User user, string deviceId)
  259. {
  260. var searchCriteria = new SearchCriteria(sparams.GetValueOrDefault("SearchCriteria", ""));
  261. var sortCriteria = new SortCriteria(sparams.GetValueOrDefault("SortCriteria", ""));
  262. var filter = new Filter(sparams.GetValueOrDefault("Filter", "*"));
  263. // sort example: dc:title, dc:date
  264. var provided = 0;
  265. var requested = 0;
  266. var start = 0;
  267. if (sparams.ContainsKey("RequestedCount") && int.TryParse(sparams["RequestedCount"], out requested) && requested <= 0)
  268. {
  269. requested = 0;
  270. }
  271. if (sparams.ContainsKey("StartingIndex") && int.TryParse(sparams["StartingIndex"], out start) && start <= 0)
  272. {
  273. start = 0;
  274. }
  275. //var root = GetItem(id) as IMediaFolder;
  276. var result = new XmlDocument();
  277. var didl = result.CreateElement(string.Empty, "DIDL-Lite", NS_DIDL);
  278. didl.SetAttribute("xmlns:dc", NS_DC);
  279. didl.SetAttribute("xmlns:dlna", NS_DLNA);
  280. didl.SetAttribute("xmlns:upnp", NS_UPNP);
  281. didl.SetAttribute("xmlns:sec", NS_SEC);
  282. result.AppendChild(didl);
  283. var folder = (Folder)GetItemFromObjectId(sparams["ContainerID"], user);
  284. var children = GetChildrenSorted(folder, user, searchCriteria, sortCriteria).ToList();
  285. var totalCount = children.Count;
  286. if (start > 0)
  287. {
  288. children = children.Skip(start).ToList();
  289. }
  290. if (requested > 0)
  291. {
  292. children = children.Take(requested).ToList();
  293. }
  294. provided = children.Count;
  295. foreach (var i in children)
  296. {
  297. if (i.IsFolder)
  298. {
  299. var f = (Folder)i;
  300. var childCount = GetChildrenSorted(f, user, searchCriteria, sortCriteria).Count();
  301. Browse_AddFolder(result, f, childCount, filter);
  302. }
  303. else
  304. {
  305. Browse_AddItem(result, i, user, deviceId, filter);
  306. }
  307. }
  308. var resXML = result.OuterXml;
  309. return new List<KeyValuePair<string, string>>
  310. {
  311. new KeyValuePair<string,string>("Result", resXML),
  312. new KeyValuePair<string,string>("NumberReturned", provided.ToString(_usCulture)),
  313. new KeyValuePair<string,string>("TotalMatches", totalCount.ToString(_usCulture)),
  314. new KeyValuePair<string,string>("UpdateID", systemID.ToString(_usCulture))
  315. };
  316. }
  317. private IEnumerable<BaseItem> GetChildrenSorted(Folder folder, User user, SearchCriteria search, SortCriteria sort)
  318. {
  319. if (search.SearchType == SearchType.Unknown)
  320. {
  321. return GetChildrenSorted(folder, user, sort);
  322. }
  323. var items = folder.GetRecursiveChildren(user);
  324. items = FilterUnsupportedContent(items);
  325. if (search.SearchType == SearchType.Audio)
  326. {
  327. items = items.OfType<Audio>();
  328. }
  329. else if (search.SearchType == SearchType.Video)
  330. {
  331. items = items.OfType<Video>();
  332. }
  333. else if (search.SearchType == SearchType.Image)
  334. {
  335. items = items.OfType<Photo>();
  336. }
  337. else if (search.SearchType == SearchType.Playlist)
  338. {
  339. }
  340. return SortItems(items, user, sort);
  341. }
  342. private IEnumerable<BaseItem> GetChildrenSorted(Folder folder, User user, SortCriteria sort)
  343. {
  344. var items = folder.GetChildren(user, true);
  345. items = FilterUnsupportedContent(items);
  346. if (folder is Series || folder is Season || folder is BoxSet)
  347. {
  348. return items;
  349. }
  350. return SortItems(items, user, sort);
  351. }
  352. private IEnumerable<BaseItem> SortItems(IEnumerable<BaseItem> items, User user, SortCriteria sort)
  353. {
  354. return _libraryManager.Sort(items, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending);
  355. }
  356. private IEnumerable<BaseItem> FilterUnsupportedContent(IEnumerable<BaseItem> items)
  357. {
  358. return items.Where(i =>
  359. {
  360. // Unplayable
  361. // TODO: Display and prevent playback with restricted flag?
  362. if (i.LocationType == LocationType.Virtual)
  363. {
  364. return false;
  365. }
  366. // Unplayable
  367. // TODO: Display and prevent playback with restricted flag?
  368. var supportsPlaceHolder = i as ISupportsPlaceHolders;
  369. if (supportsPlaceHolder != null && supportsPlaceHolder.IsPlaceHolder)
  370. {
  371. return false;
  372. }
  373. // Upnp renderers won't understand these
  374. // TODO: Display and prevent playback with restricted flag?
  375. if (i is Game || i is Book)
  376. {
  377. return false;
  378. }
  379. return true;
  380. });
  381. }
  382. private BaseItem GetItemFromObjectId(string id, User user)
  383. {
  384. return string.IsNullOrWhiteSpace(id) || string.Equals(id, "0", StringComparison.OrdinalIgnoreCase)
  385. // Samsung sometimes uses 1 as root
  386. || string.Equals(id, "1", StringComparison.OrdinalIgnoreCase)
  387. ? user.RootFolder
  388. : _libraryManager.GetItemById(new Guid(id));
  389. }
  390. private void Browse_AddFolder(XmlDocument result, Folder f, int childCount, Filter filter)
  391. {
  392. var container = result.CreateElement(string.Empty, "container", NS_DIDL);
  393. container.SetAttribute("restricted", "0");
  394. container.SetAttribute("searchable", "1");
  395. container.SetAttribute("childCount", childCount.ToString(_usCulture));
  396. container.SetAttribute("id", f.Id.ToString("N"));
  397. var parent = f.Parent;
  398. if (parent == null)
  399. {
  400. container.SetAttribute("parentID", "0");
  401. }
  402. else
  403. {
  404. container.SetAttribute("parentID", parent.Id.ToString("N"));
  405. }
  406. AddCommonFields(f, container, filter);
  407. AddCover(f, container);
  408. container.AppendChild(CreateObjectClass(result, f));
  409. result.DocumentElement.AppendChild(container);
  410. }
  411. private void AddValue(XmlElement elem, string prefix, string name, string value, string namespaceUri)
  412. {
  413. try
  414. {
  415. var date = elem.OwnerDocument.CreateElement(prefix, name, namespaceUri);
  416. date.InnerText = value;
  417. elem.AppendChild(date);
  418. }
  419. catch (XmlException)
  420. {
  421. //_logger.Error("Error adding xml value: " + value);
  422. }
  423. }
  424. private void Browse_AddItem(XmlDocument result, BaseItem item, User user, string deviceId, Filter filter)
  425. {
  426. var element = result.CreateElement(string.Empty, "item", NS_DIDL);
  427. element.SetAttribute("restricted", "1");
  428. element.SetAttribute("id", item.Id.ToString("N"));
  429. if (item.Parent != null)
  430. {
  431. element.SetAttribute("parentID", item.Parent.Id.ToString("N"));
  432. }
  433. element.AppendChild(CreateObjectClass(result, item));
  434. AddBookmarkInfo(item, user, element);
  435. AddGeneralProperties(item, element, filter);
  436. // refID?
  437. // storeAttribute(itemNode, object, ClassProperties.REF_ID, false);
  438. var audio = item as Audio;
  439. if (audio != null)
  440. {
  441. AddAudioResource(element, audio, deviceId, filter);
  442. }
  443. var video = item as Video;
  444. if (video != null)
  445. {
  446. AddVideoResource(element, video, deviceId, filter);
  447. }
  448. AddCover(item, element);
  449. result.DocumentElement.AppendChild(element);
  450. }
  451. private void AddVideoResource(XmlElement container, Video video, string deviceId, Filter filter)
  452. {
  453. var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL);
  454. var sources = _dtoService.GetMediaSources(video);
  455. int? maxBitrateSetting = null;
  456. var streamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions
  457. {
  458. ItemId = video.Id.ToString("N"),
  459. MediaSources = sources,
  460. Profile = _profile,
  461. DeviceId = deviceId,
  462. MaxBitrate = maxBitrateSetting
  463. });
  464. var url = streamInfo.ToDlnaUrl(_serverAddress);
  465. res.InnerText = url;
  466. var mediaSource = sources.First(i => string.Equals(i.Id, streamInfo.MediaSourceId));
  467. if (mediaSource.RunTimeTicks.HasValue)
  468. {
  469. res.SetAttribute("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture));
  470. }
  471. if (filter.Contains("res@size"))
  472. {
  473. if (streamInfo.IsDirectStream || streamInfo.EstimateContentLength)
  474. {
  475. var size = streamInfo.TargetSize;
  476. if (size.HasValue)
  477. {
  478. res.SetAttribute("size", size.Value.ToString(_usCulture));
  479. }
  480. }
  481. }
  482. var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video && !string.Equals(i.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase));
  483. var targetAudioBitrate = streamInfo.TargetAudioBitrate;
  484. var targetSampleRate = streamInfo.TargetAudioSampleRate;
  485. var targetChannels = streamInfo.TargetAudioChannels;
  486. var targetWidth = streamInfo.MaxWidth ?? (videoStream == null ? null : videoStream.Width);
  487. var targetHeight = streamInfo.MaxHeight ?? (videoStream == null ? null : videoStream.Height);
  488. var targetVideoCodec = streamInfo.IsDirectStream
  489. ? (videoStream == null ? null : videoStream.Codec)
  490. : streamInfo.VideoCodec;
  491. var targetAudioCodec = streamInfo.TargetAudioCodec;
  492. var targetBitrate = maxBitrateSetting ?? mediaSource.Bitrate;
  493. if (targetChannels.HasValue)
  494. {
  495. res.SetAttribute("nrAudioChannels", targetChannels.Value.ToString(_usCulture));
  496. }
  497. if (filter.Contains("res@resolution"))
  498. {
  499. if (targetWidth.HasValue && targetHeight.HasValue)
  500. {
  501. res.SetAttribute("resolution", string.Format("{0}x{1}", targetWidth.Value, targetHeight.Value));
  502. }
  503. }
  504. if (targetSampleRate.HasValue)
  505. {
  506. res.SetAttribute("sampleFrequency", targetSampleRate.Value.ToString(_usCulture));
  507. }
  508. if (targetAudioBitrate.HasValue)
  509. {
  510. res.SetAttribute("bitrate", targetAudioBitrate.Value.ToString(_usCulture));
  511. }
  512. var formatProfile = new MediaFormatProfileResolver().ResolveVideoFormat(streamInfo.Container,
  513. targetVideoCodec,
  514. targetAudioCodec,
  515. targetWidth,
  516. targetHeight,
  517. targetBitrate,
  518. TransportStreamTimestamp.NONE);
  519. var filename = url.Substring(0, url.IndexOf('?'));
  520. var orgOpValue = DlnaMaps.GetOrgOpValue(mediaSource.RunTimeTicks.HasValue, streamInfo.IsDirectStream, streamInfo.TranscodeSeekInfo);
  521. var orgCi = streamInfo.IsDirectStream ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1";
  522. res.SetAttribute("protocolInfo", String.Format(
  523. "http-get:*:{0}:DLNA.ORG_PN={1};DLNA.ORG_OP={2};DLNA.ORG_CI={3};DLNA.ORG_FLAGS={4}",
  524. MimeTypes.GetMimeType(filename),
  525. formatProfile,
  526. orgOpValue,
  527. orgCi,
  528. DlnaMaps.DefaultStreaming
  529. ));
  530. container.AppendChild(res);
  531. }
  532. private void AddAudioResource(XmlElement container, Audio audio, string deviceId, Filter filter)
  533. {
  534. var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL);
  535. var sources = _dtoService.GetMediaSources(audio);
  536. var streamInfo = new StreamBuilder().BuildAudioItem(new AudioOptions
  537. {
  538. ItemId = audio.Id.ToString("N"),
  539. MediaSources = sources,
  540. Profile = _profile,
  541. DeviceId = deviceId
  542. });
  543. var url = streamInfo.ToDlnaUrl(_serverAddress);
  544. res.InnerText = url;
  545. var mediaSource = sources.First(i => string.Equals(i.Id, streamInfo.MediaSourceId));
  546. if (mediaSource.RunTimeTicks.HasValue)
  547. {
  548. res.SetAttribute("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture));
  549. }
  550. if (filter.Contains("res@size"))
  551. {
  552. if (streamInfo.IsDirectStream || streamInfo.EstimateContentLength)
  553. {
  554. var size = streamInfo.TargetSize;
  555. if (size.HasValue)
  556. {
  557. res.SetAttribute("size", size.Value.ToString(_usCulture));
  558. }
  559. }
  560. }
  561. var targetAudioBitrate = streamInfo.TargetAudioBitrate;
  562. var targetSampleRate = streamInfo.TargetAudioSampleRate;
  563. var targetChannels = streamInfo.TargetAudioChannels;
  564. if (targetChannels.HasValue)
  565. {
  566. res.SetAttribute("nrAudioChannels", targetChannels.Value.ToString(_usCulture));
  567. }
  568. if (targetSampleRate.HasValue)
  569. {
  570. res.SetAttribute("sampleFrequency", targetSampleRate.Value.ToString(_usCulture));
  571. }
  572. if (targetAudioBitrate.HasValue)
  573. {
  574. res.SetAttribute("bitrate", targetAudioBitrate.Value.ToString(_usCulture));
  575. }
  576. var formatProfile = new MediaFormatProfileResolver().ResolveAudioFormat(streamInfo.Container, targetAudioBitrate, targetSampleRate, targetChannels);
  577. var filename = url.Substring(0, url.IndexOf('?'));
  578. var orgOpValue = DlnaMaps.GetOrgOpValue(mediaSource.RunTimeTicks.HasValue, streamInfo.IsDirectStream, streamInfo.TranscodeSeekInfo);
  579. var orgCi = streamInfo.IsDirectStream ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1";
  580. res.SetAttribute("protocolInfo", String.Format(
  581. "http-get:*:{0}:DLNA.ORG_PN={1};DLNA.ORG_OP={2};DLNA.ORG_CI={3};DLNA.ORG_FLAGS={4}",
  582. MimeTypes.GetMimeType(filename),
  583. formatProfile,
  584. orgOpValue,
  585. orgCi,
  586. DlnaMaps.DefaultStreaming
  587. ));
  588. container.AppendChild(res);
  589. }
  590. private XmlElement CreateObjectClass(XmlDocument result, BaseItem item)
  591. {
  592. var objectClass = result.CreateElement("upnp", "class", NS_UPNP);
  593. if (item.IsFolder)
  594. {
  595. string classType = null;
  596. if (!_profile.RequiresPlainFolders)
  597. {
  598. if (item is MusicAlbum)
  599. {
  600. classType = "object.container.musicAlbum";
  601. }
  602. if (item is MusicArtist)
  603. {
  604. classType = "object.container.musicArtist";
  605. }
  606. }
  607. objectClass.InnerText = classType ?? "object.container.storageFolder";
  608. }
  609. else if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
  610. {
  611. objectClass.InnerText = "object.item.audioItem.musicTrack";
  612. }
  613. else if (string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase))
  614. {
  615. objectClass.InnerText = "object.item.imageItem.photo";
  616. }
  617. else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
  618. {
  619. if (!_profile.RequiresPlainVideoItems && item is Movie)
  620. {
  621. objectClass.InnerText = "object.item.videoItem.movie";
  622. }
  623. else
  624. {
  625. objectClass.InnerText = "object.item.videoItem";
  626. }
  627. }
  628. else
  629. {
  630. throw new NotSupportedException();
  631. }
  632. return objectClass;
  633. }
  634. private void AddPeople(BaseItem item, XmlElement element)
  635. {
  636. foreach (var actor in item.People)
  637. {
  638. AddValue(element, "upnp", (actor.Type ?? PersonType.Actor).ToLower(), actor.Name, NS_UPNP);
  639. }
  640. }
  641. private void AddBookmarkInfo(BaseItem item, User user, XmlElement element)
  642. {
  643. var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
  644. if (userdata.PlaybackPositionTicks > 0)
  645. {
  646. var dcmInfo = element.OwnerDocument.CreateElement("sec", "dcmInfo", NS_SEC);
  647. dcmInfo.InnerText = string.Format("BM={0}", Convert.ToInt32(TimeSpan.FromTicks(userdata.PlaybackPositionTicks).TotalSeconds).ToString(_usCulture));
  648. element.AppendChild(dcmInfo);
  649. }
  650. }
  651. /// <summary>
  652. /// Adds fields used by both items and folders
  653. /// </summary>
  654. /// <param name="item">The item.</param>
  655. /// <param name="element">The element.</param>
  656. /// <param name="filter">The filter.</param>
  657. private void AddCommonFields(BaseItem item, XmlElement element, Filter filter)
  658. {
  659. if (filter.Contains("dc:date"))
  660. {
  661. if (item.PremiereDate.HasValue)
  662. {
  663. AddValue(element, "dc", "date", item.PremiereDate.Value.ToString("o"), NS_DC);
  664. }
  665. }
  666. foreach (var genre in item.Genres)
  667. {
  668. AddValue(element, "upnp", "genre", genre, NS_UPNP);
  669. }
  670. foreach (var studio in item.Studios)
  671. {
  672. AddValue(element, "upnp", "publisher", studio, NS_UPNP);
  673. }
  674. if (filter.Contains("dc:title"))
  675. {
  676. AddValue(element, "dc", "title", item.Name, NS_DC);
  677. }
  678. if (filter.Contains("dc:description"))
  679. {
  680. if (!string.IsNullOrWhiteSpace(item.Overview))
  681. {
  682. AddValue(element, "dc", "description", item.Overview, NS_DC);
  683. }
  684. }
  685. if (filter.Contains("upnp:longDescription"))
  686. {
  687. if (!string.IsNullOrWhiteSpace(item.Overview))
  688. {
  689. AddValue(element, "upnp", "longDescription", item.Overview, NS_UPNP);
  690. }
  691. }
  692. if (!string.IsNullOrEmpty(item.OfficialRating))
  693. {
  694. if (filter.Contains("dc:rating"))
  695. {
  696. AddValue(element, "dc", "rating", item.OfficialRating, NS_DC);
  697. }
  698. if (filter.Contains("upnp:rating"))
  699. {
  700. AddValue(element, "upnp", "rating", item.OfficialRating, NS_UPNP);
  701. }
  702. }
  703. AddPeople(item, element);
  704. }
  705. private void AddGeneralProperties(BaseItem item, XmlElement element, Filter filter)
  706. {
  707. AddCommonFields(item, element, filter);
  708. var audio = item as Audio;
  709. if (audio != null)
  710. {
  711. foreach (var artist in audio.Artists)
  712. {
  713. AddValue(element, "upnp", "artist", artist, NS_UPNP);
  714. }
  715. if (!string.IsNullOrEmpty(audio.Album))
  716. {
  717. AddValue(element, "upnp", "album", audio.Album, NS_UPNP);
  718. }
  719. if (!string.IsNullOrEmpty(audio.AlbumArtist))
  720. {
  721. AddValue(element, "upnp", "albumArtist", audio.AlbumArtist, NS_UPNP);
  722. }
  723. }
  724. var album = item as MusicAlbum;
  725. if (album != null)
  726. {
  727. if (!string.IsNullOrEmpty(album.AlbumArtist))
  728. {
  729. AddValue(element, "upnp", "artist", album.AlbumArtist, NS_UPNP);
  730. AddValue(element, "upnp", "albumArtist", album.AlbumArtist, NS_UPNP);
  731. }
  732. }
  733. var musicVideo = item as MusicVideo;
  734. if (musicVideo != null)
  735. {
  736. if (!string.IsNullOrEmpty(musicVideo.Artist))
  737. {
  738. AddValue(element, "upnp", "artist", musicVideo.Artist, NS_UPNP);
  739. }
  740. if (!string.IsNullOrEmpty(musicVideo.Album))
  741. {
  742. AddValue(element, "upnp", "album", musicVideo.Album, NS_UPNP);
  743. }
  744. }
  745. if (item.IndexNumber.HasValue)
  746. {
  747. AddValue(element, "upnp", "originalTrackNumber", item.IndexNumber.Value.ToString(_usCulture), NS_UPNP);
  748. }
  749. }
  750. private void AddCover(BaseItem item, XmlElement element)
  751. {
  752. var imageInfo = GetImageInfo(item);
  753. if (imageInfo == null)
  754. {
  755. return;
  756. }
  757. var result = element.OwnerDocument;
  758. var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight);
  759. var icon = result.CreateElement("upnp", "albumArtURI", NS_UPNP);
  760. var profile = result.CreateAttribute("dlna", "profileID", NS_DLNA);
  761. profile.InnerText = _profile.AlbumArtPn;
  762. icon.SetAttributeNode(profile);
  763. icon.InnerText = albumartUrlInfo.Url;
  764. element.AppendChild(icon);
  765. var iconUrlInfo = GetImageUrl(imageInfo, _profile.MaxIconWidth, _profile.MaxIconHeight);
  766. icon = result.CreateElement("upnp", "icon", NS_UPNP);
  767. profile = result.CreateAttribute("dlna", "profileID", NS_DLNA);
  768. profile.InnerText = _profile.AlbumArtPn;
  769. icon.SetAttributeNode(profile);
  770. icon.InnerText = iconUrlInfo.Url;
  771. element.AppendChild(icon);
  772. if (!_profile.EnableAlbumArtInDidl)
  773. {
  774. return;
  775. }
  776. var res = result.CreateElement(string.Empty, "res", NS_DIDL);
  777. res.InnerText = albumartUrlInfo.Url;
  778. var width = albumartUrlInfo.Width;
  779. var height = albumartUrlInfo.Height;
  780. var mediaProfile = new MediaFormatProfileResolver().ResolveImageFormat("jpg", width, height);
  781. res.SetAttribute("protocolInfo", string.Format(
  782. "http-get:*:{1}DLNA.ORG_PN=:{0};DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS={2}",
  783. mediaProfile, "image/jpeg", DlnaMaps.DefaultStreaming
  784. ));
  785. if (width.HasValue && height.HasValue)
  786. {
  787. res.SetAttribute("resolution", string.Format("{0}x{1}", width.Value, height.Value));
  788. }
  789. element.AppendChild(res);
  790. }
  791. private ImageDownloadInfo GetImageInfo(BaseItem item)
  792. {
  793. if (item.HasImage(ImageType.Primary))
  794. {
  795. return GetImageInfo(item, ImageType.Primary);
  796. }
  797. if (item.HasImage(ImageType.Thumb))
  798. {
  799. return GetImageInfo(item, ImageType.Thumb);
  800. }
  801. if (item is Audio || item is Episode)
  802. {
  803. item = item.Parents.FirstOrDefault(i => i.HasImage(ImageType.Primary));
  804. if (item != null)
  805. {
  806. return GetImageInfo(item, ImageType.Primary);
  807. }
  808. }
  809. return null;
  810. }
  811. private ImageDownloadInfo GetImageInfo(BaseItem item, ImageType type)
  812. {
  813. var imageInfo = item.GetImageInfo(type, 0);
  814. string tag = null;
  815. try
  816. {
  817. var guid = _imageProcessor.GetImageCacheTag(item, ImageType.Primary);
  818. tag = guid.HasValue ? guid.Value.ToString("N") : null;
  819. }
  820. catch
  821. {
  822. }
  823. int? width = null;
  824. int? height = null;
  825. try
  826. {
  827. var size = _imageProcessor.GetImageSize(imageInfo.Path, imageInfo.DateModified);
  828. width = Convert.ToInt32(size.Width);
  829. height = Convert.ToInt32(size.Height);
  830. }
  831. catch
  832. {
  833. }
  834. return new ImageDownloadInfo
  835. {
  836. ItemId = item.Id.ToString("N"),
  837. Type = ImageType.Primary,
  838. ImageTag = tag,
  839. Width = width,
  840. Height = height
  841. };
  842. }
  843. class ImageDownloadInfo
  844. {
  845. internal string ItemId;
  846. internal string ImageTag;
  847. internal ImageType Type;
  848. internal int? Width;
  849. internal int? Height;
  850. }
  851. class ImageUrlInfo
  852. {
  853. internal string Url;
  854. internal int? Width;
  855. internal int? Height;
  856. }
  857. private ImageUrlInfo GetImageUrl(ImageDownloadInfo info, int? maxWidth, int? maxHeight)
  858. {
  859. var url = string.Format("{0}/Items/{1}/Images/{2}?tag={3}&format=jpg",
  860. _serverAddress,
  861. info.ItemId,
  862. info.Type,
  863. info.ImageTag);
  864. if (maxWidth.HasValue)
  865. {
  866. url += "&maxWidth=" + maxWidth.Value.ToString(_usCulture);
  867. }
  868. if (maxHeight.HasValue)
  869. {
  870. url += "&maxHeight=" + maxHeight.Value.ToString(_usCulture);
  871. }
  872. var width = info.Width;
  873. var height = info.Height;
  874. if (width.HasValue && height.HasValue)
  875. {
  876. if (maxWidth.HasValue || maxHeight.HasValue)
  877. {
  878. var newSize = DrawingUtils.Resize(new ImageSize
  879. {
  880. Height = height.Value,
  881. Width = width.Value
  882. }, maxWidth: maxWidth, maxHeight: maxHeight);
  883. width = Convert.ToInt32(newSize.Width);
  884. height = Convert.ToInt32(newSize.Height);
  885. }
  886. }
  887. return new ImageUrlInfo
  888. {
  889. Url = url,
  890. Width = width,
  891. Height = height
  892. };
  893. }
  894. }
  895. }