DefaultThemeService.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596
  1. using MediaBrowser.Controller.Drawing;
  2. using MediaBrowser.Controller.Dto;
  3. using MediaBrowser.Controller.Entities;
  4. using MediaBrowser.Controller.Entities.Audio;
  5. using MediaBrowser.Controller.Entities.Movies;
  6. using MediaBrowser.Controller.Entities.TV;
  7. using MediaBrowser.Controller.Library;
  8. using MediaBrowser.Model.Entities;
  9. using MediaBrowser.Model.Logging;
  10. using MediaBrowser.Model.Querying;
  11. using ServiceStack.ServiceHost;
  12. using System;
  13. using System.Collections.Generic;
  14. using System.Linq;
  15. namespace MediaBrowser.Api.DefaultTheme
  16. {
  17. [Route("/MBT/DefaultTheme/Games", "GET")]
  18. public class GetGamesView : IReturn<GamesView>
  19. {
  20. [ApiMember(Name = "UserId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
  21. public Guid UserId { get; set; }
  22. }
  23. [Route("/MBT/DefaultTheme/TV", "GET")]
  24. public class GetTvView : IReturn<TvView>
  25. {
  26. [ApiMember(Name = "UserId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
  27. public Guid UserId { get; set; }
  28. [ApiMember(Name = "ComedyGenre", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
  29. public string ComedyGenre { get; set; }
  30. [ApiMember(Name = "RomanceGenre", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
  31. public string RomanceGenre { get; set; }
  32. }
  33. [Route("/MBT/DefaultTheme/Movies", "GET")]
  34. public class GetMovieView : IReturn<MoviesView>
  35. {
  36. [ApiMember(Name = "UserId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
  37. public Guid UserId { get; set; }
  38. [ApiMember(Name = "FamilyGenre", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
  39. public string FamilyGenre { get; set; }
  40. [ApiMember(Name = "ComedyGenre", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
  41. public string ComedyGenre { get; set; }
  42. [ApiMember(Name = "RomanceGenre", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
  43. public string RomanceGenre { get; set; }
  44. }
  45. [Route("/MBT/DefaultTheme/Favorites", "GET")]
  46. public class GetFavoritesView : IReturn<FavoritesView>
  47. {
  48. [ApiMember(Name = "UserId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
  49. public Guid UserId { get; set; }
  50. }
  51. public class DefaultThemeService : BaseApiService
  52. {
  53. private readonly IUserManager _userManager;
  54. private readonly IDtoService _dtoService;
  55. private readonly ILogger _logger;
  56. private readonly ILibraryManager _libraryManager;
  57. private readonly IUserDataManager _userDataManager;
  58. private readonly IImageProcessor _imageProcessor;
  59. public DefaultThemeService(IUserManager userManager, IDtoService dtoService, ILogger logger, ILibraryManager libraryManager, IImageProcessor imageProcessor, IUserDataManager userDataManager)
  60. {
  61. _userManager = userManager;
  62. _dtoService = dtoService;
  63. _logger = logger;
  64. _libraryManager = libraryManager;
  65. _imageProcessor = imageProcessor;
  66. _userDataManager = userDataManager;
  67. }
  68. public object Get(GetFavoritesView request)
  69. {
  70. var user = _userManager.GetUserById(request.UserId);
  71. var allItems = user.RootFolder.GetRecursiveChildren(user, i => _userDataManager.GetUserData(user.Id, i.GetUserDataKey()).IsFavorite)
  72. .ToList();
  73. var itemsWithImages = allItems.Where(i => !string.IsNullOrEmpty(i.PrimaryImagePath))
  74. .ToList();
  75. var itemsWithBackdrops = allItems.Where(i => i.BackdropImagePaths.Count > 0)
  76. .ToList();
  77. var view = new FavoritesView();
  78. var fields = new List<ItemFields>();
  79. view.BackdropItems = FilterItemsForBackdropDisplay(itemsWithBackdrops.OrderBy(i => Guid.NewGuid()))
  80. .Take(10)
  81. .Select(i => _dtoService.GetBaseItemDto(i, fields, user))
  82. .ToList();
  83. var spotlightItems = itemsWithBackdrops.OrderBy(i => Guid.NewGuid())
  84. .Take(10)
  85. .ToList();
  86. view.SpotlightItems = spotlightItems
  87. .Select(i => _dtoService.GetBaseItemDto(i, fields, user))
  88. .ToList();
  89. view.Albums = itemsWithImages
  90. .OfType<MusicAlbum>()
  91. .OrderBy(i => Guid.NewGuid())
  92. .Take(4)
  93. .Select(i => _dtoService.GetBaseItemDto(i, fields, user))
  94. .ToList();
  95. view.Books = itemsWithImages
  96. .OfType<Book>()
  97. .OrderBy(i => Guid.NewGuid())
  98. .Take(6)
  99. .Select(i => _dtoService.GetBaseItemDto(i, fields, user))
  100. .ToList();
  101. view.Episodes = itemsWithImages
  102. .OfType<Episode>()
  103. .OrderBy(i => Guid.NewGuid())
  104. .Take(6)
  105. .Select(i => _dtoService.GetBaseItemDto(i, fields, user))
  106. .ToList();
  107. view.Games = itemsWithImages
  108. .OfType<Game>()
  109. .OrderBy(i => Guid.NewGuid())
  110. .Take(6)
  111. .Select(i => _dtoService.GetBaseItemDto(i, fields, user))
  112. .ToList();
  113. view.Movies = itemsWithImages
  114. .OfType<Movie>()
  115. .OrderBy(i => Guid.NewGuid())
  116. .Take(6)
  117. .Select(i => _dtoService.GetBaseItemDto(i, fields, user))
  118. .ToList();
  119. view.Series = itemsWithImages
  120. .OfType<Series>()
  121. .OrderBy(i => Guid.NewGuid())
  122. .Take(6)
  123. .Select(i => _dtoService.GetBaseItemDto(i, fields, user))
  124. .ToList();
  125. view.Songs = itemsWithImages
  126. .OfType<Audio>()
  127. .OrderBy(i => Guid.NewGuid())
  128. .Take(4)
  129. .Select(i => _dtoService.GetBaseItemDto(i, fields, user))
  130. .ToList();
  131. view.Artists = itemsWithImages
  132. .OfType<MusicArtist>()
  133. .OrderBy(i => Guid.NewGuid())
  134. .Take(4)
  135. .Select(i => _dtoService.GetBaseItemDto(i, fields, user))
  136. .ToList();
  137. view.MiniSpotlights = itemsWithBackdrops
  138. .Except(spotlightItems)
  139. .OrderBy(i => Guid.NewGuid())
  140. .Take(5)
  141. .Select(i => _dtoService.GetBaseItemDto(i, fields, user))
  142. .ToList();
  143. return ToOptimizedResult(view);
  144. }
  145. public object Get(GetGamesView request)
  146. {
  147. var user = _userManager.GetUserById(request.UserId);
  148. var items = user.RootFolder.GetRecursiveChildren(user, i => i is Game || i is GameSystem)
  149. .ToList();
  150. var gamesWithImages = items.OfType<Game>().Where(i => !string.IsNullOrEmpty(i.PrimaryImagePath)).ToList();
  151. var itemsWithBackdrops = FilterItemsForBackdropDisplay(items.Where(i => i.BackdropImagePaths.Count > 0)).ToList();
  152. var gamesWithBackdrops = itemsWithBackdrops.OfType<Game>().ToList();
  153. var view = new GamesView();
  154. var fields = new List<ItemFields>();
  155. view.BackdropItems = gamesWithBackdrops
  156. .OrderBy(i => Guid.NewGuid())
  157. .Take(10)
  158. .Select(i => _dtoService.GetBaseItemDto(i, fields, user))
  159. .ToList();
  160. view.SpotlightItems = gamesWithBackdrops
  161. .OrderBy(i => Guid.NewGuid())
  162. .Take(10)
  163. .Select(i => _dtoService.GetBaseItemDto(i, fields, user))
  164. .ToList();
  165. view.MultiPlayerItems = gamesWithImages
  166. .Where(i => i.PlayersSupported.HasValue && i.PlayersSupported.Value > 1)
  167. .OrderBy(i => Guid.NewGuid())
  168. .Select(i => GetItemStub(i, ImageType.Primary))
  169. .Where(i => i != null)
  170. .Take(1)
  171. .ToList();
  172. view.MiniSpotlights = gamesWithBackdrops
  173. .OrderBy(i => Guid.NewGuid())
  174. .Take(5)
  175. .Select(i => _dtoService.GetBaseItemDto(i, fields, user))
  176. .ToList();
  177. return ToOptimizedResult(view);
  178. }
  179. public object Get(GetTvView request)
  180. {
  181. var user = _userManager.GetUserById(request.UserId);
  182. var series = user.RootFolder.GetRecursiveChildren(user)
  183. .OfType<Series>()
  184. .ToList();
  185. var seriesWithBackdrops = series.Where(i => i.BackdropImagePaths.Count > 0).ToList();
  186. var view = new TvView();
  187. var fields = new List<ItemFields>();
  188. var seriesWithBestBackdrops = FilterItemsForBackdropDisplay(seriesWithBackdrops).ToList();
  189. view.BackdropItems = seriesWithBestBackdrops
  190. .OrderBy(i => Guid.NewGuid())
  191. .Take(10)
  192. .AsParallel()
  193. .Select(i => _dtoService.GetBaseItemDto(i, fields, user))
  194. .ToList();
  195. view.ShowsItems = series
  196. .Where(i => i.BackdropImagePaths.Count > 0)
  197. .OrderBy(i => Guid.NewGuid())
  198. .Select(i => GetItemStub(i, ImageType.Backdrop))
  199. .Where(i => i != null)
  200. .Take(1)
  201. .ToList();
  202. var romanceGenres = request.RomanceGenre.Split(',').ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
  203. var comedyGenres = request.ComedyGenre.Split(',').ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
  204. view.RomanceItems = seriesWithBackdrops
  205. .Where(i => i.Genres.Any(romanceGenres.ContainsKey))
  206. .OrderBy(i => Guid.NewGuid())
  207. .Select(i => GetItemStub(i, ImageType.Backdrop))
  208. .Where(i => i != null)
  209. .Take(1)
  210. .ToList();
  211. view.ComedyItems = seriesWithBackdrops
  212. .Where(i => i.Genres.Any(comedyGenres.ContainsKey))
  213. .OrderBy(i => Guid.NewGuid())
  214. .Select(i => GetItemStub(i, ImageType.Backdrop))
  215. .Where(i => i != null)
  216. .Take(1)
  217. .ToList();
  218. view.ActorItems = GetActors(series, user.Id);
  219. var spotlightSeries = seriesWithBestBackdrops
  220. .Where(i => i.CommunityRating.HasValue && i.CommunityRating >= 8.5)
  221. .ToList();
  222. if (spotlightSeries.Count < 20)
  223. {
  224. spotlightSeries = seriesWithBestBackdrops;
  225. }
  226. spotlightSeries = spotlightSeries
  227. .OrderBy(i => Guid.NewGuid())
  228. .Take(10)
  229. .ToList();
  230. view.SpotlightItems = spotlightSeries
  231. .AsParallel()
  232. .Select(i => _dtoService.GetBaseItemDto(i, fields, user))
  233. .ToList();
  234. var miniSpotlightItems = seriesWithBackdrops
  235. .Except(spotlightSeries.OfType<Series>())
  236. .Where(i => i.CommunityRating.HasValue && i.CommunityRating >= 8)
  237. .ToList();
  238. if (miniSpotlightItems.Count < 15)
  239. {
  240. miniSpotlightItems = seriesWithBackdrops;
  241. }
  242. view.MiniSpotlights = miniSpotlightItems
  243. .OrderBy(i => Guid.NewGuid())
  244. .Take(5)
  245. .Select(i => _dtoService.GetBaseItemDto(i, fields, user))
  246. .ToList();
  247. return ToOptimizedResult(view);
  248. }
  249. public object Get(GetMovieView request)
  250. {
  251. var user = _userManager.GetUserById(request.UserId);
  252. var items = user.RootFolder.GetRecursiveChildren(user, i => i is Movie || i is Trailer || i is BoxSet)
  253. .ToList();
  254. // Exclude trailers from backdrops because they're not always 1080p
  255. var itemsWithBackdrops = items.Where(i => i.BackdropImagePaths.Count > 0)
  256. .ToList();
  257. var view = new MoviesView();
  258. var movies = items.OfType<Movie>()
  259. .ToList();
  260. var hdMovies = movies.Where(i => i.IsHD).ToList();
  261. var familyGenres = request.FamilyGenre.Split(',').ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
  262. var familyMovies = movies.Where(i => i.Genres.Any(familyGenres.ContainsKey)).ToList();
  263. view.HDMoviePercentage = 100 * hdMovies.Count;
  264. view.HDMoviePercentage /= movies.Count;
  265. view.FamilyMoviePercentage = 100 * familyMovies.Count;
  266. view.FamilyMoviePercentage /= movies.Count;
  267. var moviesWithBackdrops = movies
  268. .Where(i => i.BackdropImagePaths.Count > 0)
  269. .ToList();
  270. var fields = new List<ItemFields>();
  271. var itemsWithTopBackdrops = FilterItemsForBackdropDisplay(itemsWithBackdrops).ToList();
  272. view.BackdropItems = itemsWithTopBackdrops
  273. .OrderBy(i => Guid.NewGuid())
  274. .Take(10)
  275. .AsParallel()
  276. .Select(i => _dtoService.GetBaseItemDto(i, fields, user))
  277. .ToList();
  278. view.MovieItems = moviesWithBackdrops
  279. .OrderBy(i => Guid.NewGuid())
  280. .Select(i => GetItemStub(i, ImageType.Backdrop))
  281. .Where(i => i != null)
  282. .Take(1)
  283. .ToList();
  284. view.TrailerItems = items
  285. .OfType<Trailer>()
  286. .Where(i => !string.IsNullOrEmpty(i.PrimaryImagePath))
  287. .OrderBy(i => Guid.NewGuid())
  288. .Select(i => GetItemStub(i, ImageType.Primary))
  289. .Where(i => i != null)
  290. .Take(1)
  291. .ToList();
  292. view.BoxSetItems = items
  293. .OfType<BoxSet>()
  294. .Where(i => i.BackdropImagePaths.Count > 0)
  295. .OrderBy(i => Guid.NewGuid())
  296. .Select(i => GetItemStub(i, ImageType.Backdrop))
  297. .Where(i => i != null)
  298. .Take(1)
  299. .ToList();
  300. view.ThreeDItems = moviesWithBackdrops
  301. .Where(i => i.Is3D)
  302. .OrderBy(i => Guid.NewGuid())
  303. .Select(i => GetItemStub(i, ImageType.Backdrop))
  304. .Where(i => i != null)
  305. .Take(1)
  306. .ToList();
  307. var romanceGenres = request.RomanceGenre.Split(',').ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
  308. var comedyGenres = request.ComedyGenre.Split(',').ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
  309. view.RomanceItems = moviesWithBackdrops
  310. .Where(i => i.Genres.Any(romanceGenres.ContainsKey))
  311. .OrderBy(i => Guid.NewGuid())
  312. .Select(i => GetItemStub(i, ImageType.Backdrop))
  313. .Where(i => i != null)
  314. .Take(1)
  315. .ToList();
  316. view.ComedyItems = moviesWithBackdrops
  317. .Where(i => i.Genres.Any(comedyGenres.ContainsKey))
  318. .OrderBy(i => Guid.NewGuid())
  319. .Select(i => GetItemStub(i, ImageType.Backdrop))
  320. .Where(i => i != null)
  321. .Take(1)
  322. .ToList();
  323. view.HDItems = hdMovies
  324. .Where(i => i.BackdropImagePaths.Count > 0)
  325. .OrderBy(i => Guid.NewGuid())
  326. .Select(i => GetItemStub(i, ImageType.Backdrop))
  327. .Where(i => i != null)
  328. .Take(1)
  329. .ToList();
  330. view.FamilyMovies = familyMovies
  331. .Where(i => i.BackdropImagePaths.Count > 0)
  332. .OrderBy(i => Guid.NewGuid())
  333. .Select(i => GetItemStub(i, ImageType.Backdrop))
  334. .Where(i => i != null)
  335. .Take(1)
  336. .ToList();
  337. view.PeopleItems = GetActors(items, user.Id);
  338. var spotlightItems = itemsWithTopBackdrops
  339. .Where(i => i.CommunityRating.HasValue && i.CommunityRating >= 8)
  340. .ToList();
  341. if (spotlightItems.Count < 20)
  342. {
  343. spotlightItems = itemsWithTopBackdrops;
  344. }
  345. spotlightItems = spotlightItems
  346. .OrderBy(i => Guid.NewGuid())
  347. .Take(10)
  348. .ToList();
  349. view.SpotlightItems = spotlightItems
  350. .AsParallel()
  351. .Select(i => _dtoService.GetBaseItemDto(i, fields, user))
  352. .ToList();
  353. var miniSpotlightItems = moviesWithBackdrops
  354. .Except(spotlightItems)
  355. .Where(i => i.CommunityRating.HasValue && i.CommunityRating >= 7.5)
  356. .ToList();
  357. if (miniSpotlightItems.Count < 15)
  358. {
  359. miniSpotlightItems = itemsWithTopBackdrops;
  360. }
  361. miniSpotlightItems = miniSpotlightItems
  362. .OrderBy(i => Guid.NewGuid())
  363. .ToList();
  364. // Avoid implicitly captured closure
  365. var currentUserId = user.Id;
  366. miniSpotlightItems.InsertRange(2, moviesWithBackdrops
  367. .Where(i => _userDataManager.GetUserData(currentUserId, i.GetUserDataKey()).PlaybackPositionTicks > 0)
  368. .OrderByDescending(i => _userDataManager.GetUserData(currentUserId, i.GetUserDataKey()).LastPlayedDate ?? DateTime.MaxValue)
  369. .Take(3));
  370. view.MiniSpotlights = miniSpotlightItems
  371. .Take(5)
  372. .Select(i => _dtoService.GetBaseItemDto(i, fields, user))
  373. .ToList();
  374. return ToOptimizedResult(view);
  375. }
  376. private IEnumerable<BaseItem> FilterItemsForBackdropDisplay(IEnumerable<BaseItem> items)
  377. {
  378. var tuples = items
  379. .Select(i => new Tuple<BaseItem, double>(i, GetResolution(i, i.BackdropImagePaths[0])))
  380. .Where(i => i.Item2 > 0)
  381. .ToList();
  382. var topItems = tuples
  383. .Where(i => i.Item2 >= 1920)
  384. .ToList();
  385. if (topItems.Count >= 10)
  386. {
  387. return topItems.Select(i => i.Item1);
  388. }
  389. return tuples.Select(i => i.Item1);
  390. }
  391. private double GetResolution(BaseItem item, string path)
  392. {
  393. try
  394. {
  395. var date = item.GetImageDateModified(path);
  396. var size = _imageProcessor.GetImageSize(path, date);
  397. return size.Width;
  398. }
  399. catch
  400. {
  401. return 0;
  402. }
  403. }
  404. private List<ItemStub> GetActors(IEnumerable<BaseItem> mediaItems, Guid userId)
  405. {
  406. var actors = mediaItems.SelectMany(i => i.People)
  407. .Select(i => i.Name)
  408. .Distinct(StringComparer.OrdinalIgnoreCase)
  409. .OrderBy(i => Guid.NewGuid())
  410. .ToList();
  411. var result = actors.Select(actor =>
  412. {
  413. try
  414. {
  415. var person = _libraryManager.GetPerson(actor);
  416. if (!string.IsNullOrEmpty(person.PrimaryImagePath))
  417. {
  418. var userdata = _userDataManager.GetUserData(userId, person.GetUserDataKey());
  419. if (userdata.IsFavorite || (userdata.Likes ?? false))
  420. {
  421. return GetItemStub(person, ImageType.Primary);
  422. }
  423. }
  424. }
  425. catch (Exception ex)
  426. {
  427. _logger.ErrorException("Error getting person {0}", ex, actor);
  428. }
  429. return null;
  430. })
  431. .Where(i => i != null)
  432. .Take(1)
  433. .ToList();
  434. if (result.Count == 0)
  435. {
  436. result = actors.Select(actor =>
  437. {
  438. try
  439. {
  440. var person = _libraryManager.GetPerson(actor);
  441. if (!string.IsNullOrEmpty(person.PrimaryImagePath))
  442. {
  443. return GetItemStub(person, ImageType.Primary);
  444. }
  445. }
  446. catch (Exception ex)
  447. {
  448. _logger.ErrorException("Error getting person {0}", ex, actor);
  449. }
  450. return null;
  451. })
  452. .Where(i => i != null)
  453. .Take(1)
  454. .ToList();
  455. }
  456. return result;
  457. }
  458. private ItemStub GetItemStub(BaseItem item, ImageType imageType)
  459. {
  460. var stub = new ItemStub
  461. {
  462. Id = _dtoService.GetDtoId(item),
  463. Name = item.Name,
  464. ImageType = imageType
  465. };
  466. try
  467. {
  468. var imagePath = item.GetImagePath(imageType, 0);
  469. stub.ImageTag = _imageProcessor.GetImageCacheTag(item, imageType, imagePath);
  470. }
  471. catch (Exception ex)
  472. {
  473. _logger.ErrorException("Error getting image tag for {0}", ex, item.Path);
  474. return null;
  475. }
  476. return stub;
  477. }
  478. }
  479. }