DefaultIntroProvider.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  1. using MediaBrowser.Common.Configuration;
  2. using MediaBrowser.Common.Security;
  3. using MediaBrowser.Controller.Channels;
  4. using MediaBrowser.Controller.Entities;
  5. using MediaBrowser.Controller.Entities.Movies;
  6. using MediaBrowser.Controller.Entities.TV;
  7. using MediaBrowser.Controller.Library;
  8. using MediaBrowser.Controller.Localization;
  9. using MediaBrowser.Model.Channels;
  10. using MediaBrowser.Model.Configuration;
  11. using MediaBrowser.Model.Entities;
  12. using System;
  13. using System.Collections.Generic;
  14. using System.IO;
  15. using System.Linq;
  16. using System.Threading;
  17. using System.Threading.Tasks;
  18. using CommonIO;
  19. using MediaBrowser.Common.IO;
  20. using MoreLinq;
  21. namespace MediaBrowser.Server.Implementations.Intros
  22. {
  23. public class DefaultIntroProvider : IIntroProvider
  24. {
  25. private readonly ISecurityManager _security;
  26. private readonly ILocalizationManager _localization;
  27. private readonly IConfigurationManager _serverConfig;
  28. private readonly ILibraryManager _libraryManager;
  29. private readonly IFileSystem _fileSystem;
  30. private readonly IMediaSourceManager _mediaSourceManager;
  31. public DefaultIntroProvider(ISecurityManager security, ILocalizationManager localization, IConfigurationManager serverConfig, ILibraryManager libraryManager, IFileSystem fileSystem, IMediaSourceManager mediaSourceManager)
  32. {
  33. _security = security;
  34. _localization = localization;
  35. _serverConfig = serverConfig;
  36. _libraryManager = libraryManager;
  37. _fileSystem = fileSystem;
  38. _mediaSourceManager = mediaSourceManager;
  39. }
  40. public async Task<IEnumerable<IntroInfo>> GetIntros(BaseItem item, User user)
  41. {
  42. var config = GetOptions();
  43. if (item is Movie)
  44. {
  45. if (!config.EnableIntrosForMovies)
  46. {
  47. return new List<IntroInfo>();
  48. }
  49. }
  50. else if (item is Episode)
  51. {
  52. if (!config.EnableIntrosForEpisodes)
  53. {
  54. return new List<IntroInfo>();
  55. }
  56. }
  57. else
  58. {
  59. return new List<IntroInfo>();
  60. }
  61. var ratingLevel = string.IsNullOrWhiteSpace(item.OfficialRating)
  62. ? null
  63. : _localization.GetRatingLevel(item.OfficialRating);
  64. var random = new Random(Environment.TickCount + Guid.NewGuid().GetHashCode());
  65. var candidates = new List<ItemWithTrailer>();
  66. var itemPeople = _libraryManager.GetPeople(item);
  67. var allPeople = _libraryManager.GetPeople(new InternalPeopleQuery
  68. {
  69. AppearsInItemId = item.Id
  70. });
  71. var trailerTypes = new List<TrailerType>();
  72. if (config.EnableIntrosFromMoviesInLibrary)
  73. {
  74. trailerTypes.Add(TrailerType.LocalTrailer);
  75. }
  76. if (IsSupporter)
  77. {
  78. if (config.EnableIntrosFromUpcomingTrailers)
  79. {
  80. trailerTypes.Add(TrailerType.ComingSoonToTheaters);
  81. }
  82. if (config.EnableIntrosFromUpcomingDvdMovies)
  83. {
  84. trailerTypes.Add(TrailerType.ComingSoonToDvd);
  85. }
  86. if (config.EnableIntrosFromUpcomingStreamingMovies)
  87. {
  88. trailerTypes.Add(TrailerType.ComingSoonToStreaming);
  89. }
  90. if (config.EnableIntrosFromSimilarMovies)
  91. {
  92. trailerTypes.Add(TrailerType.Archive);
  93. }
  94. }
  95. if (trailerTypes.Count > 0)
  96. {
  97. var trailerResult = _libraryManager.GetItemList(new InternalItemsQuery
  98. {
  99. IncludeItemTypes = new[] { typeof(Trailer).Name },
  100. TrailerTypes = trailerTypes.ToArray()
  101. });
  102. candidates.AddRange(trailerResult.Select(i => new ItemWithTrailer
  103. {
  104. Item = i,
  105. Type = i.SourceType == SourceType.Channel ? ItemWithTrailerType.ChannelTrailer : ItemWithTrailerType.ItemWithTrailer,
  106. User = user,
  107. WatchingItem = item,
  108. WatchingItemPeople = itemPeople,
  109. AllPeople = allPeople,
  110. Random = random,
  111. LibraryManager = _libraryManager
  112. }));
  113. }
  114. return GetResult(item, candidates, config, ratingLevel);
  115. }
  116. private IEnumerable<IntroInfo> GetResult(BaseItem item, IEnumerable<ItemWithTrailer> candidates, CinemaModeConfiguration config, int? ratingLevel)
  117. {
  118. var customIntros = !string.IsNullOrWhiteSpace(config.CustomIntroPath) ?
  119. GetCustomIntros(config) :
  120. new List<IntroInfo>();
  121. var mediaInfoIntros = !string.IsNullOrWhiteSpace(config.MediaInfoIntroPath) ?
  122. GetMediaInfoIntros(config, item) :
  123. new List<IntroInfo>();
  124. var trailerLimit = config.TrailerLimit;
  125. // Avoid implicitly captured closure
  126. return candidates.Where(i =>
  127. {
  128. if (config.EnableIntrosParentalControl && !FilterByParentalRating(ratingLevel, i.Item))
  129. {
  130. return false;
  131. }
  132. if (!config.EnableIntrosForWatchedContent && i.IsPlayed)
  133. {
  134. return false;
  135. }
  136. return !IsDuplicate(item, i.Item);
  137. })
  138. .OrderByDescending(i => i.Score)
  139. .ThenBy(i => Guid.NewGuid())
  140. .ThenByDescending(i => (i.IsPlayed ? 0 : 1))
  141. .Select(i => i.IntroInfo)
  142. .Take(trailerLimit)
  143. .Concat(customIntros.Take(1))
  144. .Concat(mediaInfoIntros);
  145. }
  146. private bool IsDuplicate(BaseItem playingContent, BaseItem test)
  147. {
  148. var id = playingContent.GetProviderId(MetadataProviders.Imdb);
  149. if (!string.IsNullOrWhiteSpace(id) && string.Equals(id, test.GetProviderId(MetadataProviders.Imdb), StringComparison.OrdinalIgnoreCase))
  150. {
  151. return true;
  152. }
  153. id = playingContent.GetProviderId(MetadataProviders.Tmdb);
  154. if (!string.IsNullOrWhiteSpace(id) && string.Equals(id, test.GetProviderId(MetadataProviders.Tmdb), StringComparison.OrdinalIgnoreCase))
  155. {
  156. return true;
  157. }
  158. return false;
  159. }
  160. private CinemaModeConfiguration GetOptions()
  161. {
  162. return _serverConfig.GetConfiguration<CinemaModeConfiguration>("cinemamode");
  163. }
  164. private List<IntroInfo> GetCustomIntros(CinemaModeConfiguration options)
  165. {
  166. try
  167. {
  168. return GetCustomIntroFiles(options, true, false)
  169. .OrderBy(i => Guid.NewGuid())
  170. .Select(i => new IntroInfo
  171. {
  172. Path = i
  173. }).ToList();
  174. }
  175. catch (IOException)
  176. {
  177. return new List<IntroInfo>();
  178. }
  179. }
  180. private IEnumerable<IntroInfo> GetMediaInfoIntros(CinemaModeConfiguration options, BaseItem item)
  181. {
  182. try
  183. {
  184. var hasMediaSources = item as IHasMediaSources;
  185. if (hasMediaSources == null)
  186. {
  187. return new List<IntroInfo>();
  188. }
  189. var mediaSource = _mediaSourceManager.GetStaticMediaSources(hasMediaSources, false)
  190. .FirstOrDefault();
  191. if (mediaSource == null)
  192. {
  193. return new List<IntroInfo>();
  194. }
  195. var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
  196. var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
  197. var allIntros = GetCustomIntroFiles(options, false, true)
  198. .OrderBy(i => Guid.NewGuid())
  199. .Select(i => new IntroInfo
  200. {
  201. Path = i
  202. }).ToList();
  203. var returnResult = new List<IntroInfo>();
  204. if (videoStream != null)
  205. {
  206. returnResult.AddRange(GetMediaInfoIntrosByVideoStream(allIntros, videoStream).Take(1));
  207. }
  208. if (audioStream != null)
  209. {
  210. returnResult.AddRange(GetMediaInfoIntrosByAudioStream(allIntros, audioStream).Take(1));
  211. }
  212. returnResult.AddRange(GetMediaInfoIntrosByTags(allIntros, item.Tags).Take(1));
  213. return returnResult.DistinctBy(i => i.Path, StringComparer.OrdinalIgnoreCase);
  214. }
  215. catch (IOException)
  216. {
  217. return new List<IntroInfo>();
  218. }
  219. }
  220. private IEnumerable<IntroInfo> GetMediaInfoIntrosByVideoStream(List<IntroInfo> allIntros, MediaStream stream)
  221. {
  222. var codec = stream.Codec;
  223. if (string.IsNullOrWhiteSpace(codec))
  224. {
  225. return new List<IntroInfo>();
  226. }
  227. return allIntros
  228. .Where(i => IsMatch(i.Path, codec));
  229. }
  230. private IEnumerable<IntroInfo> GetMediaInfoIntrosByAudioStream(List<IntroInfo> allIntros, MediaStream stream)
  231. {
  232. var codec = stream.Codec;
  233. if (string.IsNullOrWhiteSpace(codec))
  234. {
  235. return new List<IntroInfo>();
  236. }
  237. return allIntros
  238. .Where(i => IsAudioMatch(i.Path, stream));
  239. }
  240. private IEnumerable<IntroInfo> GetMediaInfoIntrosByTags(List<IntroInfo> allIntros, List<string> tags)
  241. {
  242. return allIntros
  243. .Where(i => tags.Any(t => IsMatch(i.Path, t)));
  244. }
  245. private bool IsMatch(string file, string attribute)
  246. {
  247. var filename = Path.GetFileNameWithoutExtension(file) ?? string.Empty;
  248. filename = Normalize(filename);
  249. if (string.IsNullOrWhiteSpace(filename))
  250. {
  251. return false;
  252. }
  253. attribute = Normalize(attribute);
  254. if (string.IsNullOrWhiteSpace(attribute))
  255. {
  256. return false;
  257. }
  258. return string.Equals(filename, attribute, StringComparison.OrdinalIgnoreCase);
  259. }
  260. private string Normalize(string value)
  261. {
  262. return value;
  263. }
  264. private bool IsAudioMatch(string path, MediaStream stream)
  265. {
  266. if (!string.IsNullOrWhiteSpace(stream.Codec))
  267. {
  268. if (IsMatch(path, stream.Codec))
  269. {
  270. return true;
  271. }
  272. }
  273. if (!string.IsNullOrWhiteSpace(stream.Profile))
  274. {
  275. if (IsMatch(path, stream.Profile))
  276. {
  277. return true;
  278. }
  279. }
  280. return false;
  281. }
  282. private IEnumerable<string> GetCustomIntroFiles(CinemaModeConfiguration options, bool enableCustomIntros, bool enableMediaInfoIntros)
  283. {
  284. var list = new List<string>();
  285. if (enableCustomIntros && !string.IsNullOrWhiteSpace(options.CustomIntroPath))
  286. {
  287. list.AddRange(_fileSystem.GetFilePaths(options.CustomIntroPath, true)
  288. .Where(_libraryManager.IsVideoFile));
  289. }
  290. if (enableMediaInfoIntros && !string.IsNullOrWhiteSpace(options.MediaInfoIntroPath))
  291. {
  292. list.AddRange(_fileSystem.GetFilePaths(options.MediaInfoIntroPath, true)
  293. .Where(_libraryManager.IsVideoFile));
  294. }
  295. return list.Distinct(StringComparer.OrdinalIgnoreCase);
  296. }
  297. private bool FilterByParentalRating(int? ratingLevel, BaseItem item)
  298. {
  299. // Only content rated same or lower
  300. if (ratingLevel.HasValue)
  301. {
  302. var level = string.IsNullOrWhiteSpace(item.OfficialRating)
  303. ? (int?)null
  304. : _localization.GetRatingLevel(item.OfficialRating);
  305. return level.HasValue && level.Value <= ratingLevel.Value;
  306. }
  307. return true;
  308. }
  309. internal static int GetSimiliarityScore(BaseItem item1, List<PersonInfo> item1People, List<PersonInfo> allPeople, BaseItem item2, Random random, ILibraryManager libraryManager)
  310. {
  311. var points = 0;
  312. if (!string.IsNullOrEmpty(item1.OfficialRating) && string.Equals(item1.OfficialRating, item2.OfficialRating, StringComparison.OrdinalIgnoreCase))
  313. {
  314. points += 10;
  315. }
  316. // Find common genres
  317. points += item1.Genres.Where(i => item2.Genres.Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 10);
  318. // Find common tags
  319. points += GetTags(item1).Where(i => GetTags(item2).Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 10);
  320. // Find common keywords
  321. points += GetKeywords(item1).Where(i => GetKeywords(item2).Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 10);
  322. // Find common studios
  323. points += item1.Studios.Where(i => item2.Studios.Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 5);
  324. var item2PeopleNames = allPeople.Where(i => i.ItemId == item2.Id)
  325. .Select(i => i.Name)
  326. .Where(i => !string.IsNullOrWhiteSpace(i))
  327. .DistinctNames()
  328. .ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
  329. points += item1People.Where(i => item2PeopleNames.ContainsKey(i.Name)).Sum(i =>
  330. {
  331. if (string.Equals(i.Type, PersonType.Director, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Director, StringComparison.OrdinalIgnoreCase))
  332. {
  333. return 5;
  334. }
  335. if (string.Equals(i.Type, PersonType.Actor, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Actor, StringComparison.OrdinalIgnoreCase))
  336. {
  337. return 3;
  338. }
  339. if (string.Equals(i.Type, PersonType.Composer, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Composer, StringComparison.OrdinalIgnoreCase))
  340. {
  341. return 3;
  342. }
  343. if (string.Equals(i.Type, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase))
  344. {
  345. return 3;
  346. }
  347. if (string.Equals(i.Type, PersonType.Writer, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Writer, StringComparison.OrdinalIgnoreCase))
  348. {
  349. return 2;
  350. }
  351. return 1;
  352. });
  353. // Add some randomization so that you're not always seeing the same ones for a given movie
  354. points += random.Next(0, 50);
  355. return points;
  356. }
  357. private static IEnumerable<string> GetTags(BaseItem item)
  358. {
  359. var hasTags = item as IHasTags;
  360. if (hasTags != null)
  361. {
  362. return hasTags.Tags;
  363. }
  364. return new List<string>();
  365. }
  366. private static IEnumerable<string> GetKeywords(BaseItem item)
  367. {
  368. var hasTags = item as IHasKeywords;
  369. if (hasTags != null)
  370. {
  371. return hasTags.Keywords;
  372. }
  373. return new List<string>();
  374. }
  375. public IEnumerable<string> GetAllIntroFiles()
  376. {
  377. return GetCustomIntroFiles(GetOptions(), true, true);
  378. }
  379. private bool IsSupporter
  380. {
  381. get { return _security.IsMBSupporter; }
  382. }
  383. public string Name
  384. {
  385. get { return "Default"; }
  386. }
  387. internal class ItemWithTrailer
  388. {
  389. internal BaseItem Item;
  390. internal ItemWithTrailerType Type;
  391. internal User User;
  392. internal BaseItem WatchingItem;
  393. internal List<PersonInfo> WatchingItemPeople;
  394. internal List<PersonInfo> AllPeople;
  395. internal Random Random;
  396. internal ILibraryManager LibraryManager;
  397. private bool? _isPlayed;
  398. public bool IsPlayed
  399. {
  400. get
  401. {
  402. if (!_isPlayed.HasValue)
  403. {
  404. _isPlayed = Item.IsPlayed(User);
  405. }
  406. return _isPlayed.Value;
  407. }
  408. }
  409. private int? _score;
  410. public int Score
  411. {
  412. get
  413. {
  414. if (!_score.HasValue)
  415. {
  416. _score = GetSimiliarityScore(WatchingItem, WatchingItemPeople, AllPeople, Item, Random, LibraryManager);
  417. }
  418. return _score.Value;
  419. }
  420. }
  421. public IntroInfo IntroInfo
  422. {
  423. get
  424. {
  425. var id = Item.Id;
  426. if (Type == ItemWithTrailerType.ItemWithTrailer)
  427. {
  428. var hasTrailers = Item as IHasTrailers;
  429. if (hasTrailers != null)
  430. {
  431. id = hasTrailers.LocalTrailerIds.FirstOrDefault();
  432. }
  433. }
  434. return new IntroInfo
  435. {
  436. ItemId = id
  437. };
  438. }
  439. }
  440. }
  441. internal enum ItemWithTrailerType
  442. {
  443. ChannelTrailer,
  444. ItemWithTrailer
  445. }
  446. }
  447. public class CinemaModeConfigurationFactory : IConfigurationFactory
  448. {
  449. public IEnumerable<ConfigurationStore> GetConfigurations()
  450. {
  451. return new[]
  452. {
  453. new ConfigurationStore
  454. {
  455. ConfigurationType = typeof(CinemaModeConfiguration),
  456. Key = "cinemamode"
  457. }
  458. };
  459. }
  460. }
  461. }