2
0

DidlBuilder.cs 37 KB

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