Video.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. using MediaBrowser.Controller.Library;
  2. using MediaBrowser.Controller.Persistence;
  3. using MediaBrowser.Controller.Providers;
  4. using MediaBrowser.Controller.Resolvers;
  5. using MediaBrowser.Model.Entities;
  6. using System;
  7. using System.Collections;
  8. using System.Collections.Generic;
  9. using System.IO;
  10. using System.Linq;
  11. using System.Runtime.Serialization;
  12. using System.Threading;
  13. using System.Threading.Tasks;
  14. namespace MediaBrowser.Controller.Entities
  15. {
  16. /// <summary>
  17. /// Class Video
  18. /// </summary>
  19. public class Video : BaseItem, IHasMediaStreams, IHasAspectRatio, IHasTags, ISupportsPlaceHolders
  20. {
  21. public bool IsMultiPart { get; set; }
  22. public bool HasLocalAlternateVersions { get; set; }
  23. public Guid? PrimaryVersionId { get; set; }
  24. public List<Guid> AdditionalPartIds { get; set; }
  25. public List<Guid> LocalAlternateVersionIds { get; set; }
  26. public string FormatName { get; set; }
  27. public long? Size { get; set; }
  28. public string Container { get; set; }
  29. public int? TotalBitrate { get; set; }
  30. public Video()
  31. {
  32. PlayableStreamFileNames = new List<string>();
  33. AdditionalPartIds = new List<Guid>();
  34. LocalAlternateVersionIds = new List<Guid>();
  35. Tags = new List<string>();
  36. SubtitleFiles = new List<string>();
  37. LinkedAlternateVersions = new List<LinkedChild>();
  38. }
  39. [IgnoreDataMember]
  40. public int MediaSourceCount
  41. {
  42. get
  43. {
  44. return LinkedAlternateVersions.Count + LocalAlternateVersionIds.Count + 1;
  45. }
  46. }
  47. public List<LinkedChild> LinkedAlternateVersions { get; set; }
  48. /// <summary>
  49. /// Gets the linked children.
  50. /// </summary>
  51. /// <returns>IEnumerable{BaseItem}.</returns>
  52. public IEnumerable<Video> GetAlternateVersions()
  53. {
  54. var filesWithinSameDirectory = LocalAlternateVersionIds
  55. .Select(i => LibraryManager.GetItemById(i))
  56. .Where(i => i != null)
  57. .OfType<Video>();
  58. return filesWithinSameDirectory.Concat(GetLinkedAlternateVersions())
  59. .OrderBy(i => i.SortName);
  60. }
  61. public IEnumerable<Video> GetLinkedAlternateVersions()
  62. {
  63. var linkedVersions = LinkedAlternateVersions
  64. .Select(GetLinkedChild)
  65. .Where(i => i != null)
  66. .OfType<Video>();
  67. return linkedVersions
  68. .OrderBy(i => i.SortName);
  69. }
  70. /// <summary>
  71. /// Gets the additional parts.
  72. /// </summary>
  73. /// <returns>IEnumerable{Video}.</returns>
  74. public IEnumerable<Video> GetAdditionalParts()
  75. {
  76. return AdditionalPartIds
  77. .Select(i => LibraryManager.GetItemById(i))
  78. .Where(i => i != null)
  79. .OfType<Video>()
  80. .OrderBy(i => i.SortName);
  81. }
  82. /// <summary>
  83. /// Gets or sets the subtitle paths.
  84. /// </summary>
  85. /// <value>The subtitle paths.</value>
  86. public List<string> SubtitleFiles { get; set; }
  87. /// <summary>
  88. /// Gets or sets a value indicating whether this instance has subtitles.
  89. /// </summary>
  90. /// <value><c>true</c> if this instance has subtitles; otherwise, <c>false</c>.</value>
  91. public bool HasSubtitles { get; set; }
  92. public bool IsPlaceHolder { get; set; }
  93. /// <summary>
  94. /// Gets or sets the tags.
  95. /// </summary>
  96. /// <value>The tags.</value>
  97. public List<string> Tags { get; set; }
  98. /// <summary>
  99. /// Gets or sets the video bit rate.
  100. /// </summary>
  101. /// <value>The video bit rate.</value>
  102. public int? VideoBitRate { get; set; }
  103. /// <summary>
  104. /// Gets or sets the default index of the video stream.
  105. /// </summary>
  106. /// <value>The default index of the video stream.</value>
  107. public int? DefaultVideoStreamIndex { get; set; }
  108. /// <summary>
  109. /// Gets or sets the type of the video.
  110. /// </summary>
  111. /// <value>The type of the video.</value>
  112. public VideoType VideoType { get; set; }
  113. /// <summary>
  114. /// Gets or sets the type of the iso.
  115. /// </summary>
  116. /// <value>The type of the iso.</value>
  117. public IsoType? IsoType { get; set; }
  118. /// <summary>
  119. /// Gets or sets the video3 D format.
  120. /// </summary>
  121. /// <value>The video3 D format.</value>
  122. public Video3DFormat? Video3DFormat { get; set; }
  123. /// <summary>
  124. /// If the video is a folder-rip, this will hold the file list for the largest playlist
  125. /// </summary>
  126. public List<string> PlayableStreamFileNames { get; set; }
  127. /// <summary>
  128. /// Gets the playable stream files.
  129. /// </summary>
  130. /// <returns>List{System.String}.</returns>
  131. public List<string> GetPlayableStreamFiles()
  132. {
  133. return GetPlayableStreamFiles(Path);
  134. }
  135. /// <summary>
  136. /// Gets or sets the aspect ratio.
  137. /// </summary>
  138. /// <value>The aspect ratio.</value>
  139. public string AspectRatio { get; set; }
  140. [IgnoreDataMember]
  141. public override string ContainingFolderPath
  142. {
  143. get
  144. {
  145. if (IsMultiPart)
  146. {
  147. return System.IO.Path.GetDirectoryName(Path);
  148. }
  149. if (!IsPlaceHolder)
  150. {
  151. if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd ||
  152. VideoType == VideoType.HdDvd)
  153. {
  154. return Path;
  155. }
  156. }
  157. return base.ContainingFolderPath;
  158. }
  159. }
  160. public string MainFeaturePlaylistName { get; set; }
  161. /// <summary>
  162. /// Gets the playable stream files.
  163. /// </summary>
  164. /// <param name="rootPath">The root path.</param>
  165. /// <returns>List{System.String}.</returns>
  166. public List<string> GetPlayableStreamFiles(string rootPath)
  167. {
  168. var allFiles = Directory.EnumerateFiles(rootPath, "*", SearchOption.AllDirectories).ToList();
  169. return PlayableStreamFileNames.Select(name => allFiles.FirstOrDefault(f => string.Equals(System.IO.Path.GetFileName(f), name, StringComparison.OrdinalIgnoreCase)))
  170. .Where(f => !string.IsNullOrEmpty(f))
  171. .ToList();
  172. }
  173. /// <summary>
  174. /// Gets a value indicating whether [is3 D].
  175. /// </summary>
  176. /// <value><c>true</c> if [is3 D]; otherwise, <c>false</c>.</value>
  177. [IgnoreDataMember]
  178. public bool Is3D
  179. {
  180. get { return Video3DFormat.HasValue; }
  181. }
  182. public bool IsHD { get; set; }
  183. /// <summary>
  184. /// Gets the type of the media.
  185. /// </summary>
  186. /// <value>The type of the media.</value>
  187. public override string MediaType
  188. {
  189. get
  190. {
  191. return Model.Entities.MediaType.Video;
  192. }
  193. }
  194. protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
  195. {
  196. var hasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
  197. // Must have a parent to have additional parts or alternate versions
  198. // In other words, it must be part of the Parent/Child tree
  199. // The additional parts won't have additional parts themselves
  200. if (LocationType == LocationType.FileSystem && Parent != null)
  201. {
  202. if (IsMultiPart)
  203. {
  204. var additionalPartsChanged = await RefreshAdditionalParts(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
  205. if (additionalPartsChanged)
  206. {
  207. hasChanges = true;
  208. }
  209. }
  210. else
  211. {
  212. RefreshLinkedAlternateVersions();
  213. var additionalPartsChanged = await RefreshAlternateVersionsWithinSameDirectory(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
  214. if (additionalPartsChanged)
  215. {
  216. hasChanges = true;
  217. }
  218. }
  219. }
  220. return hasChanges;
  221. }
  222. private bool RefreshLinkedAlternateVersions()
  223. {
  224. foreach (var child in LinkedAlternateVersions)
  225. {
  226. // Reset the cached value
  227. if (child.ItemId.HasValue && child.ItemId.Value == Guid.Empty)
  228. {
  229. child.ItemId = null;
  230. }
  231. }
  232. return false;
  233. }
  234. /// <summary>
  235. /// Refreshes the additional parts.
  236. /// </summary>
  237. /// <param name="options">The options.</param>
  238. /// <param name="fileSystemChildren">The file system children.</param>
  239. /// <param name="cancellationToken">The cancellation token.</param>
  240. /// <returns>Task{System.Boolean}.</returns>
  241. private async Task<bool> RefreshAdditionalParts(MetadataRefreshOptions options, IEnumerable<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
  242. {
  243. var newItems = LoadAdditionalParts(fileSystemChildren, options.DirectoryService).ToList();
  244. var newItemIds = newItems.Select(i => i.Id).ToList();
  245. var itemsChanged = !AdditionalPartIds.SequenceEqual(newItemIds);
  246. var tasks = newItems.Select(i => i.RefreshMetadata(options, cancellationToken));
  247. await Task.WhenAll(tasks).ConfigureAwait(false);
  248. AdditionalPartIds = newItemIds;
  249. return itemsChanged;
  250. }
  251. /// <summary>
  252. /// Loads the additional parts.
  253. /// </summary>
  254. /// <returns>IEnumerable{Video}.</returns>
  255. private IEnumerable<Video> LoadAdditionalParts(IEnumerable<FileSystemInfo> fileSystemChildren, IDirectoryService directoryService)
  256. {
  257. IEnumerable<FileSystemInfo> files;
  258. var path = Path;
  259. if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd)
  260. {
  261. files = fileSystemChildren.Where(i =>
  262. {
  263. if ((i.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
  264. {
  265. return !string.Equals(i.FullName, path, StringComparison.OrdinalIgnoreCase) && EntityResolutionHelper.IsMultiPartFolder(i.FullName) && EntityResolutionHelper.IsMultiPartFile(i.Name);
  266. }
  267. return false;
  268. });
  269. }
  270. else
  271. {
  272. files = fileSystemChildren.Where(i =>
  273. {
  274. if ((i.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
  275. {
  276. return false;
  277. }
  278. return !string.Equals(i.FullName, path, StringComparison.OrdinalIgnoreCase) && EntityResolutionHelper.IsVideoFile(i.FullName) && EntityResolutionHelper.IsMultiPartFile(i.Name);
  279. });
  280. }
  281. return LibraryManager.ResolvePaths<Video>(files, directoryService, null).Select(video =>
  282. {
  283. // Try to retrieve it from the db. If we don't find it, use the resolved version
  284. var dbItem = LibraryManager.GetItemById(video.Id) as Video;
  285. if (dbItem != null)
  286. {
  287. video = dbItem;
  288. }
  289. return video;
  290. // Sort them so that the list can be easily compared for changes
  291. }).OrderBy(i => i.Path).ToList();
  292. }
  293. private async Task<bool> RefreshAlternateVersionsWithinSameDirectory(MetadataRefreshOptions options, IEnumerable<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
  294. {
  295. var newItems = HasLocalAlternateVersions ?
  296. LoadAlternateVersionsWithinSameDirectory(fileSystemChildren, options.DirectoryService).ToList() :
  297. new List<Video>();
  298. var newItemIds = newItems.Select(i => i.Id).ToList();
  299. var itemsChanged = !LocalAlternateVersionIds.SequenceEqual(newItemIds);
  300. var tasks = newItems.Select(i => RefreshAlternateVersion(options, i, cancellationToken));
  301. await Task.WhenAll(tasks).ConfigureAwait(false);
  302. LocalAlternateVersionIds = newItemIds;
  303. return itemsChanged;
  304. }
  305. private Task RefreshAlternateVersion(MetadataRefreshOptions options, Video video, CancellationToken cancellationToken)
  306. {
  307. var currentImagePath = video.GetImagePath(ImageType.Primary);
  308. var ownerImagePath = this.GetImagePath(ImageType.Primary);
  309. var newOptions = new MetadataRefreshOptions
  310. {
  311. DirectoryService = options.DirectoryService,
  312. ImageRefreshMode = options.ImageRefreshMode,
  313. MetadataRefreshMode = options.MetadataRefreshMode,
  314. ReplaceAllMetadata = options.ReplaceAllMetadata
  315. };
  316. if (!string.Equals(currentImagePath, ownerImagePath, StringComparison.OrdinalIgnoreCase))
  317. {
  318. newOptions.ForceSave = true;
  319. if (string.IsNullOrWhiteSpace(ownerImagePath))
  320. {
  321. video.ImageInfos.Clear();
  322. }
  323. else
  324. {
  325. video.SetImagePath(ImageType.Primary, ownerImagePath);
  326. }
  327. }
  328. return video.RefreshMetadata(newOptions, cancellationToken);
  329. }
  330. public override async Task UpdateToRepository(ItemUpdateType updateReason, CancellationToken cancellationToken)
  331. {
  332. await base.UpdateToRepository(updateReason, cancellationToken).ConfigureAwait(false);
  333. foreach (var item in LocalAlternateVersionIds.Select(i => LibraryManager.GetItemById(i)))
  334. {
  335. item.ImageInfos = ImageInfos;
  336. item.Overview = Overview;
  337. item.ProductionYear = ProductionYear;
  338. item.PremiereDate = PremiereDate;
  339. item.CommunityRating = CommunityRating;
  340. item.OfficialRating = OfficialRating;
  341. item.Genres = Genres;
  342. item.ProviderIds = ProviderIds;
  343. await item.UpdateToRepository(ItemUpdateType.MetadataDownload, cancellationToken).ConfigureAwait(false);
  344. }
  345. }
  346. /// <summary>
  347. /// Loads the additional parts.
  348. /// </summary>
  349. /// <returns>IEnumerable{Video}.</returns>
  350. private IEnumerable<Video> LoadAlternateVersionsWithinSameDirectory(IEnumerable<FileSystemInfo> fileSystemChildren, IDirectoryService directoryService)
  351. {
  352. IEnumerable<FileSystemInfo> files;
  353. // Only support this for video files. For folder rips, they'll have to use the linking feature
  354. if (VideoType == VideoType.VideoFile || VideoType == VideoType.Iso)
  355. {
  356. var path = Path;
  357. var filenamePrefix = System.IO.Path.GetFileName(System.IO.Path.GetDirectoryName(path));
  358. files = fileSystemChildren.Where(i =>
  359. {
  360. if ((i.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
  361. {
  362. return false;
  363. }
  364. return !string.Equals(i.FullName, path, StringComparison.OrdinalIgnoreCase) &&
  365. EntityResolutionHelper.IsVideoFile(i.FullName) &&
  366. i.Name.StartsWith(filenamePrefix + " - ", StringComparison.OrdinalIgnoreCase);
  367. });
  368. }
  369. else
  370. {
  371. files = new List<FileSystemInfo>();
  372. }
  373. return LibraryManager.ResolvePaths<Video>(files, directoryService, null).Select(video =>
  374. {
  375. // Try to retrieve it from the db. If we don't find it, use the resolved version
  376. var dbItem = LibraryManager.GetItemById(video.Id) as Video;
  377. if (dbItem != null)
  378. {
  379. video = dbItem;
  380. }
  381. video.PrimaryVersionId = Id;
  382. return video;
  383. // Sort them so that the list can be easily compared for changes
  384. }).OrderBy(i => i.Path).ToList();
  385. }
  386. public override IEnumerable<string> GetDeletePaths()
  387. {
  388. if (!IsInMixedFolder)
  389. {
  390. return new[] { ContainingFolderPath };
  391. }
  392. return base.GetDeletePaths();
  393. }
  394. public virtual IEnumerable<MediaStream> GetMediaStreams()
  395. {
  396. return ItemRepository.GetMediaStreams(new MediaStreamQuery
  397. {
  398. ItemId = Id
  399. });
  400. }
  401. public virtual MediaStream GetDefaultVideoStream()
  402. {
  403. if (!DefaultVideoStreamIndex.HasValue)
  404. {
  405. return null;
  406. }
  407. return ItemRepository.GetMediaStreams(new MediaStreamQuery
  408. {
  409. ItemId = Id,
  410. Index = DefaultVideoStreamIndex.Value
  411. }).FirstOrDefault();
  412. }
  413. }
  414. }