DidlBuilder.cs 39 KB

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