Video.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.Linq;
  5. using System.Text.Json.Serialization;
  6. using System.Threading;
  7. using System.Threading.Tasks;
  8. using MediaBrowser.Controller.Library;
  9. using MediaBrowser.Controller.LiveTv;
  10. using MediaBrowser.Controller.MediaEncoding;
  11. using MediaBrowser.Controller.Persistence;
  12. using MediaBrowser.Controller.Providers;
  13. using MediaBrowser.Model.Dto;
  14. using MediaBrowser.Model.Entities;
  15. using MediaBrowser.Model.IO;
  16. using MediaBrowser.Model.MediaInfo;
  17. namespace MediaBrowser.Controller.Entities
  18. {
  19. /// <summary>
  20. /// Class Video
  21. /// </summary>
  22. public class Video : BaseItem,
  23. IHasAspectRatio,
  24. ISupportsPlaceHolders,
  25. IHasMediaSources
  26. {
  27. [JsonIgnore]
  28. public string PrimaryVersionId { get; set; }
  29. public string[] AdditionalParts { get; set; }
  30. public string[] LocalAlternateVersions { get; set; }
  31. public LinkedChild[] LinkedAlternateVersions { get; set; }
  32. [JsonIgnore]
  33. public override bool SupportsPlayedStatus => true;
  34. [JsonIgnore]
  35. public override bool SupportsPeople => true;
  36. [JsonIgnore]
  37. public override bool SupportsInheritedParentImages => true;
  38. [JsonIgnore]
  39. public override bool SupportsPositionTicksResume
  40. {
  41. get
  42. {
  43. var extraType = ExtraType;
  44. if (extraType.HasValue)
  45. {
  46. if (extraType.Value == Model.Entities.ExtraType.Sample)
  47. {
  48. return false;
  49. }
  50. if (extraType.Value == Model.Entities.ExtraType.ThemeVideo)
  51. {
  52. return false;
  53. }
  54. if (extraType.Value == Model.Entities.ExtraType.Trailer)
  55. {
  56. return false;
  57. }
  58. }
  59. return true;
  60. }
  61. }
  62. public void SetPrimaryVersionId(string id)
  63. {
  64. if (string.IsNullOrEmpty(id))
  65. {
  66. PrimaryVersionId = null;
  67. }
  68. else
  69. {
  70. PrimaryVersionId = id;
  71. }
  72. PresentationUniqueKey = CreatePresentationUniqueKey();
  73. }
  74. public override string CreatePresentationUniqueKey()
  75. {
  76. if (!string.IsNullOrEmpty(PrimaryVersionId))
  77. {
  78. return PrimaryVersionId;
  79. }
  80. return base.CreatePresentationUniqueKey();
  81. }
  82. [JsonIgnore]
  83. public override bool SupportsThemeMedia => true;
  84. /// <summary>
  85. /// Gets or sets the timestamp.
  86. /// </summary>
  87. /// <value>The timestamp.</value>
  88. public TransportStreamTimestamp? Timestamp { get; set; }
  89. /// <summary>
  90. /// Gets or sets the subtitle paths.
  91. /// </summary>
  92. /// <value>The subtitle paths.</value>
  93. public string[] SubtitleFiles { get; set; }
  94. /// <summary>
  95. /// Gets or sets a value indicating whether this instance has subtitles.
  96. /// </summary>
  97. /// <value><c>true</c> if this instance has subtitles; otherwise, <c>false</c>.</value>
  98. public bool HasSubtitles { get; set; }
  99. public bool IsPlaceHolder { get; set; }
  100. /// <summary>
  101. /// Gets or sets the default index of the video stream.
  102. /// </summary>
  103. /// <value>The default index of the video stream.</value>
  104. public int? DefaultVideoStreamIndex { get; set; }
  105. /// <summary>
  106. /// Gets or sets the type of the video.
  107. /// </summary>
  108. /// <value>The type of the video.</value>
  109. public VideoType VideoType { get; set; }
  110. /// <summary>
  111. /// Gets or sets the type of the iso.
  112. /// </summary>
  113. /// <value>The type of the iso.</value>
  114. public IsoType? IsoType { get; set; }
  115. /// <summary>
  116. /// Gets or sets the video3 D format.
  117. /// </summary>
  118. /// <value>The video3 D format.</value>
  119. public Video3DFormat? Video3DFormat { get; set; }
  120. public string[] GetPlayableStreamFileNames()
  121. {
  122. var videoType = VideoType;
  123. if (videoType == VideoType.Iso && IsoType == Model.Entities.IsoType.BluRay)
  124. {
  125. videoType = VideoType.BluRay;
  126. }
  127. else if (videoType == VideoType.Iso && IsoType == Model.Entities.IsoType.Dvd)
  128. {
  129. videoType = VideoType.Dvd;
  130. }
  131. else
  132. {
  133. return Array.Empty<string>();
  134. }
  135. throw new NotImplementedException();
  136. }
  137. /// <summary>
  138. /// Gets or sets the aspect ratio.
  139. /// </summary>
  140. /// <value>The aspect ratio.</value>
  141. public string AspectRatio { get; set; }
  142. public Video()
  143. {
  144. AdditionalParts = Array.Empty<string>();
  145. LocalAlternateVersions = Array.Empty<string>();
  146. SubtitleFiles = Array.Empty<string>();
  147. LinkedAlternateVersions = Array.Empty<LinkedChild>();
  148. }
  149. public override bool CanDownload()
  150. {
  151. if (VideoType == VideoType.Dvd || VideoType == VideoType.BluRay)
  152. {
  153. return false;
  154. }
  155. return IsFileProtocol;
  156. }
  157. [JsonIgnore]
  158. public override bool SupportsAddingToPlaylist => true;
  159. [JsonIgnore]
  160. public int MediaSourceCount
  161. {
  162. get
  163. {
  164. if (!string.IsNullOrEmpty(PrimaryVersionId))
  165. {
  166. var item = LibraryManager.GetItemById(PrimaryVersionId);
  167. if (item is Video video)
  168. {
  169. return video.MediaSourceCount;
  170. }
  171. }
  172. return LinkedAlternateVersions.Length + LocalAlternateVersions.Length + 1;
  173. }
  174. }
  175. [JsonIgnore]
  176. public bool IsStacked => AdditionalParts.Length > 0;
  177. [JsonIgnore]
  178. public override bool HasLocalAlternateVersions => LocalAlternateVersions.Length > 0;
  179. public IEnumerable<Guid> GetAdditionalPartIds()
  180. {
  181. return AdditionalParts.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
  182. }
  183. public IEnumerable<Guid> GetLocalAlternateVersionIds()
  184. {
  185. return LocalAlternateVersions.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
  186. }
  187. public static ILiveTvManager LiveTvManager { get; set; }
  188. [JsonIgnore]
  189. public override SourceType SourceType
  190. {
  191. get
  192. {
  193. if (IsActiveRecording())
  194. {
  195. return SourceType.LiveTV;
  196. }
  197. return base.SourceType;
  198. }
  199. }
  200. protected override bool IsActiveRecording()
  201. {
  202. return LiveTvManager.GetActiveRecordingInfo(Path) != null;
  203. }
  204. public override bool CanDelete()
  205. {
  206. if (IsActiveRecording())
  207. {
  208. return false;
  209. }
  210. return base.CanDelete();
  211. }
  212. [JsonIgnore]
  213. public bool IsCompleteMedia
  214. {
  215. get
  216. {
  217. if (SourceType == SourceType.Channel)
  218. {
  219. return !Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase);
  220. }
  221. return !IsActiveRecording();
  222. }
  223. }
  224. [JsonIgnore]
  225. protected virtual bool EnableDefaultVideoUserDataKeys => true;
  226. public override List<string> GetUserDataKeys()
  227. {
  228. var list = base.GetUserDataKeys();
  229. if (EnableDefaultVideoUserDataKeys)
  230. {
  231. if (ExtraType.HasValue)
  232. {
  233. var key = this.GetProviderId(MetadataProviders.Tmdb);
  234. if (!string.IsNullOrEmpty(key))
  235. {
  236. list.Insert(0, GetUserDataKey(key));
  237. }
  238. key = this.GetProviderId(MetadataProviders.Imdb);
  239. if (!string.IsNullOrEmpty(key))
  240. {
  241. list.Insert(0, GetUserDataKey(key));
  242. }
  243. }
  244. else
  245. {
  246. var key = this.GetProviderId(MetadataProviders.Imdb);
  247. if (!string.IsNullOrEmpty(key))
  248. {
  249. list.Insert(0, key);
  250. }
  251. key = this.GetProviderId(MetadataProviders.Tmdb);
  252. if (!string.IsNullOrEmpty(key))
  253. {
  254. list.Insert(0, key);
  255. }
  256. }
  257. }
  258. return list;
  259. }
  260. private string GetUserDataKey(string providerId)
  261. {
  262. var key = providerId + "-" + ExtraType.ToString().ToLowerInvariant();
  263. // Make sure different trailers have their own data.
  264. if (RunTimeTicks.HasValue)
  265. {
  266. key += "-" + RunTimeTicks.Value.ToString(CultureInfo.InvariantCulture);
  267. }
  268. return key;
  269. }
  270. public IEnumerable<Video> GetLinkedAlternateVersions()
  271. {
  272. return LinkedAlternateVersions
  273. .Select(GetLinkedChild)
  274. .Where(i => i != null)
  275. .OfType<Video>()
  276. .OrderBy(i => i.SortName);
  277. }
  278. /// <summary>
  279. /// Gets the additional parts.
  280. /// </summary>
  281. /// <returns>IEnumerable{Video}.</returns>
  282. public IOrderedEnumerable<Video> GetAdditionalParts()
  283. {
  284. return GetAdditionalPartIds()
  285. .Select(i => LibraryManager.GetItemById(i))
  286. .Where(i => i != null)
  287. .OfType<Video>()
  288. .OrderBy(i => i.SortName);
  289. }
  290. [JsonIgnore]
  291. public override string ContainingFolderPath
  292. {
  293. get
  294. {
  295. if (IsStacked)
  296. {
  297. return System.IO.Path.GetDirectoryName(Path);
  298. }
  299. if (!IsPlaceHolder)
  300. {
  301. if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd)
  302. {
  303. return Path;
  304. }
  305. }
  306. return base.ContainingFolderPath;
  307. }
  308. }
  309. [JsonIgnore]
  310. public override string FileNameWithoutExtension
  311. {
  312. get
  313. {
  314. if (IsFileProtocol)
  315. {
  316. if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd)
  317. {
  318. return System.IO.Path.GetFileName(Path);
  319. }
  320. return System.IO.Path.GetFileNameWithoutExtension(Path);
  321. }
  322. return null;
  323. }
  324. }
  325. internal override ItemUpdateType UpdateFromResolvedItem(BaseItem newItem)
  326. {
  327. var updateType = base.UpdateFromResolvedItem(newItem);
  328. if (newItem is Video newVideo)
  329. {
  330. if (!AdditionalParts.SequenceEqual(newVideo.AdditionalParts, StringComparer.Ordinal))
  331. {
  332. AdditionalParts = newVideo.AdditionalParts;
  333. updateType |= ItemUpdateType.MetadataImport;
  334. }
  335. if (!LocalAlternateVersions.SequenceEqual(newVideo.LocalAlternateVersions, StringComparer.Ordinal))
  336. {
  337. LocalAlternateVersions = newVideo.LocalAlternateVersions;
  338. updateType |= ItemUpdateType.MetadataImport;
  339. }
  340. if (VideoType != newVideo.VideoType)
  341. {
  342. VideoType = newVideo.VideoType;
  343. updateType |= ItemUpdateType.MetadataImport;
  344. }
  345. }
  346. return updateType;
  347. }
  348. public static string[] QueryPlayableStreamFiles(string rootPath, VideoType videoType)
  349. {
  350. if (videoType == VideoType.Dvd)
  351. {
  352. return FileSystem.GetFiles(rootPath, new[] { ".vob" }, false, true)
  353. .OrderByDescending(i => i.Length)
  354. .ThenBy(i => i.FullName)
  355. .Take(1)
  356. .Select(i => i.FullName)
  357. .ToArray();
  358. }
  359. if (videoType == VideoType.BluRay)
  360. {
  361. return FileSystem.GetFiles(rootPath, new[] { ".m2ts" }, false, true)
  362. .OrderByDescending(i => i.Length)
  363. .ThenBy(i => i.FullName)
  364. .Take(1)
  365. .Select(i => i.FullName)
  366. .ToArray();
  367. }
  368. return Array.Empty<string>();
  369. }
  370. /// <summary>
  371. /// Gets a value indicating whether [is3 D].
  372. /// </summary>
  373. /// <value><c>true</c> if [is3 D]; otherwise, <c>false</c>.</value>
  374. [JsonIgnore]
  375. public bool Is3D => Video3DFormat.HasValue;
  376. /// <summary>
  377. /// Gets the type of the media.
  378. /// </summary>
  379. /// <value>The type of the media.</value>
  380. [JsonIgnore]
  381. public override string MediaType => Model.Entities.MediaType.Video;
  382. protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
  383. {
  384. var hasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
  385. if (IsStacked)
  386. {
  387. var tasks = AdditionalParts
  388. .Select(i => RefreshMetadataForOwnedVideo(options, true, i, cancellationToken));
  389. await Task.WhenAll(tasks).ConfigureAwait(false);
  390. }
  391. // Must have a parent to have additional parts or alternate versions
  392. // In other words, it must be part of the Parent/Child tree
  393. // The additional parts won't have additional parts themselves
  394. if (IsFileProtocol && SupportsOwnedItems)
  395. {
  396. if (!IsStacked)
  397. {
  398. RefreshLinkedAlternateVersions();
  399. var tasks = LocalAlternateVersions
  400. .Select(i => RefreshMetadataForOwnedVideo(options, false, i, cancellationToken));
  401. await Task.WhenAll(tasks).ConfigureAwait(false);
  402. }
  403. }
  404. return hasChanges;
  405. }
  406. private void RefreshLinkedAlternateVersions()
  407. {
  408. foreach (var child in LinkedAlternateVersions)
  409. {
  410. // Reset the cached value
  411. if (child.ItemId.HasValue && child.ItemId.Value.Equals(Guid.Empty))
  412. {
  413. child.ItemId = null;
  414. }
  415. }
  416. }
  417. public override void UpdateToRepository(ItemUpdateType updateReason, CancellationToken cancellationToken)
  418. {
  419. base.UpdateToRepository(updateReason, cancellationToken);
  420. var localAlternates = GetLocalAlternateVersionIds()
  421. .Select(i => LibraryManager.GetItemById(i))
  422. .Where(i => i != null);
  423. foreach (var item in localAlternates)
  424. {
  425. item.ImageInfos = ImageInfos;
  426. item.Overview = Overview;
  427. item.ProductionYear = ProductionYear;
  428. item.PremiereDate = PremiereDate;
  429. item.CommunityRating = CommunityRating;
  430. item.OfficialRating = OfficialRating;
  431. item.Genres = Genres;
  432. item.ProviderIds = ProviderIds;
  433. item.UpdateToRepository(ItemUpdateType.MetadataDownload, cancellationToken);
  434. }
  435. }
  436. public override IEnumerable<FileSystemMetadata> GetDeletePaths()
  437. {
  438. if (!IsInMixedFolder)
  439. {
  440. return new[] {
  441. new FileSystemMetadata
  442. {
  443. FullName = ContainingFolderPath,
  444. IsDirectory = true
  445. }
  446. };
  447. }
  448. return base.GetDeletePaths();
  449. }
  450. public virtual MediaStream GetDefaultVideoStream()
  451. {
  452. if (!DefaultVideoStreamIndex.HasValue)
  453. {
  454. return null;
  455. }
  456. return MediaSourceManager.GetMediaStreams(new MediaStreamQuery
  457. {
  458. ItemId = Id,
  459. Index = DefaultVideoStreamIndex.Value
  460. }).FirstOrDefault();
  461. }
  462. protected override List<Tuple<BaseItem, MediaSourceType>> GetAllItemsForMediaSources()
  463. {
  464. var list = new List<Tuple<BaseItem, MediaSourceType>>();
  465. list.Add(new Tuple<BaseItem, MediaSourceType>(this, MediaSourceType.Default));
  466. list.AddRange(GetLinkedAlternateVersions().Select(i => new Tuple<BaseItem, MediaSourceType>(i, MediaSourceType.Grouping)));
  467. if (!string.IsNullOrEmpty(PrimaryVersionId))
  468. {
  469. var primary = LibraryManager.GetItemById(PrimaryVersionId) as Video;
  470. if (primary != null)
  471. {
  472. var existingIds = list.Select(i => i.Item1.Id).ToList();
  473. list.Add(new Tuple<BaseItem, MediaSourceType>(primary, MediaSourceType.Grouping));
  474. list.AddRange(primary.GetLinkedAlternateVersions().Where(i => !existingIds.Contains(i.Id)).Select(i => new Tuple<BaseItem, MediaSourceType>(i, MediaSourceType.Grouping)));
  475. }
  476. }
  477. var localAlternates = list
  478. .SelectMany(i =>
  479. {
  480. var video = i.Item1 as Video;
  481. return video == null ? new List<Guid>() : video.GetLocalAlternateVersionIds();
  482. })
  483. .Select(LibraryManager.GetItemById)
  484. .Where(i => i != null)
  485. .ToList();
  486. list.AddRange(localAlternates.Select(i => new Tuple<BaseItem, MediaSourceType>(i, MediaSourceType.Default)));
  487. return list;
  488. }
  489. }
  490. }