Episode.cs 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.Linq;
  5. using System.Text.Json.Serialization;
  6. using Jellyfin.Data.Enums;
  7. using MediaBrowser.Controller.Providers;
  8. using MediaBrowser.Model.Entities;
  9. using MediaBrowser.Model.IO;
  10. using Microsoft.Extensions.Logging;
  11. namespace MediaBrowser.Controller.Entities.TV
  12. {
  13. /// <summary>
  14. /// Class Episode.
  15. /// </summary>
  16. public class Episode : Video, IHasTrailers, IHasLookupInfo<EpisodeInfo>, IHasSeries
  17. {
  18. public Episode()
  19. {
  20. RemoteTrailers = Array.Empty<MediaUrl>();
  21. LocalTrailerIds = Array.Empty<Guid>();
  22. RemoteTrailerIds = Array.Empty<Guid>();
  23. }
  24. /// <inheritdoc />
  25. public IReadOnlyList<Guid> LocalTrailerIds { get; set; }
  26. /// <inheritdoc />
  27. public IReadOnlyList<Guid> RemoteTrailerIds { get; set; }
  28. /// <summary>
  29. /// Gets the season in which it aired.
  30. /// </summary>
  31. /// <value>The aired season.</value>
  32. public int? AirsBeforeSeasonNumber { get; set; }
  33. public int? AirsAfterSeasonNumber { get; set; }
  34. public int? AirsBeforeEpisodeNumber { get; set; }
  35. /// <summary>
  36. /// This is the ending episode number for double episodes.
  37. /// </summary>
  38. /// <value>The index number.</value>
  39. public int? IndexNumberEnd { get; set; }
  40. public string FindSeriesSortName()
  41. {
  42. var series = Series;
  43. return series == null ? SeriesName : series.SortName;
  44. }
  45. [JsonIgnore]
  46. protected override bool SupportsOwnedItems => IsStacked || MediaSourceCount > 1;
  47. [JsonIgnore]
  48. public override bool SupportsInheritedParentImages => true;
  49. [JsonIgnore]
  50. public override bool SupportsPeople => true;
  51. [JsonIgnore]
  52. public int? AiredSeasonNumber => AirsAfterSeasonNumber ?? AirsBeforeSeasonNumber ?? ParentIndexNumber;
  53. [JsonIgnore]
  54. public override Folder LatestItemsIndexContainer => Series;
  55. [JsonIgnore]
  56. public override Guid DisplayParentId => SeasonId;
  57. [JsonIgnore]
  58. protected override bool EnableDefaultVideoUserDataKeys => false;
  59. public override double GetDefaultPrimaryImageAspectRatio()
  60. {
  61. // hack for tv plugins
  62. if (SourceType == SourceType.Channel)
  63. {
  64. return 0;
  65. }
  66. return 16.0 / 9;
  67. }
  68. public override List<string> GetUserDataKeys()
  69. {
  70. var list = base.GetUserDataKeys();
  71. var series = Series;
  72. if (series != null && ParentIndexNumber.HasValue && IndexNumber.HasValue)
  73. {
  74. var seriesUserDataKeys = series.GetUserDataKeys();
  75. var take = seriesUserDataKeys.Count;
  76. if (seriesUserDataKeys.Count > 1)
  77. {
  78. take--;
  79. }
  80. list.InsertRange(0, seriesUserDataKeys.Take(take).Select(i => i + ParentIndexNumber.Value.ToString("000") + IndexNumber.Value.ToString("000")));
  81. }
  82. return list;
  83. }
  84. /// <summary>
  85. /// This Episode's Series Instance.
  86. /// </summary>
  87. /// <value>The series.</value>
  88. [JsonIgnore]
  89. public Series Series
  90. {
  91. get
  92. {
  93. var seriesId = SeriesId;
  94. if (seriesId.Equals(Guid.Empty))
  95. {
  96. seriesId = FindSeriesId();
  97. }
  98. return !seriesId.Equals(Guid.Empty) ? (LibraryManager.GetItemById(seriesId) as Series) : null;
  99. }
  100. }
  101. [JsonIgnore]
  102. public Season Season
  103. {
  104. get
  105. {
  106. var seasonId = SeasonId;
  107. if (seasonId.Equals(Guid.Empty))
  108. {
  109. seasonId = FindSeasonId();
  110. }
  111. return !seasonId.Equals(Guid.Empty) ? (LibraryManager.GetItemById(seasonId) as Season) : null;
  112. }
  113. }
  114. [JsonIgnore]
  115. public bool IsInSeasonFolder => FindParent<Season>() != null;
  116. [JsonIgnore]
  117. public string SeriesPresentationUniqueKey { get; set; }
  118. [JsonIgnore]
  119. public string SeriesName { get; set; }
  120. [JsonIgnore]
  121. public string SeasonName { get; set; }
  122. public string FindSeriesPresentationUniqueKey()
  123. {
  124. var series = Series;
  125. return series == null ? null : series.PresentationUniqueKey;
  126. }
  127. public string FindSeasonName()
  128. {
  129. var season = Season;
  130. if (season == null)
  131. {
  132. if (ParentIndexNumber.HasValue)
  133. {
  134. return "Season " + ParentIndexNumber.Value.ToString(CultureInfo.InvariantCulture);
  135. }
  136. return "Season Unknown";
  137. }
  138. return season.Name;
  139. }
  140. public string FindSeriesName()
  141. {
  142. var series = Series;
  143. return series == null ? SeriesName : series.Name;
  144. }
  145. public Guid FindSeasonId()
  146. {
  147. var season = FindParent<Season>();
  148. // Episodes directly in series folder
  149. if (season == null)
  150. {
  151. var series = Series;
  152. if (series != null && ParentIndexNumber.HasValue)
  153. {
  154. var findNumber = ParentIndexNumber.Value;
  155. season = series.Children
  156. .OfType<Season>()
  157. .FirstOrDefault(i => i.IndexNumber.HasValue && i.IndexNumber.Value == findNumber);
  158. }
  159. }
  160. return season == null ? Guid.Empty : season.Id;
  161. }
  162. /// <summary>
  163. /// Creates the name of the sort.
  164. /// </summary>
  165. /// <returns>System.String.</returns>
  166. protected override string CreateSortName()
  167. {
  168. return (ParentIndexNumber != null ? ParentIndexNumber.Value.ToString("000 - ") : "")
  169. + (IndexNumber != null ? IndexNumber.Value.ToString("0000 - ") : "") + Name;
  170. }
  171. /// <summary>
  172. /// Determines whether [contains episode number] [the specified number].
  173. /// </summary>
  174. /// <param name="number">The number.</param>
  175. /// <returns><c>true</c> if [contains episode number] [the specified number]; otherwise, <c>false</c>.</returns>
  176. public bool ContainsEpisodeNumber(int number)
  177. {
  178. if (IndexNumber.HasValue)
  179. {
  180. if (IndexNumberEnd.HasValue)
  181. {
  182. return number >= IndexNumber.Value && number <= IndexNumberEnd.Value;
  183. }
  184. return IndexNumber.Value == number;
  185. }
  186. return false;
  187. }
  188. [JsonIgnore]
  189. public override bool SupportsRemoteImageDownloading
  190. {
  191. get
  192. {
  193. if (IsMissingEpisode)
  194. {
  195. return false;
  196. }
  197. return true;
  198. }
  199. }
  200. [JsonIgnore]
  201. public bool IsMissingEpisode => LocationType == LocationType.Virtual;
  202. [JsonIgnore]
  203. public Guid SeasonId { get; set; }
  204. [JsonIgnore]
  205. public Guid SeriesId { get; set; }
  206. public Guid FindSeriesId()
  207. {
  208. var series = FindParent<Series>();
  209. return series == null ? Guid.Empty : series.Id;
  210. }
  211. public override IEnumerable<Guid> GetAncestorIds()
  212. {
  213. var list = base.GetAncestorIds().ToList();
  214. var seasonId = SeasonId;
  215. if (!seasonId.Equals(Guid.Empty) && !list.Contains(seasonId))
  216. {
  217. list.Add(seasonId);
  218. }
  219. return list;
  220. }
  221. public override IEnumerable<FileSystemMetadata> GetDeletePaths()
  222. {
  223. return new[] {
  224. new FileSystemMetadata
  225. {
  226. FullName = Path,
  227. IsDirectory = IsFolder
  228. }
  229. }.Concat(GetLocalMetadataFilesToDelete());
  230. }
  231. public override UnratedItem GetBlockUnratedType()
  232. {
  233. return UnratedItem.Series;
  234. }
  235. public EpisodeInfo GetLookupInfo()
  236. {
  237. var id = GetItemLookupInfo<EpisodeInfo>();
  238. var series = Series;
  239. if (series != null)
  240. {
  241. id.SeriesProviderIds = series.ProviderIds;
  242. id.SeriesDisplayOrder = series.DisplayOrder;
  243. }
  244. id.IsMissingEpisode = IsMissingEpisode;
  245. id.IndexNumberEnd = IndexNumberEnd;
  246. return id;
  247. }
  248. public override bool BeforeMetadataRefresh(bool replaceAllMetdata)
  249. {
  250. var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata);
  251. if (!IsLocked)
  252. {
  253. if (SourceType == SourceType.Library)
  254. {
  255. try
  256. {
  257. if (LibraryManager.FillMissingEpisodeNumbersFromPath(this, replaceAllMetdata))
  258. {
  259. hasChanges = true;
  260. }
  261. }
  262. catch (Exception ex)
  263. {
  264. Logger.LogError(ex, "Error in FillMissingEpisodeNumbersFromPath. Episode: {Episode}", Path ?? Name ?? Id.ToString());
  265. }
  266. }
  267. }
  268. return hasChanges;
  269. }
  270. }
  271. }