DidlBuilder.cs 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115
  1. using MediaBrowser.Model.Extensions;
  2. using MediaBrowser.Controller.Channels;
  3. using MediaBrowser.Controller.Drawing;
  4. using MediaBrowser.Controller.Entities;
  5. using MediaBrowser.Controller.Entities.Audio;
  6. using MediaBrowser.Controller.Entities.Movies;
  7. using MediaBrowser.Controller.Entities.TV;
  8. using MediaBrowser.Controller.Library;
  9. using MediaBrowser.Controller.Localization;
  10. using MediaBrowser.Controller.Playlists;
  11. using MediaBrowser.Dlna.ContentDirectory;
  12. using MediaBrowser.Model.Dlna;
  13. using MediaBrowser.Model.Drawing;
  14. using MediaBrowser.Model.Entities;
  15. using MediaBrowser.Model.Logging;
  16. using MediaBrowser.Model.Net;
  17. using System;
  18. using System.Globalization;
  19. using System.IO;
  20. using System.Linq;
  21. using System.Xml;
  22. namespace MediaBrowser.Dlna.Didl
  23. {
  24. public class DidlBuilder
  25. {
  26. private readonly CultureInfo _usCulture = new CultureInfo("en-US");
  27. private const string NS_DIDL = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
  28. private const string NS_DC = "http://purl.org/dc/elements/1.1/";
  29. private const string NS_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/";
  30. private const string NS_DLNA = "urn:schemas-dlna-org:metadata-1-0/";
  31. private readonly DeviceProfile _profile;
  32. private readonly IImageProcessor _imageProcessor;
  33. private readonly string _serverAddress;
  34. private readonly string _accessToken;
  35. private readonly User _user;
  36. private readonly IUserDataManager _userDataManager;
  37. private readonly ILocalizationManager _localization;
  38. private readonly IMediaSourceManager _mediaSourceManager;
  39. private readonly ILogger _logger;
  40. private readonly ILibraryManager _libraryManager;
  41. public DidlBuilder(DeviceProfile profile, User user, IImageProcessor imageProcessor, string serverAddress, string accessToken, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, ILogger logger, ILibraryManager libraryManager)
  42. {
  43. _profile = profile;
  44. _imageProcessor = imageProcessor;
  45. _serverAddress = serverAddress;
  46. _userDataManager = userDataManager;
  47. _localization = localization;
  48. _mediaSourceManager = mediaSourceManager;
  49. _logger = logger;
  50. _libraryManager = libraryManager;
  51. _accessToken = accessToken;
  52. _user = user;
  53. }
  54. public string GetItemDidl(BaseItem item, BaseItem context, string deviceId, Filter filter, StreamInfo streamInfo)
  55. {
  56. var result = new XmlDocument();
  57. var didl = result.CreateElement(string.Empty, "DIDL-Lite", NS_DIDL);
  58. didl.SetAttribute("xmlns:dc", NS_DC);
  59. didl.SetAttribute("xmlns:dlna", NS_DLNA);
  60. didl.SetAttribute("xmlns:upnp", NS_UPNP);
  61. //didl.SetAttribute("xmlns:sec", NS_SEC);
  62. foreach (var att in _profile.XmlRootAttributes)
  63. {
  64. didl.SetAttribute(att.Name, att.Value);
  65. }
  66. result.AppendChild(didl);
  67. result.DocumentElement.AppendChild(GetItemElement(result, item, context, null, deviceId, filter, streamInfo));
  68. return result.DocumentElement.OuterXml;
  69. }
  70. public XmlElement GetItemElement(XmlDocument doc, BaseItem item, BaseItem context, StubType? contextStubType, string deviceId, Filter filter, StreamInfo streamInfo = null)
  71. {
  72. var clientId = GetClientId(item, null);
  73. var element = doc.CreateElement(string.Empty, "item", NS_DIDL);
  74. element.SetAttribute("restricted", "1");
  75. element.SetAttribute("id", clientId);
  76. if (context != null)
  77. {
  78. element.SetAttribute("parentID", GetClientId(context, contextStubType));
  79. }
  80. else
  81. {
  82. var parent = item.DisplayParent;
  83. if (parent != null)
  84. {
  85. element.SetAttribute("parentID", GetClientId(parent, null));
  86. }
  87. }
  88. //AddBookmarkInfo(item, user, element);
  89. AddGeneralProperties(item, null, context, element, filter);
  90. // refID?
  91. // storeAttribute(itemNode, object, ClassProperties.REF_ID, false);
  92. var hasMediaSources = item as IHasMediaSources;
  93. if (hasMediaSources != null)
  94. {
  95. if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
  96. {
  97. AddAudioResource(element, hasMediaSources, deviceId, filter, streamInfo);
  98. }
  99. else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
  100. {
  101. AddVideoResource(element, hasMediaSources, deviceId, filter, streamInfo);
  102. }
  103. }
  104. AddCover(item, context, null, element);
  105. return element;
  106. }
  107. private void AddVideoResource(XmlElement container, IHasMediaSources video, string deviceId, Filter filter, StreamInfo streamInfo = null)
  108. {
  109. if (streamInfo == null)
  110. {
  111. var sources = _mediaSourceManager.GetStaticMediaSources(video, true, _user).ToList();
  112. streamInfo = new StreamBuilder(_logger).BuildVideoItem(new VideoOptions
  113. {
  114. ItemId = GetClientId(video),
  115. MediaSources = sources,
  116. Profile = _profile,
  117. DeviceId = deviceId,
  118. MaxBitrate = _profile.MaxStreamingBitrate
  119. });
  120. }
  121. var targetWidth = streamInfo.TargetWidth;
  122. var targetHeight = streamInfo.TargetHeight;
  123. var contentFeatureList = new ContentFeatureBuilder(_profile).BuildVideoHeader(streamInfo.Container,
  124. streamInfo.VideoCodec,
  125. streamInfo.AudioCodec,
  126. targetWidth,
  127. targetHeight,
  128. streamInfo.TargetVideoBitDepth,
  129. streamInfo.TargetVideoBitrate,
  130. streamInfo.TargetTimestamp,
  131. streamInfo.IsDirectStream,
  132. streamInfo.RunTimeTicks,
  133. streamInfo.TargetVideoProfile,
  134. streamInfo.TargetVideoLevel,
  135. streamInfo.TargetFramerate,
  136. streamInfo.TargetPacketLength,
  137. streamInfo.TranscodeSeekInfo,
  138. streamInfo.IsTargetAnamorphic,
  139. streamInfo.IsTargetCabac,
  140. streamInfo.TargetRefFrames,
  141. streamInfo.TargetVideoStreamCount,
  142. streamInfo.TargetAudioStreamCount,
  143. streamInfo.TargetVideoCodecTag);
  144. foreach (var contentFeature in contentFeatureList)
  145. {
  146. AddVideoResource(container, video, deviceId, filter, contentFeature, streamInfo);
  147. }
  148. foreach (var subtitle in streamInfo.GetSubtitleProfiles(false, _serverAddress, _accessToken))
  149. {
  150. if (subtitle.DeliveryMethod == SubtitleDeliveryMethod.External)
  151. {
  152. var subtitleAdded = AddSubtitleElement(container, subtitle);
  153. if (subtitleAdded && _profile.EnableSingleSubtitleLimit)
  154. {
  155. break;
  156. }
  157. }
  158. }
  159. }
  160. private bool AddSubtitleElement(XmlElement container, SubtitleStreamInfo info)
  161. {
  162. var subtitleProfile = _profile.SubtitleProfiles
  163. .FirstOrDefault(i => string.Equals(info.Format, i.Format, StringComparison.OrdinalIgnoreCase) && i.Method == SubtitleDeliveryMethod.External);
  164. if (subtitleProfile == null)
  165. {
  166. return false;
  167. }
  168. var subtitleMode = subtitleProfile.DidlMode;
  169. if (string.Equals(subtitleMode, "CaptionInfoEx", StringComparison.OrdinalIgnoreCase))
  170. {
  171. // <sec:CaptionInfoEx sec:type="srt">http://192.168.1.3:9999/video.srt</sec:CaptionInfoEx>
  172. // <sec:CaptionInfo sec:type="srt">http://192.168.1.3:9999/video.srt</sec:CaptionInfo>
  173. //var res = container.OwnerDocument.CreateElement("SEC", "CaptionInfoEx");
  174. //res.InnerText = info.Url;
  175. //// TODO: attribute needs SEC:
  176. //res.SetAttribute("type", info.Format.ToLower());
  177. //container.AppendChild(res);
  178. }
  179. else if (string.Equals(subtitleMode, "smi", StringComparison.OrdinalIgnoreCase))
  180. {
  181. var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL);
  182. res.InnerText = info.Url;
  183. res.SetAttribute("protocolInfo", "http-get:*:smi/caption:*");
  184. container.AppendChild(res);
  185. }
  186. else
  187. {
  188. var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL);
  189. res.InnerText = info.Url;
  190. var protocolInfo = string.Format("http-get:*:text/{0}:*", info.Format.ToLower());
  191. res.SetAttribute("protocolInfo", protocolInfo);
  192. container.AppendChild(res);
  193. }
  194. return true;
  195. }
  196. private void AddVideoResource(XmlElement container, IHasMediaSources video, string deviceId, Filter filter, string contentFeatures, StreamInfo streamInfo)
  197. {
  198. var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL);
  199. var url = streamInfo.ToDlnaUrl(_serverAddress, _accessToken);
  200. res.InnerText = url;
  201. var mediaSource = streamInfo.MediaSource;
  202. if (mediaSource.RunTimeTicks.HasValue)
  203. {
  204. res.SetAttribute("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture));
  205. }
  206. if (filter.Contains("res@size"))
  207. {
  208. if (streamInfo.IsDirectStream || streamInfo.EstimateContentLength)
  209. {
  210. var size = streamInfo.TargetSize;
  211. if (size.HasValue)
  212. {
  213. res.SetAttribute("size", size.Value.ToString(_usCulture));
  214. }
  215. }
  216. }
  217. var totalBitrate = streamInfo.TargetTotalBitrate;
  218. var targetSampleRate = streamInfo.TargetAudioSampleRate;
  219. var targetChannels = streamInfo.TargetAudioChannels;
  220. var targetWidth = streamInfo.TargetWidth;
  221. var targetHeight = streamInfo.TargetHeight;
  222. if (targetChannels.HasValue)
  223. {
  224. res.SetAttribute("nrAudioChannels", targetChannels.Value.ToString(_usCulture));
  225. }
  226. if (filter.Contains("res@resolution"))
  227. {
  228. if (targetWidth.HasValue && targetHeight.HasValue)
  229. {
  230. res.SetAttribute("resolution", string.Format("{0}x{1}", targetWidth.Value, targetHeight.Value));
  231. }
  232. }
  233. if (targetSampleRate.HasValue)
  234. {
  235. res.SetAttribute("sampleFrequency", targetSampleRate.Value.ToString(_usCulture));
  236. }
  237. if (totalBitrate.HasValue)
  238. {
  239. res.SetAttribute("bitrate", totalBitrate.Value.ToString(_usCulture));
  240. }
  241. var mediaProfile = _profile.GetVideoMediaProfile(streamInfo.Container,
  242. streamInfo.AudioCodec,
  243. streamInfo.VideoCodec,
  244. streamInfo.TargetAudioBitrate,
  245. targetWidth,
  246. targetHeight,
  247. streamInfo.TargetVideoBitDepth,
  248. streamInfo.TargetVideoProfile,
  249. streamInfo.TargetVideoLevel,
  250. streamInfo.TargetFramerate,
  251. streamInfo.TargetPacketLength,
  252. streamInfo.TargetTimestamp,
  253. streamInfo.IsTargetAnamorphic,
  254. streamInfo.IsTargetCabac,
  255. streamInfo.TargetRefFrames,
  256. streamInfo.TargetVideoStreamCount,
  257. streamInfo.TargetAudioStreamCount,
  258. streamInfo.TargetVideoCodecTag);
  259. var filename = url.Substring(0, url.IndexOf('?'));
  260. var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
  261. ? MimeTypes.GetMimeType(filename)
  262. : mediaProfile.MimeType;
  263. res.SetAttribute("protocolInfo", String.Format(
  264. "http-get:*:{0}:{1}",
  265. mimeType,
  266. contentFeatures
  267. ));
  268. container.AppendChild(res);
  269. }
  270. private string GetDisplayName(BaseItem item, StubType? itemStubType, BaseItem context)
  271. {
  272. if (itemStubType.HasValue && itemStubType.Value == StubType.People)
  273. {
  274. if (item is Video)
  275. {
  276. return _localization.GetLocalizedString("HeaderCastCrew");
  277. }
  278. return _localization.GetLocalizedString("HeaderPeople");
  279. }
  280. var episode = item as Episode;
  281. var season = context as Season;
  282. if (episode != null && season != null)
  283. {
  284. // This is a special embedded within a season
  285. if (item.ParentIndexNumber.HasValue && item.ParentIndexNumber.Value == 0)
  286. {
  287. if (season.IndexNumber.HasValue && season.IndexNumber.Value != 0)
  288. {
  289. return string.Format(_localization.GetLocalizedString("ValueSpecialEpisodeName"), item.Name);
  290. }
  291. }
  292. if (item.IndexNumber.HasValue)
  293. {
  294. var number = item.IndexNumber.Value.ToString("00").ToString(CultureInfo.InvariantCulture);
  295. if (episode.IndexNumberEnd.HasValue)
  296. {
  297. number += "-" + episode.IndexNumberEnd.Value.ToString("00").ToString(CultureInfo.InvariantCulture);
  298. }
  299. return number + " - " + item.Name;
  300. }
  301. }
  302. return item.Name;
  303. }
  304. private void AddAudioResource(XmlElement container, IHasMediaSources audio, string deviceId, Filter filter, StreamInfo streamInfo = null)
  305. {
  306. var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL);
  307. if (streamInfo == null)
  308. {
  309. var sources = _mediaSourceManager.GetStaticMediaSources(audio, true, _user).ToList();
  310. streamInfo = new StreamBuilder(new NullLogger()).BuildAudioItem(new AudioOptions
  311. {
  312. ItemId = GetClientId(audio),
  313. MediaSources = sources,
  314. Profile = _profile,
  315. DeviceId = deviceId
  316. });
  317. }
  318. var url = streamInfo.ToDlnaUrl(_serverAddress, _accessToken);
  319. res.InnerText = url;
  320. var mediaSource = streamInfo.MediaSource;
  321. if (mediaSource.RunTimeTicks.HasValue)
  322. {
  323. res.SetAttribute("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture));
  324. }
  325. if (filter.Contains("res@size"))
  326. {
  327. if (streamInfo.IsDirectStream || streamInfo.EstimateContentLength)
  328. {
  329. var size = streamInfo.TargetSize;
  330. if (size.HasValue)
  331. {
  332. res.SetAttribute("size", size.Value.ToString(_usCulture));
  333. }
  334. }
  335. }
  336. var targetAudioBitrate = streamInfo.TargetAudioBitrate;
  337. var targetSampleRate = streamInfo.TargetAudioSampleRate;
  338. var targetChannels = streamInfo.TargetAudioChannels;
  339. if (targetChannels.HasValue)
  340. {
  341. res.SetAttribute("nrAudioChannels", targetChannels.Value.ToString(_usCulture));
  342. }
  343. if (targetSampleRate.HasValue)
  344. {
  345. res.SetAttribute("sampleFrequency", targetSampleRate.Value.ToString(_usCulture));
  346. }
  347. if (targetAudioBitrate.HasValue)
  348. {
  349. res.SetAttribute("bitrate", targetAudioBitrate.Value.ToString(_usCulture));
  350. }
  351. var mediaProfile = _profile.GetAudioMediaProfile(streamInfo.Container,
  352. streamInfo.AudioCodec,
  353. targetChannels,
  354. targetAudioBitrate);
  355. var filename = url.Substring(0, url.IndexOf('?'));
  356. var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
  357. ? MimeTypes.GetMimeType(filename)
  358. : mediaProfile.MimeType;
  359. var contentFeatures = new ContentFeatureBuilder(_profile).BuildAudioHeader(streamInfo.Container,
  360. streamInfo.TargetAudioCodec,
  361. targetAudioBitrate,
  362. targetSampleRate,
  363. targetChannels,
  364. streamInfo.IsDirectStream,
  365. streamInfo.RunTimeTicks,
  366. streamInfo.TranscodeSeekInfo);
  367. res.SetAttribute("protocolInfo", String.Format(
  368. "http-get:*:{0}:{1}",
  369. mimeType,
  370. contentFeatures
  371. ));
  372. container.AppendChild(res);
  373. }
  374. public static bool IsIdRoot(string id)
  375. {
  376. if (string.IsNullOrWhiteSpace(id) ||
  377. string.Equals(id, "0", StringComparison.OrdinalIgnoreCase)
  378. // Samsung sometimes uses 1 as root
  379. || string.Equals(id, "1", StringComparison.OrdinalIgnoreCase))
  380. {
  381. return true;
  382. }
  383. return false;
  384. }
  385. public XmlElement GetFolderElement(XmlDocument doc, BaseItem folder, StubType? stubType, BaseItem context, int childCount, Filter filter, string requestedId = null)
  386. {
  387. var container = doc.CreateElement(string.Empty, "container", NS_DIDL);
  388. container.SetAttribute("restricted", "0");
  389. container.SetAttribute("searchable", "1");
  390. container.SetAttribute("childCount", childCount.ToString(_usCulture));
  391. var clientId = GetClientId(folder, stubType);
  392. if (string.Equals(requestedId, "0"))
  393. {
  394. container.SetAttribute("id", "0");
  395. container.SetAttribute("parentID", "-1");
  396. }
  397. else
  398. {
  399. container.SetAttribute("id", clientId);
  400. var parent = context ?? folder.DisplayParent;
  401. if (parent == null)
  402. {
  403. container.SetAttribute("parentID", "0");
  404. }
  405. else
  406. {
  407. container.SetAttribute("parentID", GetClientId(parent, null));
  408. }
  409. }
  410. AddCommonFields(folder, stubType, null, container, filter);
  411. AddCover(folder, context, stubType, container);
  412. return container;
  413. }
  414. //private void AddBookmarkInfo(BaseItem item, User user, XmlElement element)
  415. //{
  416. // var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
  417. // if (userdata.PlaybackPositionTicks > 0)
  418. // {
  419. // var dcmInfo = element.OwnerDocument.CreateElement("sec", "dcmInfo", NS_SEC);
  420. // dcmInfo.InnerText = string.Format("BM={0}", Convert.ToInt32(TimeSpan.FromTicks(userdata.PlaybackPositionTicks).TotalSeconds).ToString(_usCulture));
  421. // element.AppendChild(dcmInfo);
  422. // }
  423. //}
  424. /// <summary>
  425. /// Adds fields used by both items and folders
  426. /// </summary>
  427. /// <param name="item">The item.</param>
  428. /// <param name="itemStubType">Type of the item stub.</param>
  429. /// <param name="context">The context.</param>
  430. /// <param name="element">The element.</param>
  431. /// <param name="filter">The filter.</param>
  432. private void AddCommonFields(BaseItem item, StubType? itemStubType, BaseItem context, XmlElement element, Filter filter)
  433. {
  434. // Don't filter on dc:title because not all devices will include it in the filter
  435. // MediaMonkey for example won't display content without a title
  436. //if (filter.Contains("dc:title"))
  437. {
  438. AddValue(element, "dc", "title", GetDisplayName(item, itemStubType, context), NS_DC);
  439. }
  440. element.AppendChild(CreateObjectClass(element.OwnerDocument, item, itemStubType));
  441. if (filter.Contains("dc:date"))
  442. {
  443. if (item.PremiereDate.HasValue)
  444. {
  445. AddValue(element, "dc", "date", item.PremiereDate.Value.ToString("o"), NS_DC);
  446. }
  447. }
  448. if (filter.Contains("upnp:genre"))
  449. {
  450. foreach (var genre in item.Genres)
  451. {
  452. AddValue(element, "upnp", "genre", genre, NS_UPNP);
  453. }
  454. }
  455. foreach (var studio in item.Studios)
  456. {
  457. AddValue(element, "upnp", "publisher", studio, NS_UPNP);
  458. }
  459. if (filter.Contains("dc:description"))
  460. {
  461. var desc = item.Overview;
  462. var hasShortOverview = item as IHasShortOverview;
  463. if (hasShortOverview != null && !string.IsNullOrEmpty(hasShortOverview.ShortOverview))
  464. {
  465. desc = hasShortOverview.ShortOverview;
  466. }
  467. if (!string.IsNullOrWhiteSpace(desc))
  468. {
  469. AddValue(element, "dc", "description", desc, NS_DC);
  470. }
  471. }
  472. if (filter.Contains("upnp:longDescription"))
  473. {
  474. if (!string.IsNullOrWhiteSpace(item.Overview))
  475. {
  476. AddValue(element, "upnp", "longDescription", item.Overview, NS_UPNP);
  477. }
  478. }
  479. if (!string.IsNullOrEmpty(item.OfficialRating))
  480. {
  481. if (filter.Contains("dc:rating"))
  482. {
  483. AddValue(element, "dc", "rating", item.OfficialRating, NS_DC);
  484. }
  485. if (filter.Contains("upnp:rating"))
  486. {
  487. AddValue(element, "upnp", "rating", item.OfficialRating, NS_UPNP);
  488. }
  489. }
  490. AddPeople(item, element);
  491. }
  492. private XmlElement CreateObjectClass(XmlDocument result, BaseItem item, StubType? stubType)
  493. {
  494. // More types here
  495. // http://oss.linn.co.uk/repos/Public/LibUpnpCil/DidlLite/UpnpAv/Test/TestDidlLite.cs
  496. var objectClass = result.CreateElement("upnp", "class", NS_UPNP);
  497. if (item.IsFolder || stubType.HasValue)
  498. {
  499. string classType = null;
  500. if (!_profile.RequiresPlainFolders)
  501. {
  502. if (item is MusicAlbum)
  503. {
  504. classType = "object.container.album.musicAlbum";
  505. }
  506. else if (item is MusicArtist)
  507. {
  508. classType = "object.container.person.musicArtist";
  509. }
  510. else if (item is Series || item is Season || item is BoxSet || item is Video)
  511. {
  512. classType = "object.container.album.videoAlbum";
  513. }
  514. else if (item is Playlist)
  515. {
  516. classType = "object.container.playlistContainer";
  517. }
  518. else if (item is PhotoAlbum)
  519. {
  520. classType = "object.container.album.photoAlbum";
  521. }
  522. }
  523. objectClass.InnerText = classType ?? "object.container.storageFolder";
  524. }
  525. else if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
  526. {
  527. objectClass.InnerText = "object.item.audioItem.musicTrack";
  528. }
  529. else if (string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase))
  530. {
  531. objectClass.InnerText = "object.item.imageItem.photo";
  532. }
  533. else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
  534. {
  535. if (!_profile.RequiresPlainVideoItems && item is Movie)
  536. {
  537. objectClass.InnerText = "object.item.videoItem.movie";
  538. }
  539. else if (!_profile.RequiresPlainVideoItems && item is MusicVideo)
  540. {
  541. objectClass.InnerText = "object.item.videoItem.musicVideoClip";
  542. }
  543. else
  544. {
  545. objectClass.InnerText = "object.item.videoItem";
  546. }
  547. }
  548. else if (item is MusicGenre)
  549. {
  550. objectClass.InnerText = _profile.RequiresPlainFolders ? "object.container.storageFolder" : "object.container.genre.musicGenre";
  551. }
  552. else if (item is Genre || item is GameGenre)
  553. {
  554. objectClass.InnerText = _profile.RequiresPlainFolders ? "object.container.storageFolder" : "object.container.genre";
  555. }
  556. else
  557. {
  558. objectClass.InnerText = "object.item";
  559. }
  560. return objectClass;
  561. }
  562. private void AddPeople(BaseItem item, XmlElement element)
  563. {
  564. var types = new[] { PersonType.Director, PersonType.Writer, PersonType.Producer, PersonType.Composer, "Creator" };
  565. var people = _libraryManager.GetPeople(item);
  566. foreach (var actor in people)
  567. {
  568. var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase))
  569. ?? PersonType.Actor;
  570. AddValue(element, "upnp", type.ToLower(), actor.Name, NS_UPNP);
  571. }
  572. }
  573. private void AddGeneralProperties(BaseItem item, StubType? itemStubType, BaseItem context, XmlElement element, Filter filter)
  574. {
  575. AddCommonFields(item, itemStubType, context, element, filter);
  576. var audio = item as Audio;
  577. if (audio != null)
  578. {
  579. foreach (var artist in audio.Artists)
  580. {
  581. AddValue(element, "upnp", "artist", artist, NS_UPNP);
  582. }
  583. if (!string.IsNullOrEmpty(audio.Album))
  584. {
  585. AddValue(element, "upnp", "album", audio.Album, NS_UPNP);
  586. }
  587. foreach (var artist in audio.AlbumArtists)
  588. {
  589. AddAlbumArtist(element, artist);
  590. }
  591. }
  592. var album = item as MusicAlbum;
  593. if (album != null)
  594. {
  595. foreach (var artist in album.AlbumArtists)
  596. {
  597. AddAlbumArtist(element, artist);
  598. AddValue(element, "upnp", "artist", artist, NS_UPNP);
  599. }
  600. foreach (var artist in album.Artists)
  601. {
  602. AddValue(element, "upnp", "artist", artist, NS_UPNP);
  603. }
  604. }
  605. var musicVideo = item as MusicVideo;
  606. if (musicVideo != null)
  607. {
  608. foreach (var artist in musicVideo.Artists)
  609. {
  610. AddValue(element, "upnp", "artist", artist, NS_UPNP);
  611. AddAlbumArtist(element, artist);
  612. }
  613. if (!string.IsNullOrEmpty(musicVideo.Album))
  614. {
  615. AddValue(element, "upnp", "album", musicVideo.Album, NS_UPNP);
  616. }
  617. }
  618. if (item.IndexNumber.HasValue)
  619. {
  620. AddValue(element, "upnp", "originalTrackNumber", item.IndexNumber.Value.ToString(_usCulture), NS_UPNP);
  621. if (item is Episode)
  622. {
  623. AddValue(element, "upnp", "episodeNumber", item.IndexNumber.Value.ToString(_usCulture), NS_UPNP);
  624. }
  625. }
  626. }
  627. private void AddAlbumArtist(XmlElement elem, string name)
  628. {
  629. try
  630. {
  631. var newNode = elem.OwnerDocument.CreateElement("upnp", "artist", NS_UPNP);
  632. newNode.InnerText = name;
  633. newNode.SetAttribute("role", "AlbumArtist");
  634. elem.AppendChild(newNode);
  635. }
  636. catch (XmlException)
  637. {
  638. //_logger.Error("Error adding xml value: " + value);
  639. }
  640. }
  641. private void AddValue(XmlElement elem, string prefix, string name, string value, string namespaceUri)
  642. {
  643. try
  644. {
  645. var date = elem.OwnerDocument.CreateElement(prefix, name, namespaceUri);
  646. date.InnerText = value;
  647. elem.AppendChild(date);
  648. }
  649. catch (XmlException)
  650. {
  651. //_logger.Error("Error adding xml value: " + value);
  652. }
  653. }
  654. private void AddCover(BaseItem item, BaseItem context, StubType? stubType, XmlElement element)
  655. {
  656. if (stubType.HasValue && stubType.Value == StubType.People)
  657. {
  658. AddEmbeddedImageAsCover("people", element);
  659. return;
  660. }
  661. ImageDownloadInfo imageInfo = null;
  662. if (context is UserView)
  663. {
  664. var episode = item as Episode;
  665. if (episode != null)
  666. {
  667. var parent = (BaseItem)episode.Series ?? episode.Season;
  668. if (parent != null)
  669. {
  670. imageInfo = GetImageInfo(parent);
  671. }
  672. }
  673. }
  674. // Finally, just use the image from the item
  675. if (imageInfo == null)
  676. {
  677. imageInfo = GetImageInfo(item);
  678. }
  679. if (imageInfo == null)
  680. {
  681. return;
  682. }
  683. var result = element.OwnerDocument;
  684. var playbackPercentage = 0;
  685. var unplayedCount = 0;
  686. if (item is Video)
  687. {
  688. var userData = _userDataManager.GetUserDataDto(item, _user);
  689. playbackPercentage = Convert.ToInt32(userData.PlayedPercentage ?? 0);
  690. if (playbackPercentage >= 100 || userData.Played)
  691. {
  692. playbackPercentage = 100;
  693. }
  694. }
  695. else if (item is Series || item is Season || item is BoxSet)
  696. {
  697. var userData = _userDataManager.GetUserDataDto(item, _user);
  698. if (userData.Played)
  699. {
  700. playbackPercentage = 100;
  701. }
  702. else
  703. {
  704. unplayedCount = userData.UnplayedItemCount ?? 0;
  705. }
  706. }
  707. var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight, playbackPercentage, unplayedCount, "jpg");
  708. var icon = result.CreateElement("upnp", "albumArtURI", NS_UPNP);
  709. var profile = result.CreateAttribute("dlna", "profileID", NS_DLNA);
  710. profile.InnerText = _profile.AlbumArtPn;
  711. icon.SetAttributeNode(profile);
  712. icon.InnerText = albumartUrlInfo.Url;
  713. element.AppendChild(icon);
  714. // TOOD: Remove these default values
  715. var iconUrlInfo = GetImageUrl(imageInfo, _profile.MaxIconWidth ?? 48, _profile.MaxIconHeight ?? 48, playbackPercentage, unplayedCount, "jpg");
  716. icon = result.CreateElement("upnp", "icon", NS_UPNP);
  717. icon.InnerText = iconUrlInfo.Url;
  718. element.AppendChild(icon);
  719. if (!_profile.EnableAlbumArtInDidl)
  720. {
  721. if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)
  722. || string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
  723. {
  724. if (!stubType.HasValue)
  725. {
  726. return;
  727. }
  728. }
  729. }
  730. AddImageResElement(item, element, 160, 160, playbackPercentage, unplayedCount, "jpg", "JPEG_TN");
  731. if (!_profile.EnableSingleAlbumArtLimit)
  732. {
  733. AddImageResElement(item, element, 4096, 4096, playbackPercentage, unplayedCount, "jpg", "JPEG_LRG");
  734. AddImageResElement(item, element, 1024, 768, playbackPercentage, unplayedCount, "jpg", "JPEG_MED");
  735. AddImageResElement(item, element, 640, 480, playbackPercentage, unplayedCount, "jpg", "JPEG_SM");
  736. AddImageResElement(item, element, 4096, 4096, playbackPercentage, unplayedCount, "png", "PNG_LRG");
  737. AddImageResElement(item, element, 160, 160, playbackPercentage, unplayedCount, "png", "PNG_TN");
  738. }
  739. }
  740. private void AddEmbeddedImageAsCover(string name, XmlElement element)
  741. {
  742. var result = element.OwnerDocument;
  743. var icon = result.CreateElement("upnp", "albumArtURI", NS_UPNP);
  744. var profile = result.CreateAttribute("dlna", "profileID", NS_DLNA);
  745. profile.InnerText = _profile.AlbumArtPn;
  746. icon.SetAttributeNode(profile);
  747. icon.InnerText = _serverAddress + "/Dlna/icons/people480.jpg";
  748. element.AppendChild(icon);
  749. icon = result.CreateElement("upnp", "icon", NS_UPNP);
  750. icon.InnerText = _serverAddress + "/Dlna/icons/people48.jpg";
  751. element.AppendChild(icon);
  752. }
  753. private void AddImageResElement(BaseItem item,
  754. XmlElement element,
  755. int maxWidth,
  756. int maxHeight,
  757. int playbackPercentage,
  758. int unplayedCount,
  759. string format,
  760. string org_Pn)
  761. {
  762. var imageInfo = GetImageInfo(item);
  763. if (imageInfo == null)
  764. {
  765. return;
  766. }
  767. var result = element.OwnerDocument;
  768. var albumartUrlInfo = GetImageUrl(imageInfo, maxWidth, maxHeight, playbackPercentage, unplayedCount, format);
  769. var res = result.CreateElement(string.Empty, "res", NS_DIDL);
  770. res.InnerText = albumartUrlInfo.Url;
  771. var width = albumartUrlInfo.Width;
  772. var height = albumartUrlInfo.Height;
  773. var contentFeatures = new ContentFeatureBuilder(_profile)
  774. .BuildImageHeader(format, width, height, imageInfo.IsDirectStream, org_Pn);
  775. res.SetAttribute("protocolInfo", String.Format(
  776. "http-get:*:{0}:{1}",
  777. MimeTypes.GetMimeType("file." + format),
  778. contentFeatures
  779. ));
  780. if (width.HasValue && height.HasValue)
  781. {
  782. res.SetAttribute("resolution", string.Format("{0}x{1}", width.Value, height.Value));
  783. }
  784. element.AppendChild(res);
  785. }
  786. private ImageDownloadInfo GetImageInfo(BaseItem item)
  787. {
  788. if (item.HasImage(ImageType.Primary))
  789. {
  790. return GetImageInfo(item, ImageType.Primary);
  791. }
  792. if (item.HasImage(ImageType.Thumb))
  793. {
  794. return GetImageInfo(item, ImageType.Thumb);
  795. }
  796. if (item.HasImage(ImageType.Backdrop))
  797. {
  798. if (item is Channel)
  799. {
  800. return GetImageInfo(item, ImageType.Backdrop);
  801. }
  802. }
  803. item = item.GetParents().FirstOrDefault(i => i.HasImage(ImageType.Primary));
  804. if (item != null)
  805. {
  806. return GetImageInfo(item, ImageType.Primary);
  807. }
  808. return null;
  809. }
  810. private ImageDownloadInfo GetImageInfo(BaseItem item, ImageType type)
  811. {
  812. var imageInfo = item.GetImageInfo(type, 0);
  813. string tag = null;
  814. try
  815. {
  816. tag = _imageProcessor.GetImageCacheTag(item, type);
  817. }
  818. catch
  819. {
  820. }
  821. int? width = null;
  822. int? height = null;
  823. //try
  824. //{
  825. // var size = _imageProcessor.GetImageSize(imageInfo);
  826. // width = Convert.ToInt32(size.Width);
  827. // height = Convert.ToInt32(size.Height);
  828. //}
  829. //catch
  830. //{
  831. //}
  832. var inputFormat = (Path.GetExtension(imageInfo.Path) ?? string.Empty)
  833. .TrimStart('.')
  834. .Replace("jpeg", "jpg", StringComparison.OrdinalIgnoreCase);
  835. return new ImageDownloadInfo
  836. {
  837. ItemId = item.Id.ToString("N"),
  838. Type = type,
  839. ImageTag = tag,
  840. Width = width,
  841. Height = height,
  842. Format = inputFormat,
  843. ItemImageInfo = imageInfo
  844. };
  845. }
  846. class ImageDownloadInfo
  847. {
  848. internal string ItemId;
  849. internal string ImageTag;
  850. internal ImageType Type;
  851. internal int? Width;
  852. internal int? Height;
  853. internal bool IsDirectStream;
  854. internal string Format;
  855. internal ItemImageInfo ItemImageInfo;
  856. }
  857. class ImageUrlInfo
  858. {
  859. internal string Url;
  860. internal int? Width;
  861. internal int? Height;
  862. }
  863. public static string GetClientId(BaseItem item, StubType? stubType)
  864. {
  865. var id = item.Id.ToString("N");
  866. if (stubType.HasValue)
  867. {
  868. id = stubType.Value.ToString().ToLower() + "_" + id;
  869. }
  870. return id;
  871. }
  872. public static string GetClientId(IHasMediaSources item)
  873. {
  874. var id = item.Id.ToString("N");
  875. return id;
  876. }
  877. private ImageUrlInfo GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, int playbackPercentage, int unplayedCount, string format)
  878. {
  879. var url = string.Format("{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/{7}/{8}",
  880. _serverAddress,
  881. info.ItemId,
  882. info.Type,
  883. info.ImageTag,
  884. format,
  885. maxWidth.ToString(CultureInfo.InvariantCulture),
  886. maxHeight.ToString(CultureInfo.InvariantCulture),
  887. playbackPercentage.ToString(CultureInfo.InvariantCulture),
  888. unplayedCount.ToString(CultureInfo.InvariantCulture)
  889. );
  890. var width = info.Width;
  891. var height = info.Height;
  892. info.IsDirectStream = false;
  893. if (width.HasValue && height.HasValue)
  894. {
  895. var newSize = DrawingUtils.Resize(new ImageSize
  896. {
  897. Height = height.Value,
  898. Width = width.Value
  899. }, null, null, maxWidth, maxHeight);
  900. width = Convert.ToInt32(newSize.Width);
  901. height = Convert.ToInt32(newSize.Height);
  902. var normalizedFormat = format
  903. .Replace("jpeg", "jpg", StringComparison.OrdinalIgnoreCase);
  904. if (string.Equals(info.Format, normalizedFormat, StringComparison.OrdinalIgnoreCase))
  905. {
  906. info.IsDirectStream = maxWidth >= width.Value && maxHeight >= height.Value;
  907. }
  908. }
  909. return new ImageUrlInfo
  910. {
  911. Url = url,
  912. Width = width,
  913. Height = height
  914. };
  915. }
  916. }
  917. }