DidlBuilder.cs 40 KB

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