DidlBuilder.cs 36 KB

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