BaseItemXmlParser.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659
  1. using MediaBrowser.Controller.Entities;
  2. using MediaBrowser.Model.Entities;
  3. using MediaBrowser.Model.Logging;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. using System.Threading;
  8. using System.Xml;
  9. namespace MediaBrowser.Controller.Providers
  10. {
  11. /// <summary>
  12. /// Provides a base class for parsing metadata xml
  13. /// </summary>
  14. /// <typeparam name="T"></typeparam>
  15. public class BaseItemXmlParser<T>
  16. where T : BaseItem, new()
  17. {
  18. /// <summary>
  19. /// The logger
  20. /// </summary>
  21. protected ILogger Logger { get; private set; }
  22. /// <summary>
  23. /// Initializes a new instance of the <see cref="BaseItemXmlParser{T}" /> class.
  24. /// </summary>
  25. /// <param name="logger">The logger.</param>
  26. public BaseItemXmlParser(ILogger logger)
  27. {
  28. Logger = logger;
  29. }
  30. /// <summary>
  31. /// Fetches metadata for an item from one xml file
  32. /// </summary>
  33. /// <param name="item">The item.</param>
  34. /// <param name="metadataFile">The metadata file.</param>
  35. /// <param name="cancellationToken">The cancellation token.</param>
  36. /// <exception cref="System.ArgumentNullException"></exception>
  37. public void Fetch(T item, string metadataFile, CancellationToken cancellationToken)
  38. {
  39. if (item == null)
  40. {
  41. throw new ArgumentNullException();
  42. }
  43. if (string.IsNullOrEmpty(metadataFile))
  44. {
  45. throw new ArgumentNullException();
  46. }
  47. // Use XmlReader for best performance
  48. using (var reader = XmlReader.Create(metadataFile))
  49. {
  50. reader.MoveToContent();
  51. // Loop through each element
  52. while (reader.Read())
  53. {
  54. cancellationToken.ThrowIfCancellationRequested();
  55. if (reader.NodeType == XmlNodeType.Element)
  56. {
  57. FetchDataFromXmlNode(reader, item);
  58. }
  59. }
  60. }
  61. }
  62. /// <summary>
  63. /// Fetches metadata from one Xml Element
  64. /// </summary>
  65. /// <param name="reader">The reader.</param>
  66. /// <param name="item">The item.</param>
  67. protected virtual void FetchDataFromXmlNode(XmlReader reader, T item)
  68. {
  69. switch (reader.Name)
  70. {
  71. // DateCreated
  72. case "Added":
  73. DateTime added;
  74. if (DateTime.TryParse(reader.ReadElementContentAsString() ?? string.Empty, out added))
  75. {
  76. item.DateCreated = added.ToUniversalTime();
  77. }
  78. break;
  79. case "LocalTitle":
  80. item.Name = reader.ReadElementContentAsString();
  81. break;
  82. case "Type":
  83. {
  84. var type = reader.ReadElementContentAsString();
  85. if (!string.IsNullOrWhiteSpace(type) && !type.Equals("none",StringComparison.OrdinalIgnoreCase))
  86. {
  87. item.DisplayMediaType = type;
  88. }
  89. break;
  90. }
  91. case "SortTitle":
  92. item.ForcedSortName = reader.ReadElementContentAsString();
  93. break;
  94. case "Overview":
  95. case "Description":
  96. item.Overview = reader.ReadElementContentAsString();
  97. break;
  98. case "TagLine":
  99. {
  100. var tagline = reader.ReadElementContentAsString();
  101. if (!string.IsNullOrWhiteSpace(tagline))
  102. {
  103. item.AddTagline(tagline);
  104. }
  105. break;
  106. }
  107. case "Website":
  108. {
  109. var val = reader.ReadElementContentAsString();
  110. if (!string.IsNullOrWhiteSpace(val))
  111. {
  112. item.HomePageUrl = val;
  113. }
  114. break;
  115. }
  116. case "TagLines":
  117. {
  118. FetchFromTaglinesNode(reader.ReadSubtree(), item);
  119. break;
  120. }
  121. case "ContentRating":
  122. case "certification":
  123. case "MPAARating":
  124. {
  125. var rating = reader.ReadElementContentAsString();
  126. if (!string.IsNullOrWhiteSpace(rating))
  127. {
  128. item.OfficialRating = rating;
  129. }
  130. break;
  131. }
  132. case "CustomRating":
  133. {
  134. var val = reader.ReadElementContentAsString();
  135. if (!string.IsNullOrWhiteSpace(val))
  136. {
  137. item.CustomRating = val;
  138. }
  139. break;
  140. }
  141. case "Runtime":
  142. case "RunningTime":
  143. {
  144. var text = reader.ReadElementContentAsString();
  145. if (!string.IsNullOrWhiteSpace(text))
  146. {
  147. int runtime;
  148. if (int.TryParse(text.Split(' ')[0], out runtime))
  149. {
  150. item.RunTimeTicks = TimeSpan.FromMinutes(runtime).Ticks;
  151. }
  152. }
  153. break;
  154. }
  155. case "Genre":
  156. {
  157. foreach (var name in SplitNames(reader.ReadElementContentAsString()))
  158. {
  159. if (string.IsNullOrWhiteSpace(name))
  160. {
  161. continue;
  162. }
  163. item.AddGenre(name);
  164. }
  165. break;
  166. }
  167. case "AspectRatio":
  168. {
  169. var val = reader.ReadElementContentAsString();
  170. if (!string.IsNullOrWhiteSpace(val))
  171. {
  172. item.AspectRatio = val;
  173. }
  174. break;
  175. }
  176. case "Network":
  177. {
  178. foreach (var name in SplitNames(reader.ReadElementContentAsString()))
  179. {
  180. if (string.IsNullOrWhiteSpace(name))
  181. {
  182. continue;
  183. }
  184. item.AddStudio(name);
  185. }
  186. break;
  187. }
  188. case "Director":
  189. {
  190. foreach (var p in SplitNames(reader.ReadElementContentAsString()).Select(v => new PersonInfo { Name = v, Type = PersonType.Director }))
  191. {
  192. if (string.IsNullOrWhiteSpace(p.Name))
  193. {
  194. continue;
  195. }
  196. item.AddPerson(p);
  197. }
  198. break;
  199. }
  200. case "Writer":
  201. {
  202. foreach (var p in SplitNames(reader.ReadElementContentAsString()).Select(v => new PersonInfo { Name = v, Type = PersonType.Writer }))
  203. {
  204. if (string.IsNullOrWhiteSpace(p.Name))
  205. {
  206. continue;
  207. }
  208. item.AddPerson(p);
  209. }
  210. break;
  211. }
  212. case "Actors":
  213. {
  214. foreach (var p in SplitNames(reader.ReadElementContentAsString()).Select(v => new PersonInfo { Name = v.Trim(), Type = PersonType.Actor }))
  215. {
  216. if (string.IsNullOrWhiteSpace(p.Name))
  217. {
  218. continue;
  219. }
  220. item.AddPerson(p);
  221. }
  222. break;
  223. }
  224. case "GuestStars":
  225. {
  226. foreach (var p in SplitNames(reader.ReadElementContentAsString()).Select(v => new PersonInfo { Name = v.Trim(), Type = PersonType.GuestStar }))
  227. {
  228. if (string.IsNullOrWhiteSpace(p.Name))
  229. {
  230. continue;
  231. }
  232. item.AddPerson(p);
  233. }
  234. break;
  235. }
  236. case "Trailer":
  237. {
  238. var val = reader.ReadElementContentAsString();
  239. if (!string.IsNullOrWhiteSpace(val))
  240. {
  241. item.AddTrailerUrl(val);
  242. }
  243. break;
  244. }
  245. case "ProductionYear":
  246. {
  247. var val = reader.ReadElementContentAsString();
  248. if (!string.IsNullOrWhiteSpace(val))
  249. {
  250. int ProductionYear;
  251. if (int.TryParse(val, out ProductionYear) && ProductionYear > 1850)
  252. {
  253. item.ProductionYear = ProductionYear;
  254. }
  255. }
  256. break;
  257. }
  258. case "Rating":
  259. case "IMDBrating":
  260. {
  261. var rating = reader.ReadElementContentAsString();
  262. if (!string.IsNullOrWhiteSpace(rating))
  263. {
  264. float val;
  265. if (float.TryParse(rating, out val))
  266. {
  267. item.CommunityRating = val;
  268. }
  269. }
  270. break;
  271. }
  272. case "FirstAired":
  273. {
  274. var firstAired = reader.ReadElementContentAsString();
  275. if (!string.IsNullOrWhiteSpace(firstAired))
  276. {
  277. DateTime airDate;
  278. if (DateTime.TryParse(firstAired, out airDate) && airDate.Year > 1850)
  279. {
  280. item.PremiereDate = airDate.ToUniversalTime();
  281. item.ProductionYear = airDate.Year;
  282. }
  283. }
  284. break;
  285. }
  286. case "TMDbId":
  287. var tmdb = reader.ReadElementContentAsString();
  288. if (!string.IsNullOrWhiteSpace(tmdb))
  289. {
  290. item.SetProviderId(MetadataProviders.Tmdb, tmdb);
  291. }
  292. break;
  293. case "TVcomId":
  294. var TVcomId = reader.ReadElementContentAsString();
  295. if (!string.IsNullOrWhiteSpace(TVcomId))
  296. {
  297. item.SetProviderId(MetadataProviders.Tvcom, TVcomId);
  298. }
  299. break;
  300. case "IMDB_ID":
  301. case "IMDB":
  302. case "IMDbId":
  303. var IMDbId = reader.ReadElementContentAsString();
  304. if (!string.IsNullOrWhiteSpace(IMDbId))
  305. {
  306. item.SetProviderId(MetadataProviders.Imdb, IMDbId);
  307. }
  308. break;
  309. case "Genres":
  310. FetchFromGenresNode(reader.ReadSubtree(), item);
  311. break;
  312. case "Persons":
  313. FetchDataFromPersonsNode(reader.ReadSubtree(), item);
  314. break;
  315. case "ParentalRating":
  316. FetchFromParentalRatingNode(reader.ReadSubtree(), item);
  317. break;
  318. case "Studios":
  319. FetchFromStudiosNode(reader.ReadSubtree(), item);
  320. break;
  321. default:
  322. reader.Skip();
  323. break;
  324. }
  325. }
  326. /// <summary>
  327. /// Fetches from taglines node.
  328. /// </summary>
  329. /// <param name="reader">The reader.</param>
  330. /// <param name="item">The item.</param>
  331. private void FetchFromTaglinesNode(XmlReader reader, T item)
  332. {
  333. reader.MoveToContent();
  334. while (reader.Read())
  335. {
  336. if (reader.NodeType == XmlNodeType.Element)
  337. {
  338. switch (reader.Name)
  339. {
  340. case "Tagline":
  341. {
  342. var val = reader.ReadElementContentAsString();
  343. if (!string.IsNullOrWhiteSpace(val))
  344. {
  345. item.AddTagline(val);
  346. }
  347. break;
  348. }
  349. default:
  350. reader.Skip();
  351. break;
  352. }
  353. }
  354. }
  355. }
  356. /// <summary>
  357. /// Fetches from genres node.
  358. /// </summary>
  359. /// <param name="reader">The reader.</param>
  360. /// <param name="item">The item.</param>
  361. private void FetchFromGenresNode(XmlReader reader, T item)
  362. {
  363. reader.MoveToContent();
  364. while (reader.Read())
  365. {
  366. if (reader.NodeType == XmlNodeType.Element)
  367. {
  368. switch (reader.Name)
  369. {
  370. case "Genre":
  371. {
  372. var genre = reader.ReadElementContentAsString();
  373. if (!string.IsNullOrWhiteSpace(genre))
  374. {
  375. item.AddGenre(genre);
  376. }
  377. break;
  378. }
  379. default:
  380. reader.Skip();
  381. break;
  382. }
  383. }
  384. }
  385. }
  386. /// <summary>
  387. /// Fetches the data from persons node.
  388. /// </summary>
  389. /// <param name="reader">The reader.</param>
  390. /// <param name="item">The item.</param>
  391. private void FetchDataFromPersonsNode(XmlReader reader, T item)
  392. {
  393. reader.MoveToContent();
  394. while (reader.Read())
  395. {
  396. if (reader.NodeType == XmlNodeType.Element)
  397. {
  398. switch (reader.Name)
  399. {
  400. case "Person":
  401. {
  402. item.AddPeople(GetPersonsFromXmlNode(reader.ReadSubtree()));
  403. break;
  404. }
  405. default:
  406. reader.Skip();
  407. break;
  408. }
  409. }
  410. }
  411. }
  412. /// <summary>
  413. /// Fetches from studios node.
  414. /// </summary>
  415. /// <param name="reader">The reader.</param>
  416. /// <param name="item">The item.</param>
  417. private void FetchFromStudiosNode(XmlReader reader, T item)
  418. {
  419. reader.MoveToContent();
  420. while (reader.Read())
  421. {
  422. if (reader.NodeType == XmlNodeType.Element)
  423. {
  424. switch (reader.Name)
  425. {
  426. case "Studio":
  427. {
  428. var studio = reader.ReadElementContentAsString();
  429. if (!string.IsNullOrWhiteSpace(studio))
  430. {
  431. item.AddStudio(studio);
  432. }
  433. break;
  434. }
  435. default:
  436. reader.Skip();
  437. break;
  438. }
  439. }
  440. }
  441. }
  442. /// <summary>
  443. /// Fetches from parental rating node.
  444. /// </summary>
  445. /// <param name="reader">The reader.</param>
  446. /// <param name="item">The item.</param>
  447. private void FetchFromParentalRatingNode(XmlReader reader, T item)
  448. {
  449. reader.MoveToContent();
  450. while (reader.Read())
  451. {
  452. if (reader.NodeType == XmlNodeType.Element)
  453. {
  454. switch (reader.Name)
  455. {
  456. case "Value":
  457. {
  458. var ratingString = reader.ReadElementContentAsString();
  459. int rating = 7;
  460. if (!string.IsNullOrWhiteSpace(ratingString))
  461. {
  462. int.TryParse(ratingString, out rating);
  463. }
  464. switch (rating)
  465. {
  466. case -1:
  467. item.OfficialRating = "NR";
  468. break;
  469. case 0:
  470. item.OfficialRating = "UR";
  471. break;
  472. case 1:
  473. item.OfficialRating = "G";
  474. break;
  475. case 3:
  476. item.OfficialRating = "PG";
  477. break;
  478. case 4:
  479. item.OfficialRating = "PG-13";
  480. break;
  481. case 5:
  482. item.OfficialRating = "NC-17";
  483. break;
  484. case 6:
  485. item.OfficialRating = "R";
  486. break;
  487. }
  488. break;
  489. }
  490. default:
  491. reader.Skip();
  492. break;
  493. }
  494. }
  495. }
  496. }
  497. /// <summary>
  498. /// Gets the persons from XML node.
  499. /// </summary>
  500. /// <param name="reader">The reader.</param>
  501. /// <returns>IEnumerable{PersonInfo}.</returns>
  502. private IEnumerable<PersonInfo> GetPersonsFromXmlNode(XmlReader reader)
  503. {
  504. var names = new List<string>();
  505. var type = string.Empty;
  506. var role = string.Empty;
  507. reader.MoveToContent();
  508. while (reader.Read())
  509. {
  510. if (reader.NodeType == XmlNodeType.Element)
  511. {
  512. switch (reader.Name)
  513. {
  514. case "Name":
  515. names.AddRange(SplitNames(reader.ReadElementContentAsString()));
  516. break;
  517. case "Type":
  518. {
  519. var val = reader.ReadElementContentAsString();
  520. if (!string.IsNullOrWhiteSpace(val))
  521. {
  522. type = val;
  523. }
  524. break;
  525. }
  526. case "Role":
  527. {
  528. var val = reader.ReadElementContentAsString();
  529. if (!string.IsNullOrWhiteSpace(val))
  530. {
  531. role = val;
  532. }
  533. break;
  534. }
  535. default:
  536. reader.Skip();
  537. break;
  538. }
  539. }
  540. }
  541. return names.Select(n => new PersonInfo { Name = n, Role = role, Type = type });
  542. }
  543. /// <summary>
  544. /// Used to split names of comma or pipe delimeted genres and people
  545. /// </summary>
  546. /// <param name="value">The value.</param>
  547. /// <returns>IEnumerable{System.String}.</returns>
  548. private IEnumerable<string> SplitNames(string value)
  549. {
  550. value = value ?? string.Empty;
  551. // Only split by comma if there is no pipe in the string
  552. // We have to be careful to not split names like Matthew, Jr.
  553. var separator = value.IndexOf('|') == -1 ? ',' : '|';
  554. value = value.Trim().Trim(separator);
  555. return string.IsNullOrWhiteSpace(value) ? new string[] { } : Split(value, separator, StringSplitOptions.RemoveEmptyEntries);
  556. }
  557. /// <summary>
  558. /// Provides an additional overload for string.split
  559. /// </summary>
  560. /// <param name="val">The val.</param>
  561. /// <param name="separator">The separator.</param>
  562. /// <param name="options">The options.</param>
  563. /// <returns>System.String[][].</returns>
  564. private static string[] Split(string val, char separator, StringSplitOptions options)
  565. {
  566. return val.Split(new[] { separator }, options);
  567. }
  568. }
  569. }