DidlBuilder.cs 38 KB

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