XmlSaverHelpers.cs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Security;
  7. using System.Text;
  8. using System.Xml;
  9. using MediaBrowser.Common.Extensions;
  10. using MediaBrowser.Common.IO;
  11. using MediaBrowser.Controller.Configuration;
  12. using MediaBrowser.Controller.Entities;
  13. using MediaBrowser.Controller.Entities.Audio;
  14. using MediaBrowser.Controller.Entities.Movies;
  15. using MediaBrowser.Controller.Entities.TV;
  16. using MediaBrowser.Controller.Library;
  17. using MediaBrowser.Controller.Persistence;
  18. using MediaBrowser.Model.Configuration;
  19. using MediaBrowser.Model.Entities;
  20. using MediaBrowser.XbmcMetadata.Configuration;
  21. namespace MediaBrowser.XbmcMetadata.Savers
  22. {
  23. public static class XmlSaverHelpers
  24. {
  25. private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
  26. private static readonly Dictionary<string, string> CommonTags = new[] {
  27. "plot",
  28. "customrating",
  29. "lockdata",
  30. "type",
  31. "dateadded",
  32. "title",
  33. "rating",
  34. "year",
  35. "sorttitle",
  36. "mpaa",
  37. "mpaadescription",
  38. "aspectratio",
  39. "website",
  40. "collectionnumber",
  41. "tmdbid",
  42. "rottentomatoesid",
  43. "language",
  44. "tvcomid",
  45. "budget",
  46. "revenue",
  47. "tagline",
  48. "studio",
  49. "genre",
  50. "tag",
  51. "runtime",
  52. "actor",
  53. "criticratingsummary",
  54. "criticrating",
  55. "fileinfo",
  56. "director",
  57. "writer",
  58. "trailer",
  59. "premiered",
  60. "releasedate",
  61. "outline",
  62. "id",
  63. "votes",
  64. "credits",
  65. "originaltitle",
  66. "watched",
  67. "playcount",
  68. "lastplayed",
  69. "art",
  70. "resume",
  71. "biography",
  72. "formed",
  73. "review",
  74. "style",
  75. "imdbid",
  76. "imdb_id",
  77. "plotkeyword",
  78. "country",
  79. "audiodbalbumid",
  80. "audiodbartistid",
  81. "awardsummary",
  82. "enddate",
  83. "lockedfields",
  84. "metascore",
  85. "zap2itid",
  86. "tvrageid",
  87. "gamesdbid",
  88. "musicbrainzartistid",
  89. "musicbrainzalbumartistid",
  90. "musicbrainzalbumid",
  91. "musicbrainzreleasegroupid",
  92. "tvdbid",
  93. "collectionitem"
  94. }.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
  95. /// <summary>
  96. /// Saves the specified XML.
  97. /// </summary>
  98. /// <param name="xml">The XML.</param>
  99. /// <param name="path">The path.</param>
  100. /// <param name="xmlTagsUsed">The XML tags used.</param>
  101. public static void Save(StringBuilder xml, string path, List<string> xmlTagsUsed)
  102. {
  103. if (File.Exists(path))
  104. {
  105. var tags = xmlTagsUsed.ToList();
  106. var position = xml.ToString().LastIndexOf("</", StringComparison.OrdinalIgnoreCase);
  107. xml.Insert(position, GetCustomTags(path, tags));
  108. }
  109. var xmlDocument = new XmlDocument();
  110. xmlDocument.LoadXml(xml.ToString());
  111. //Add the new node to the document.
  112. xmlDocument.InsertBefore(xmlDocument.CreateXmlDeclaration("1.0", "UTF-8", "yes"), xmlDocument.DocumentElement);
  113. Directory.CreateDirectory(Path.GetDirectoryName(path));
  114. var wasHidden = false;
  115. var file = new FileInfo(path);
  116. // This will fail if the file is hidden
  117. if (file.Exists)
  118. {
  119. if ((file.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
  120. {
  121. file.Attributes &= ~FileAttributes.Hidden;
  122. wasHidden = true;
  123. }
  124. }
  125. using (var filestream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
  126. {
  127. using (var streamWriter = new StreamWriter(filestream, Encoding.UTF8))
  128. {
  129. xmlDocument.Save(streamWriter);
  130. }
  131. }
  132. if (wasHidden)
  133. {
  134. file.Refresh();
  135. // Add back the attribute
  136. file.Attributes |= FileAttributes.Hidden;
  137. }
  138. }
  139. /// <summary>
  140. /// Gets the custom tags.
  141. /// </summary>
  142. /// <param name="path">The path.</param>
  143. /// <param name="xmlTagsUsed">The XML tags used.</param>
  144. /// <returns>System.String.</returns>
  145. private static string GetCustomTags(string path, List<string> xmlTagsUsed)
  146. {
  147. var settings = new XmlReaderSettings
  148. {
  149. CheckCharacters = false,
  150. IgnoreProcessingInstructions = true,
  151. IgnoreComments = true,
  152. ValidationType = ValidationType.None
  153. };
  154. var builder = new StringBuilder();
  155. using (var streamReader = new StreamReader(path, Encoding.UTF8))
  156. {
  157. // Use XmlReader for best performance
  158. using (var reader = XmlReader.Create(streamReader, settings))
  159. {
  160. reader.MoveToContent();
  161. // Loop through each element
  162. while (reader.Read())
  163. {
  164. if (reader.NodeType == XmlNodeType.Element)
  165. {
  166. var name = reader.Name;
  167. if (!CommonTags.ContainsKey(name) && !xmlTagsUsed.Contains(name, StringComparer.OrdinalIgnoreCase))
  168. {
  169. builder.AppendLine(reader.ReadOuterXml());
  170. }
  171. else
  172. {
  173. reader.Skip();
  174. }
  175. }
  176. }
  177. }
  178. }
  179. return builder.ToString();
  180. }
  181. public static void AddMediaInfo<T>(T item, StringBuilder builder)
  182. where T : BaseItem, IHasMediaSources
  183. {
  184. builder.Append("<fileinfo>");
  185. builder.Append("<streamdetails>");
  186. foreach (var stream in item.GetMediaSources(false).First().MediaStreams)
  187. {
  188. builder.Append("<" + stream.Type.ToString().ToLower() + ">");
  189. if (!string.IsNullOrEmpty(stream.Codec))
  190. {
  191. builder.Append("<codec>" + SecurityElement.Escape(stream.Codec) + "</codec>");
  192. builder.Append("<micodec>" + SecurityElement.Escape(stream.Codec) + "</micodec>");
  193. }
  194. if (stream.BitRate.HasValue)
  195. {
  196. builder.Append("<bitrate>" + stream.BitRate.Value.ToString(UsCulture) + "</bitrate>");
  197. }
  198. if (stream.Width.HasValue)
  199. {
  200. builder.Append("<width>" + stream.Width.Value.ToString(UsCulture) + "</width>");
  201. }
  202. if (stream.Height.HasValue)
  203. {
  204. builder.Append("<height>" + stream.Height.Value.ToString(UsCulture) + "</height>");
  205. }
  206. if (!string.IsNullOrEmpty(stream.AspectRatio))
  207. {
  208. builder.Append("<aspect>" + SecurityElement.Escape(stream.AspectRatio) + "</aspect>");
  209. builder.Append("<aspectratio>" + SecurityElement.Escape(stream.AspectRatio) + "</aspectratio>");
  210. }
  211. var framerate = stream.AverageFrameRate ?? stream.RealFrameRate;
  212. if (framerate.HasValue)
  213. {
  214. builder.Append("<framerate>" + framerate.Value.ToString(UsCulture) + "</framerate>");
  215. }
  216. if (!string.IsNullOrEmpty(stream.Language))
  217. {
  218. builder.Append("<language>" + SecurityElement.Escape(stream.Language) + "</language>");
  219. }
  220. var scanType = stream.IsInterlaced ? "interlaced" : "progressive";
  221. if (!string.IsNullOrEmpty(scanType))
  222. {
  223. builder.Append("<scantype>" + SecurityElement.Escape(scanType) + "</scantype>");
  224. }
  225. if (stream.Channels.HasValue)
  226. {
  227. builder.Append("<channels>" + stream.Channels.Value.ToString(UsCulture) + "</channels>");
  228. }
  229. if (stream.SampleRate.HasValue)
  230. {
  231. builder.Append("<samplingrate>" + stream.SampleRate.Value.ToString(UsCulture) + "</samplingrate>");
  232. }
  233. builder.Append("<default>" + SecurityElement.Escape(stream.IsDefault.ToString()) + "</default>");
  234. builder.Append("<forced>" + SecurityElement.Escape(stream.IsForced.ToString()) + "</forced>");
  235. if (stream.Type == MediaStreamType.Video)
  236. {
  237. if (item.RunTimeTicks.HasValue)
  238. {
  239. var timespan = TimeSpan.FromTicks(item.RunTimeTicks.Value);
  240. builder.Append("<duration>" + Convert.ToInt32(timespan.TotalMinutes).ToString(UsCulture) + "</duration>");
  241. builder.Append("<durationinseconds>" + Convert.ToInt32(timespan.TotalSeconds).ToString(UsCulture) + "</durationinseconds>");
  242. }
  243. var video = item as Video;
  244. if (video != null)
  245. {
  246. //AddChapters(video, builder, itemRepository);
  247. if (video.Video3DFormat.HasValue)
  248. {
  249. switch (video.Video3DFormat.Value)
  250. {
  251. case Video3DFormat.FullSideBySide:
  252. builder.Append("<format3d>FSBS</format3d>");
  253. break;
  254. case Video3DFormat.FullTopAndBottom:
  255. builder.Append("<format3d>FTAB</format3d>");
  256. break;
  257. case Video3DFormat.HalfSideBySide:
  258. builder.Append("<format3d>HSBS</format3d>");
  259. break;
  260. case Video3DFormat.HalfTopAndBottom:
  261. builder.Append("<format3d>HTAB</format3d>");
  262. break;
  263. }
  264. }
  265. }
  266. }
  267. builder.Append("</" + stream.Type.ToString().ToLower() + ">");
  268. }
  269. builder.Append("</streamdetails>");
  270. builder.Append("</fileinfo>");
  271. }
  272. /// <summary>
  273. /// Adds the common nodes.
  274. /// </summary>
  275. /// <returns>Task.</returns>
  276. public static void AddCommonNodes(BaseItem item, StringBuilder builder, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataRepo, IFileSystem fileSystem, IServerConfigurationManager config)
  277. {
  278. var overview = (item.Overview ?? string.Empty)
  279. .StripHtml()
  280. .Replace("&quot;", "'");
  281. var options = config.GetNfoConfiguration();
  282. if (item is MusicArtist)
  283. {
  284. builder.Append("<biography><![CDATA[" + overview + "]]></biography>");
  285. }
  286. else if (item is MusicAlbum)
  287. {
  288. builder.Append("<review><![CDATA[" + overview + "]]></review>");
  289. }
  290. else
  291. {
  292. builder.Append("<plot><![CDATA[" + overview + "]]></plot>");
  293. }
  294. var hasShortOverview = item as IHasShortOverview;
  295. if (hasShortOverview != null)
  296. {
  297. var outline = (hasShortOverview.ShortOverview ?? string.Empty)
  298. .StripHtml()
  299. .Replace("&quot;", "'");
  300. builder.Append("<outline><![CDATA[" + outline + "]]></outline>");
  301. }
  302. else
  303. {
  304. builder.Append("<outline><![CDATA[" + overview + "]]></outline>");
  305. }
  306. builder.Append("<customrating>" + SecurityElement.Escape(item.CustomRating ?? string.Empty) + "</customrating>");
  307. builder.Append("<lockdata>" + item.IsLocked.ToString().ToLower() + "</lockdata>");
  308. if (item.LockedFields.Count > 0)
  309. {
  310. builder.Append("<lockedfields>" + string.Join("|", item.LockedFields.Select(i => i.ToString()).ToArray()) + "</lockedfields>");
  311. }
  312. if (!string.IsNullOrEmpty(item.DisplayMediaType))
  313. {
  314. builder.Append("<type>" + SecurityElement.Escape(item.DisplayMediaType) + "</type>");
  315. }
  316. builder.Append("<dateadded>" + SecurityElement.Escape(item.DateCreated.ToString("yyyy-MM-dd HH:mm:ss")) + "</dateadded>");
  317. builder.Append("<title>" + SecurityElement.Escape(item.Name ?? string.Empty) + "</title>");
  318. builder.Append("<originaltitle>" + SecurityElement.Escape(item.Name ?? string.Empty) + "</originaltitle>");
  319. var directors = item.People
  320. .Where(i => IsPersonType(i, PersonType.Director))
  321. .Select(i => i.Name)
  322. .ToList();
  323. foreach (var person in directors)
  324. {
  325. builder.Append("<director>" + SecurityElement.Escape(person) + "</director>");
  326. }
  327. var writers = item.People
  328. .Where(i => IsPersonType(i, PersonType.Writer))
  329. .Select(i => i.Name)
  330. .Distinct(StringComparer.OrdinalIgnoreCase)
  331. .ToList();
  332. foreach (var person in writers)
  333. {
  334. builder.Append("<writer>" + SecurityElement.Escape(person) + "</writer>");
  335. }
  336. if (writers.Count > 0)
  337. {
  338. builder.Append("<credits>" + SecurityElement.Escape(string.Join(" / ", writers.ToArray())) + "</credits>");
  339. }
  340. var hasTrailer = item as IHasTrailers;
  341. if (hasTrailer != null)
  342. {
  343. foreach (var trailer in hasTrailer.RemoteTrailers)
  344. {
  345. builder.Append("<trailer>" + SecurityElement.Escape(GetOutputTrailerUrl(trailer.Url)) + "</trailer>");
  346. }
  347. }
  348. if (item.CommunityRating.HasValue)
  349. {
  350. builder.Append("<rating>" + SecurityElement.Escape(item.CommunityRating.Value.ToString(UsCulture)) + "</rating>");
  351. }
  352. if (item.ProductionYear.HasValue)
  353. {
  354. builder.Append("<year>" + SecurityElement.Escape(item.ProductionYear.Value.ToString(UsCulture)) + "</year>");
  355. }
  356. if (!string.IsNullOrEmpty(item.ForcedSortName))
  357. {
  358. builder.Append("<sorttitle>" + SecurityElement.Escape(item.ForcedSortName) + "</sorttitle>");
  359. }
  360. if (!string.IsNullOrEmpty(item.OfficialRating))
  361. {
  362. builder.Append("<mpaa>" + SecurityElement.Escape(item.OfficialRating) + "</mpaa>");
  363. }
  364. if (!string.IsNullOrEmpty(item.OfficialRatingDescription))
  365. {
  366. builder.Append("<mpaadescription>" + SecurityElement.Escape(item.OfficialRatingDescription) + "</mpaadescription>");
  367. }
  368. var hasAspectRatio = item as IHasAspectRatio;
  369. if (hasAspectRatio != null)
  370. {
  371. if (!string.IsNullOrEmpty(hasAspectRatio.AspectRatio))
  372. {
  373. builder.Append("<aspectratio>" + SecurityElement.Escape(hasAspectRatio.AspectRatio) + "</aspectratio>");
  374. }
  375. }
  376. if (!string.IsNullOrEmpty(item.HomePageUrl))
  377. {
  378. builder.Append("<website>" + SecurityElement.Escape(item.HomePageUrl) + "</website>");
  379. }
  380. var rt = item.GetProviderId(MetadataProviders.RottenTomatoes);
  381. if (!string.IsNullOrEmpty(rt))
  382. {
  383. builder.Append("<rottentomatoesid>" + SecurityElement.Escape(rt) + "</rottentomatoesid>");
  384. }
  385. var tmdbCollection = item.GetProviderId(MetadataProviders.TmdbCollection);
  386. if (!string.IsNullOrEmpty(tmdbCollection))
  387. {
  388. builder.Append("<collectionnumber>" + SecurityElement.Escape(tmdbCollection) + "</collectionnumber>");
  389. }
  390. var imdb = item.GetProviderId(MetadataProviders.Imdb);
  391. if (!string.IsNullOrEmpty(imdb))
  392. {
  393. if (item is Series)
  394. {
  395. builder.Append("<imdb_id>" + SecurityElement.Escape(imdb) + "</imdb_id>");
  396. }
  397. else
  398. {
  399. builder.Append("<imdbid>" + SecurityElement.Escape(imdb) + "</imdbid>");
  400. }
  401. }
  402. // Series xml saver already saves this
  403. if (!(item is Series))
  404. {
  405. var tvdb = item.GetProviderId(MetadataProviders.Tvdb);
  406. if (!string.IsNullOrEmpty(tvdb))
  407. {
  408. builder.Append("<tvdbid>" + SecurityElement.Escape(tvdb) + "</tvdbid>");
  409. }
  410. }
  411. var tmdb = item.GetProviderId(MetadataProviders.Tmdb);
  412. if (!string.IsNullOrEmpty(tmdb))
  413. {
  414. builder.Append("<tmdbid>" + SecurityElement.Escape(tmdb) + "</tmdbid>");
  415. }
  416. var tvcom = item.GetProviderId(MetadataProviders.Tvcom);
  417. if (!string.IsNullOrEmpty(tvcom))
  418. {
  419. builder.Append("<tvcomid>" + SecurityElement.Escape(tvcom) + "</tvcomid>");
  420. }
  421. var hasLanguage = item as IHasPreferredMetadataLanguage;
  422. if (hasLanguage != null)
  423. {
  424. if (!string.IsNullOrEmpty(hasLanguage.PreferredMetadataLanguage))
  425. {
  426. builder.Append("<language>" + SecurityElement.Escape(hasLanguage.PreferredMetadataLanguage) + "</language>");
  427. }
  428. }
  429. if (item.PremiereDate.HasValue && !(item is Episode))
  430. {
  431. var formatString = options.ReleaseDateFormat;
  432. if (item is MusicArtist)
  433. {
  434. builder.Append("<formed>" + SecurityElement.Escape(item.PremiereDate.Value.ToString(formatString)) + "</formed>");
  435. }
  436. else
  437. {
  438. builder.Append("<premiered>" + SecurityElement.Escape(item.PremiereDate.Value.ToString(formatString)) + "</premiered>");
  439. builder.Append("<releasedate>" + SecurityElement.Escape(item.PremiereDate.Value.ToString(formatString)) + "</releasedate>");
  440. }
  441. }
  442. if (item.EndDate.HasValue)
  443. {
  444. if (!(item is Episode))
  445. {
  446. var formatString = options.ReleaseDateFormat;
  447. builder.Append("<enddate>" + SecurityElement.Escape(item.EndDate.Value.ToString(formatString)) + "</enddate>");
  448. }
  449. }
  450. var hasCriticRating = item as IHasCriticRating;
  451. if (hasCriticRating != null)
  452. {
  453. if (hasCriticRating.CriticRating.HasValue)
  454. {
  455. builder.Append("<criticrating>" + SecurityElement.Escape(hasCriticRating.CriticRating.Value.ToString(UsCulture)) + "</criticrating>");
  456. }
  457. if (!string.IsNullOrEmpty(hasCriticRating.CriticRatingSummary))
  458. {
  459. builder.Append("<criticratingsummary><![CDATA[" + hasCriticRating.CriticRatingSummary + "]]></criticratingsummary>");
  460. }
  461. }
  462. var hasDisplayOrder = item as IHasDisplayOrder;
  463. if (hasDisplayOrder != null)
  464. {
  465. if (!string.IsNullOrEmpty(hasDisplayOrder.DisplayOrder))
  466. {
  467. builder.Append("<displayorder>" + SecurityElement.Escape(hasDisplayOrder.DisplayOrder) + "</displayorder>");
  468. }
  469. }
  470. if (item.VoteCount.HasValue)
  471. {
  472. builder.Append("<votes>" + SecurityElement.Escape(item.VoteCount.Value.ToString(UsCulture)) + "</votes>");
  473. }
  474. var hasBudget = item as IHasBudget;
  475. if (hasBudget != null)
  476. {
  477. if (hasBudget.Budget.HasValue)
  478. {
  479. builder.Append("<budget>" + SecurityElement.Escape(hasBudget.Budget.Value.ToString(UsCulture)) + "</budget>");
  480. }
  481. if (hasBudget.Revenue.HasValue)
  482. {
  483. builder.Append("<revenue>" + SecurityElement.Escape(hasBudget.Revenue.Value.ToString(UsCulture)) + "</revenue>");
  484. }
  485. }
  486. var hasMetascore = item as IHasMetascore;
  487. if (hasMetascore != null && hasMetascore.Metascore.HasValue)
  488. {
  489. builder.Append("<metascore>" + SecurityElement.Escape(hasMetascore.Metascore.Value.ToString(UsCulture)) + "</metascore>");
  490. }
  491. // Use original runtime here, actual file runtime later in MediaInfo
  492. var runTimeTicks = item.RunTimeTicks;
  493. if (runTimeTicks.HasValue)
  494. {
  495. var timespan = TimeSpan.FromTicks(runTimeTicks.Value);
  496. builder.Append("<runtime>" + Convert.ToInt32(timespan.TotalMinutes).ToString(UsCulture) + "</runtime>");
  497. }
  498. var hasTaglines = item as IHasTaglines;
  499. if (hasTaglines != null)
  500. {
  501. foreach (var tagline in hasTaglines.Taglines)
  502. {
  503. builder.Append("<tagline>" + SecurityElement.Escape(tagline) + "</tagline>");
  504. }
  505. }
  506. var hasProductionLocations = item as IHasProductionLocations;
  507. if (hasProductionLocations != null)
  508. {
  509. foreach (var country in hasProductionLocations.ProductionLocations)
  510. {
  511. builder.Append("<country>" + SecurityElement.Escape(country) + "</country>");
  512. }
  513. }
  514. foreach (var genre in item.Genres)
  515. {
  516. builder.Append("<genre>" + SecurityElement.Escape(genre) + "</genre>");
  517. }
  518. foreach (var studio in item.Studios)
  519. {
  520. builder.Append("<studio>" + SecurityElement.Escape(studio) + "</studio>");
  521. }
  522. var hasTags = item as IHasTags;
  523. if (hasTags != null)
  524. {
  525. foreach (var tag in hasTags.Tags)
  526. {
  527. if (item is MusicAlbum || item is MusicArtist)
  528. {
  529. builder.Append("<style>" + SecurityElement.Escape(tag) + "</style>");
  530. }
  531. else
  532. {
  533. builder.Append("<tag>" + SecurityElement.Escape(tag) + "</tag>");
  534. }
  535. }
  536. }
  537. var hasKeywords = item as IHasKeywords;
  538. if (hasKeywords != null)
  539. {
  540. foreach (var tag in hasKeywords.Keywords)
  541. {
  542. builder.Append("<plotkeyword>" + SecurityElement.Escape(tag) + "</plotkeyword>");
  543. }
  544. }
  545. var hasAwards = item as IHasAwards;
  546. if (hasAwards != null && !string.IsNullOrEmpty(hasAwards.AwardSummary))
  547. {
  548. builder.Append("<awardsummary>" + SecurityElement.Escape(hasAwards.AwardSummary) + "</awardsummary>");
  549. }
  550. var externalId = item.GetProviderId(MetadataProviders.AudioDbArtist);
  551. if (!string.IsNullOrEmpty(externalId))
  552. {
  553. builder.Append("<audiodbartistid>" + SecurityElement.Escape(externalId) + "</audiodbartistid>");
  554. }
  555. externalId = item.GetProviderId(MetadataProviders.AudioDbAlbum);
  556. if (!string.IsNullOrEmpty(externalId))
  557. {
  558. builder.Append("<audiodbalbumid>" + SecurityElement.Escape(externalId) + "</audiodbalbumid>");
  559. }
  560. externalId = item.GetProviderId(MetadataProviders.Zap2It);
  561. if (!string.IsNullOrEmpty(externalId))
  562. {
  563. builder.Append("<zap2itid>" + SecurityElement.Escape(externalId) + "</zap2itid>");
  564. }
  565. externalId = item.GetProviderId(MetadataProviders.MusicBrainzAlbum);
  566. if (!string.IsNullOrEmpty(externalId))
  567. {
  568. builder.Append("<musicbrainzalbumid>" + SecurityElement.Escape(externalId) + "</musicbrainzalbumid>");
  569. }
  570. externalId = item.GetProviderId(MetadataProviders.MusicBrainzAlbumArtist);
  571. if (!string.IsNullOrEmpty(externalId))
  572. {
  573. builder.Append("<musicbrainzalbumartistid>" + SecurityElement.Escape(externalId) + "</musicbrainzalbumartistid>");
  574. }
  575. externalId = item.GetProviderId(MetadataProviders.MusicBrainzArtist);
  576. if (!string.IsNullOrEmpty(externalId))
  577. {
  578. builder.Append("<musicbrainzartistid>" + SecurityElement.Escape(externalId) + "</musicbrainzartistid>");
  579. }
  580. externalId = item.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup);
  581. if (!string.IsNullOrEmpty(externalId))
  582. {
  583. builder.Append("<musicbrainzreleasegroupid>" + SecurityElement.Escape(externalId) + "</musicbrainzreleasegroupid>");
  584. }
  585. externalId = item.GetProviderId(MetadataProviders.Gamesdb);
  586. if (!string.IsNullOrEmpty(externalId))
  587. {
  588. builder.Append("<gamesdbid>" + SecurityElement.Escape(externalId) + "</gamesdbid>");
  589. }
  590. externalId = item.GetProviderId(MetadataProviders.TvRage);
  591. if (!string.IsNullOrEmpty(externalId))
  592. {
  593. builder.Append("<tvrageid>" + SecurityElement.Escape(externalId) + "</tvrageid>");
  594. }
  595. if (options.SaveImagePathsInNfo)
  596. {
  597. AddImages(item, builder, fileSystem, config);
  598. }
  599. AddUserData(item, builder, userManager, userDataRepo, options);
  600. AddActors(item, builder, libraryManager, fileSystem, config);
  601. var folder = item as BoxSet;
  602. if (folder != null)
  603. {
  604. AddCollectionItems(folder, builder);
  605. }
  606. }
  607. public static void AddChapters(Video item, StringBuilder builder, IItemRepository repository)
  608. {
  609. var chapters = repository.GetChapters(item.Id);
  610. foreach (var chapter in chapters)
  611. {
  612. builder.Append("<chapter>");
  613. builder.Append("<name>" + SecurityElement.Escape(chapter.Name) + "</name>");
  614. var time = TimeSpan.FromTicks(chapter.StartPositionTicks);
  615. var ms = Convert.ToInt64(time.TotalMilliseconds);
  616. builder.Append("<startpositionms>" + SecurityElement.Escape(ms.ToString(UsCulture)) + "</startpositionms>");
  617. builder.Append("</chapter>");
  618. }
  619. }
  620. public static void AddCollectionItems(Folder item, StringBuilder builder)
  621. {
  622. var items = item.LinkedChildren
  623. .Where(i => i.Type == LinkedChildType.Manual && !string.IsNullOrWhiteSpace(i.ItemName))
  624. .ToList();
  625. foreach (var link in items)
  626. {
  627. builder.Append("<collectionitem>");
  628. builder.Append("<name>" + SecurityElement.Escape(link.ItemName) + "</name>");
  629. builder.Append("<type>" + SecurityElement.Escape(link.ItemType) + "</type>");
  630. if (link.ItemYear.HasValue)
  631. {
  632. builder.Append("<year>" + SecurityElement.Escape(link.ItemYear.Value.ToString(UsCulture)) + "</year>");
  633. }
  634. builder.Append("</collectionitem>");
  635. }
  636. }
  637. /// <summary>
  638. /// Gets the output trailer URL.
  639. /// </summary>
  640. /// <param name="url">The URL.</param>
  641. /// <returns>System.String.</returns>
  642. private static string GetOutputTrailerUrl(string url)
  643. {
  644. // This is what xbmc expects
  645. return url.Replace("http://www.youtube.com/watch?v=",
  646. "plugin://plugin.video.youtube/?action=play_video&videoid=",
  647. StringComparison.OrdinalIgnoreCase);
  648. }
  649. private static void AddImages(BaseItem item, StringBuilder builder, IFileSystem fileSystem, IServerConfigurationManager config)
  650. {
  651. builder.Append("<art>");
  652. var poster = item.PrimaryImagePath;
  653. if (!string.IsNullOrEmpty(poster))
  654. {
  655. builder.Append("<poster>" + SecurityElement.Escape(GetPathToSave(item.PrimaryImagePath, fileSystem, config)) + "</poster>");
  656. }
  657. foreach (var backdrop in item.GetImages(ImageType.Backdrop))
  658. {
  659. builder.Append("<fanart>" + SecurityElement.Escape(GetPathToSave(backdrop.Path, fileSystem, config)) + "</fanart>");
  660. }
  661. builder.Append("</art>");
  662. }
  663. private static void AddUserData(BaseItem item, StringBuilder builder, IUserManager userManager, IUserDataManager userDataRepo, XbmcMetadataOptions options)
  664. {
  665. var userId = options.UserId;
  666. if (string.IsNullOrWhiteSpace(userId))
  667. {
  668. return;
  669. }
  670. var user = userManager.GetUserById(new Guid(userId));
  671. if (user == null)
  672. {
  673. return;
  674. }
  675. if (item.IsFolder)
  676. {
  677. return;
  678. }
  679. var userdata = userDataRepo.GetUserData(user.Id, item.GetUserDataKey());
  680. builder.Append("<playcount>" + userdata.PlayCount.ToString(UsCulture) + "</playcount>");
  681. builder.Append("<watched>" + userdata.Played.ToString().ToLower() + "</watched>");
  682. if (userdata.LastPlayedDate.HasValue)
  683. {
  684. builder.Append("<lastplayed>" + SecurityElement.Escape(userdata.LastPlayedDate.Value.ToString("yyyy-MM-dd HH:mm:ss")) + "</lastplayed>");
  685. }
  686. builder.Append("<resume>");
  687. var runTimeTicks = item.RunTimeTicks ?? 0;
  688. builder.Append("<position>" + TimeSpan.FromTicks(userdata.PlaybackPositionTicks).TotalSeconds.ToString(UsCulture) + "</position>");
  689. builder.Append("<total>" + TimeSpan.FromTicks(runTimeTicks).TotalSeconds.ToString(UsCulture) + "</total>");
  690. builder.Append("</resume>");
  691. }
  692. public static void AddActors(BaseItem item, StringBuilder builder, ILibraryManager libraryManager, IFileSystem fileSystem, IServerConfigurationManager config)
  693. {
  694. var actors = item.People
  695. .Where(i => !IsPersonType(i, PersonType.Director) && !IsPersonType(i, PersonType.Writer))
  696. .ToList();
  697. foreach (var person in actors)
  698. {
  699. builder.Append("<actor>");
  700. builder.Append("<name>" + SecurityElement.Escape(person.Name ?? string.Empty) + "</name>");
  701. builder.Append("<role>" + SecurityElement.Escape(person.Role ?? string.Empty) + "</role>");
  702. builder.Append("<type>" + SecurityElement.Escape(person.Type ?? string.Empty) + "</type>");
  703. if (person.SortOrder.HasValue)
  704. {
  705. builder.Append("<sortorder>" + SecurityElement.Escape(person.SortOrder.Value.ToString(UsCulture)) + "</sortorder>");
  706. }
  707. try
  708. {
  709. var personEntity = libraryManager.GetPerson(person.Name);
  710. if (!string.IsNullOrEmpty(personEntity.PrimaryImagePath))
  711. {
  712. builder.Append("<thumb>" + SecurityElement.Escape(GetPathToSave(personEntity.PrimaryImagePath, fileSystem, config)) + "</thumb>");
  713. }
  714. }
  715. catch (Exception)
  716. {
  717. // Already logged in core
  718. }
  719. builder.Append("</actor>");
  720. }
  721. }
  722. private static bool IsPersonType(PersonInfo person, string type)
  723. {
  724. return string.Equals(person.Type, type, StringComparison.OrdinalIgnoreCase) || string.Equals(person.Role, type, StringComparison.OrdinalIgnoreCase);
  725. }
  726. private static string GetPathToSave(string path, IFileSystem fileSystem, IServerConfigurationManager config)
  727. {
  728. foreach (var map in config.Configuration.PathSubstitutions)
  729. {
  730. path = fileSystem.SubstitutePath(path, map.From, map.To);
  731. }
  732. return path;
  733. }
  734. public static string ReplaceString(string str, string oldValue, string newValue, StringComparison comparison)
  735. {
  736. var sb = new StringBuilder();
  737. int previousIndex = 0;
  738. int index = str.IndexOf(oldValue, comparison);
  739. while (index != -1)
  740. {
  741. sb.Append(str.Substring(previousIndex, index - previousIndex));
  742. sb.Append(newValue);
  743. index += oldValue.Length;
  744. previousIndex = index;
  745. index = str.IndexOf(oldValue, index, comparison);
  746. }
  747. sb.Append(str.Substring(previousIndex));
  748. return sb.ToString();
  749. }
  750. }
  751. }