BaseItemXmlParser.cs 21 KB

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