DidlBuilder.cs 37 KB

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