LibraryService.cs 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Net;
  6. using System.Text.RegularExpressions;
  7. using System.Threading;
  8. using System.Threading.Tasks;
  9. using MediaBrowser.Api.Movies;
  10. using MediaBrowser.Common.Extensions;
  11. using MediaBrowser.Common.Progress;
  12. using MediaBrowser.Controller.Configuration;
  13. using MediaBrowser.Controller.Dto;
  14. using MediaBrowser.Controller.Entities;
  15. using MediaBrowser.Controller.Entities.Audio;
  16. using MediaBrowser.Controller.Entities.Movies;
  17. using MediaBrowser.Controller.Entities.TV;
  18. using MediaBrowser.Controller.Library;
  19. using MediaBrowser.Controller.Net;
  20. using MediaBrowser.Controller.Providers;
  21. using MediaBrowser.Model.Activity;
  22. using MediaBrowser.Model.Configuration;
  23. using MediaBrowser.Model.Dto;
  24. using MediaBrowser.Model.Entities;
  25. using MediaBrowser.Model.Globalization;
  26. using MediaBrowser.Model.Querying;
  27. using MediaBrowser.Model.Services;
  28. using Microsoft.Extensions.Logging;
  29. using Microsoft.Net.Http.Headers;
  30. namespace MediaBrowser.Api.Library
  31. {
  32. [Route("/Items/{Id}/File", "GET", Summary = "Gets the original file of an item")]
  33. [Authenticated]
  34. public class GetFile
  35. {
  36. /// <summary>
  37. /// Gets or sets the id.
  38. /// </summary>
  39. /// <value>The id.</value>
  40. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
  41. public string Id { get; set; }
  42. }
  43. /// <summary>
  44. /// Class GetCriticReviews
  45. /// </summary>
  46. [Route("/Items/{Id}/CriticReviews", "GET", Summary = "Gets critic reviews for an item")]
  47. [Authenticated]
  48. public class GetCriticReviews : IReturn<QueryResult<BaseItemDto>>
  49. {
  50. /// <summary>
  51. /// Gets or sets the id.
  52. /// </summary>
  53. /// <value>The id.</value>
  54. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
  55. public string Id { get; set; }
  56. /// <summary>
  57. /// Skips over a given number of items within the results. Use for paging.
  58. /// </summary>
  59. /// <value>The start index.</value>
  60. [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
  61. public int? StartIndex { get; set; }
  62. /// <summary>
  63. /// The maximum number of items to return
  64. /// </summary>
  65. /// <value>The limit.</value>
  66. [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
  67. public int? Limit { get; set; }
  68. }
  69. /// <summary>
  70. /// Class GetThemeSongs
  71. /// </summary>
  72. [Route("/Items/{Id}/ThemeSongs", "GET", Summary = "Gets theme songs for an item")]
  73. [Authenticated]
  74. public class GetThemeSongs : IReturn<ThemeMediaResult>
  75. {
  76. /// <summary>
  77. /// Gets or sets the user id.
  78. /// </summary>
  79. /// <value>The user id.</value>
  80. [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
  81. public Guid UserId { get; set; }
  82. /// <summary>
  83. /// Gets or sets the id.
  84. /// </summary>
  85. /// <value>The id.</value>
  86. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
  87. public string Id { get; set; }
  88. [ApiMember(Name = "InheritFromParent", Description = "Determines whether or not parent items should be searched for theme media.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
  89. public bool InheritFromParent { get; set; }
  90. }
  91. /// <summary>
  92. /// Class GetThemeVideos
  93. /// </summary>
  94. [Route("/Items/{Id}/ThemeVideos", "GET", Summary = "Gets theme videos for an item")]
  95. [Authenticated]
  96. public class GetThemeVideos : IReturn<ThemeMediaResult>
  97. {
  98. /// <summary>
  99. /// Gets or sets the user id.
  100. /// </summary>
  101. /// <value>The user id.</value>
  102. [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
  103. public Guid UserId { get; set; }
  104. /// <summary>
  105. /// Gets or sets the id.
  106. /// </summary>
  107. /// <value>The id.</value>
  108. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
  109. public string Id { get; set; }
  110. [ApiMember(Name = "InheritFromParent", Description = "Determines whether or not parent items should be searched for theme media.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
  111. public bool InheritFromParent { get; set; }
  112. }
  113. /// <summary>
  114. /// Class GetThemeVideos
  115. /// </summary>
  116. [Route("/Items/{Id}/ThemeMedia", "GET", Summary = "Gets theme videos and songs for an item")]
  117. [Authenticated]
  118. public class GetThemeMedia : IReturn<AllThemeMediaResult>
  119. {
  120. /// <summary>
  121. /// Gets or sets the user id.
  122. /// </summary>
  123. /// <value>The user id.</value>
  124. [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
  125. public Guid UserId { get; set; }
  126. /// <summary>
  127. /// Gets or sets the id.
  128. /// </summary>
  129. /// <value>The id.</value>
  130. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
  131. public string Id { get; set; }
  132. [ApiMember(Name = "InheritFromParent", Description = "Determines whether or not parent items should be searched for theme media.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
  133. public bool InheritFromParent { get; set; }
  134. }
  135. [Route("/Library/Refresh", "POST", Summary = "Starts a library scan")]
  136. [Authenticated(Roles = "Admin")]
  137. public class RefreshLibrary : IReturnVoid
  138. {
  139. }
  140. [Route("/Items/{Id}", "DELETE", Summary = "Deletes an item from the library and file system")]
  141. [Authenticated]
  142. public class DeleteItem : IReturnVoid
  143. {
  144. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
  145. public string Id { get; set; }
  146. }
  147. [Route("/Items", "DELETE", Summary = "Deletes an item from the library and file system")]
  148. [Authenticated]
  149. public class DeleteItems : IReturnVoid
  150. {
  151. [ApiMember(Name = "Ids", Description = "Ids", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
  152. public string Ids { get; set; }
  153. }
  154. [Route("/Items/Counts", "GET")]
  155. [Authenticated]
  156. public class GetItemCounts : IReturn<ItemCounts>
  157. {
  158. [ApiMember(Name = "UserId", Description = "Optional. Get counts from a specific user's library.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
  159. public Guid UserId { get; set; }
  160. [ApiMember(Name = "IsFavorite", Description = "Optional. Get counts of favorite items", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
  161. public bool? IsFavorite { get; set; }
  162. }
  163. [Route("/Items/{Id}/Ancestors", "GET", Summary = "Gets all parents of an item")]
  164. [Authenticated]
  165. public class GetAncestors : IReturn<BaseItemDto[]>
  166. {
  167. /// <summary>
  168. /// Gets or sets the user id.
  169. /// </summary>
  170. /// <value>The user id.</value>
  171. [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
  172. public Guid UserId { get; set; }
  173. /// <summary>
  174. /// Gets or sets the id.
  175. /// </summary>
  176. /// <value>The id.</value>
  177. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
  178. public string Id { get; set; }
  179. }
  180. /// <summary>
  181. /// Class GetPhyscialPaths
  182. /// </summary>
  183. [Route("/Library/PhysicalPaths", "GET", Summary = "Gets a list of physical paths from virtual folders")]
  184. [Authenticated(Roles = "Admin")]
  185. public class GetPhyscialPaths : IReturn<List<string>>
  186. {
  187. }
  188. [Route("/Library/MediaFolders", "GET", Summary = "Gets all user media folders.")]
  189. [Authenticated]
  190. public class GetMediaFolders : IReturn<QueryResult<BaseItemDto>>
  191. {
  192. [ApiMember(Name = "IsHidden", Description = "Optional. Filter by folders that are marked hidden, or not.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
  193. public bool? IsHidden { get; set; }
  194. }
  195. [Route("/Library/Series/Added", "POST", Summary = "Reports that new episodes of a series have been added by an external source")]
  196. [Route("/Library/Series/Updated", "POST", Summary = "Reports that new episodes of a series have been added by an external source")]
  197. [Authenticated]
  198. public class PostUpdatedSeries : IReturnVoid
  199. {
  200. [ApiMember(Name = "TvdbId", Description = "Tvdb Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "POST")]
  201. public string TvdbId { get; set; }
  202. }
  203. [Route("/Library/Movies/Added", "POST", Summary = "Reports that new movies have been added by an external source")]
  204. [Route("/Library/Movies/Updated", "POST", Summary = "Reports that new movies have been added by an external source")]
  205. [Authenticated]
  206. public class PostUpdatedMovies : IReturnVoid
  207. {
  208. [ApiMember(Name = "TmdbId", Description = "Tmdb Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "POST")]
  209. public string TmdbId { get; set; }
  210. [ApiMember(Name = "ImdbId", Description = "Imdb Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "POST")]
  211. public string ImdbId { get; set; }
  212. }
  213. public class MediaUpdateInfo
  214. {
  215. public string Path { get; set; }
  216. // Created, Modified, Deleted
  217. public string UpdateType { get; set; }
  218. }
  219. [Route("/Library/Media/Updated", "POST", Summary = "Reports that new movies have been added by an external source")]
  220. [Authenticated]
  221. public class PostUpdatedMedia : IReturnVoid
  222. {
  223. [ApiMember(Name = "Updates", Description = "A list of updated media paths", IsRequired = false, DataType = "string", ParameterType = "body", Verb = "POST")]
  224. public List<MediaUpdateInfo> Updates { get; set; }
  225. }
  226. [Route("/Items/{Id}/Download", "GET", Summary = "Downloads item media")]
  227. [Authenticated(Roles = "download")]
  228. public class GetDownload
  229. {
  230. /// <summary>
  231. /// Gets or sets the id.
  232. /// </summary>
  233. /// <value>The id.</value>
  234. [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
  235. public string Id { get; set; }
  236. }
  237. [Route("/Artists/{Id}/Similar", "GET", Summary = "Finds albums similar to a given album.")]
  238. [Route("/Items/{Id}/Similar", "GET", Summary = "Gets similar items")]
  239. [Route("/Albums/{Id}/Similar", "GET", Summary = "Finds albums similar to a given album.")]
  240. [Route("/Shows/{Id}/Similar", "GET", Summary = "Finds tv shows similar to a given one.")]
  241. [Route("/Movies/{Id}/Similar", "GET", Summary = "Finds movies and trailers similar to a given movie.")]
  242. [Route("/Trailers/{Id}/Similar", "GET", Summary = "Finds movies and trailers similar to a given trailer.")]
  243. [Authenticated]
  244. public class GetSimilarItems : BaseGetSimilarItemsFromItem
  245. {
  246. }
  247. [Route("/Libraries/AvailableOptions", "GET")]
  248. [Authenticated(AllowBeforeStartupWizard = true)]
  249. public class GetLibraryOptionsInfo : IReturn<LibraryOptionsResult>
  250. {
  251. public string LibraryContentType { get; set; }
  252. public bool IsNewLibrary { get; set; }
  253. }
  254. public class LibraryOptionInfo
  255. {
  256. public string Name { get; set; }
  257. public bool DefaultEnabled { get; set; }
  258. }
  259. public class LibraryOptionsResult
  260. {
  261. public LibraryOptionInfo[] MetadataSavers { get; set; }
  262. public LibraryOptionInfo[] MetadataReaders { get; set; }
  263. public LibraryOptionInfo[] SubtitleFetchers { get; set; }
  264. public LibraryTypeOptions[] TypeOptions { get; set; }
  265. }
  266. public class LibraryTypeOptions
  267. {
  268. public string Type { get; set; }
  269. public LibraryOptionInfo[] MetadataFetchers { get; set; }
  270. public LibraryOptionInfo[] ImageFetchers { get; set; }
  271. public ImageType[] SupportedImageTypes { get; set; }
  272. public ImageOption[] DefaultImageOptions { get; set; }
  273. }
  274. /// <summary>
  275. /// Class LibraryService
  276. /// </summary>
  277. public class LibraryService : BaseApiService
  278. {
  279. private readonly IProviderManager _providerManager;
  280. private readonly ILibraryManager _libraryManager;
  281. private readonly IUserManager _userManager;
  282. private readonly IDtoService _dtoService;
  283. private readonly IAuthorizationContext _authContext;
  284. private readonly IActivityManager _activityManager;
  285. private readonly ILocalizationManager _localization;
  286. private readonly ILibraryMonitor _libraryMonitor;
  287. private readonly ILogger<MoviesService> _moviesServiceLogger;
  288. /// <summary>
  289. /// Initializes a new instance of the <see cref="LibraryService" /> class.
  290. /// </summary>
  291. public LibraryService(
  292. ILogger<LibraryService> logger,
  293. ILogger<MoviesService> moviesServiceLogger,
  294. IServerConfigurationManager serverConfigurationManager,
  295. IHttpResultFactory httpResultFactory,
  296. IProviderManager providerManager,
  297. ILibraryManager libraryManager,
  298. IUserManager userManager,
  299. IDtoService dtoService,
  300. IAuthorizationContext authContext,
  301. IActivityManager activityManager,
  302. ILocalizationManager localization,
  303. ILibraryMonitor libraryMonitor)
  304. : base(logger, serverConfigurationManager, httpResultFactory)
  305. {
  306. _providerManager = providerManager;
  307. _libraryManager = libraryManager;
  308. _userManager = userManager;
  309. _dtoService = dtoService;
  310. _authContext = authContext;
  311. _activityManager = activityManager;
  312. _localization = localization;
  313. _libraryMonitor = libraryMonitor;
  314. _moviesServiceLogger = moviesServiceLogger;
  315. }
  316. private string[] GetRepresentativeItemTypes(string contentType)
  317. {
  318. return contentType switch
  319. {
  320. CollectionType.BoxSets => new[] {"BoxSet"},
  321. CollectionType.Playlists => new[] {"Playlist"},
  322. CollectionType.Movies => new[] {"Movie"},
  323. CollectionType.TvShows => new[] {"Series", "Season", "Episode"},
  324. CollectionType.Books => new[] {"Book"},
  325. CollectionType.Music => new[] {"MusicAlbum", "MusicArtist", "Audio", "MusicVideo"},
  326. CollectionType.HomeVideos => new[] {"Video", "Photo"},
  327. CollectionType.Photos => new[] {"Video", "Photo"},
  328. CollectionType.MusicVideos => new[] {"MusicVideo"},
  329. _ => new[] {"Series", "Season", "Episode", "Movie"}
  330. };
  331. }
  332. private bool IsSaverEnabledByDefault(string name, string[] itemTypes, bool isNewLibrary)
  333. {
  334. if (isNewLibrary)
  335. {
  336. return false;
  337. }
  338. var metadataOptions = ServerConfigurationManager.Configuration.MetadataOptions
  339. .Where(i => itemTypes.Contains(i.ItemType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
  340. .ToArray();
  341. if (metadataOptions.Length == 0)
  342. {
  343. return true;
  344. }
  345. return metadataOptions.Any(i => !i.DisabledMetadataSavers.Contains(name, StringComparer.OrdinalIgnoreCase));
  346. }
  347. private bool IsMetadataFetcherEnabledByDefault(string name, string type, bool isNewLibrary)
  348. {
  349. if (isNewLibrary)
  350. {
  351. if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase))
  352. {
  353. return !(string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase)
  354. || string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase)
  355. || string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase));
  356. }
  357. return string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase)
  358. || string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase)
  359. || string.Equals(name, "MusicBrainz", StringComparison.OrdinalIgnoreCase);
  360. }
  361. var metadataOptions = ServerConfigurationManager.Configuration.MetadataOptions
  362. .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
  363. .ToArray();
  364. return metadataOptions.Length == 0
  365. || metadataOptions.Any(i => !i.DisabledMetadataFetchers.Contains(name, StringComparer.OrdinalIgnoreCase));
  366. }
  367. private bool IsImageFetcherEnabledByDefault(string name, string type, bool isNewLibrary)
  368. {
  369. if (isNewLibrary)
  370. {
  371. if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase))
  372. {
  373. return !string.Equals(type, "Series", StringComparison.OrdinalIgnoreCase)
  374. && !string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase)
  375. && !string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase)
  376. && !string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase);
  377. }
  378. return string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase)
  379. || string.Equals(name, "Screen Grabber", StringComparison.OrdinalIgnoreCase)
  380. || string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase)
  381. || string.Equals(name, "Emby Designs", StringComparison.OrdinalIgnoreCase)
  382. || string.Equals(name, "Image Extractor", StringComparison.OrdinalIgnoreCase);
  383. }
  384. var metadataOptions = ServerConfigurationManager.Configuration.MetadataOptions
  385. .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
  386. .ToArray();
  387. if (metadataOptions.Length == 0)
  388. {
  389. return true;
  390. }
  391. return metadataOptions.Any(i => !i.DisabledImageFetchers.Contains(name, StringComparer.OrdinalIgnoreCase));
  392. }
  393. public object Get(GetLibraryOptionsInfo request)
  394. {
  395. var result = new LibraryOptionsResult();
  396. var types = GetRepresentativeItemTypes(request.LibraryContentType);
  397. var isNewLibrary = request.IsNewLibrary;
  398. var typesList = types.ToList();
  399. var plugins = _providerManager.GetAllMetadataPlugins()
  400. .Where(i => types.Contains(i.ItemType, StringComparer.OrdinalIgnoreCase))
  401. .OrderBy(i => typesList.IndexOf(i.ItemType))
  402. .ToList();
  403. result.MetadataSavers = plugins
  404. .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.MetadataSaver))
  405. .Select(i => new LibraryOptionInfo
  406. {
  407. Name = i.Name,
  408. DefaultEnabled = IsSaverEnabledByDefault(i.Name, types, isNewLibrary)
  409. })
  410. .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
  411. .Select(x => x.First())
  412. .ToArray();
  413. result.MetadataReaders = plugins
  414. .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.LocalMetadataProvider))
  415. .Select(i => new LibraryOptionInfo
  416. {
  417. Name = i.Name,
  418. DefaultEnabled = true
  419. })
  420. .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
  421. .Select(x => x.First())
  422. .ToArray();
  423. result.SubtitleFetchers = plugins
  424. .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.SubtitleFetcher))
  425. .Select(i => new LibraryOptionInfo
  426. {
  427. Name = i.Name,
  428. DefaultEnabled = true
  429. })
  430. .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
  431. .Select(x => x.First())
  432. .ToArray();
  433. var typeOptions = new List<LibraryTypeOptions>();
  434. foreach (var type in types)
  435. {
  436. TypeOptions.DefaultImageOptions.TryGetValue(type, out var defaultImageOptions);
  437. typeOptions.Add(new LibraryTypeOptions
  438. {
  439. Type = type,
  440. MetadataFetchers = plugins
  441. .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
  442. .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.MetadataFetcher))
  443. .Select(i => new LibraryOptionInfo
  444. {
  445. Name = i.Name,
  446. DefaultEnabled = IsMetadataFetcherEnabledByDefault(i.Name, type, isNewLibrary)
  447. })
  448. .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
  449. .Select(x => x.First())
  450. .ToArray(),
  451. ImageFetchers = plugins
  452. .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
  453. .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.ImageFetcher))
  454. .Select(i => new LibraryOptionInfo
  455. {
  456. Name = i.Name,
  457. DefaultEnabled = IsImageFetcherEnabledByDefault(i.Name, type, isNewLibrary)
  458. })
  459. .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
  460. .Select(x => x.First())
  461. .ToArray(),
  462. SupportedImageTypes = plugins
  463. .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
  464. .SelectMany(i => i.SupportedImageTypes ?? Array.Empty<ImageType>())
  465. .Distinct()
  466. .ToArray(),
  467. DefaultImageOptions = defaultImageOptions ?? Array.Empty<ImageOption>()
  468. });
  469. }
  470. result.TypeOptions = typeOptions.ToArray();
  471. return result;
  472. }
  473. public object Get(GetSimilarItems request)
  474. {
  475. var item = string.IsNullOrEmpty(request.Id) ?
  476. (!request.UserId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() :
  477. _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
  478. var program = item as IHasProgramAttributes;
  479. if (item is Movie || (program != null && program.IsMovie) || item is Trailer)
  480. {
  481. return new MoviesService(
  482. _moviesServiceLogger,
  483. ServerConfigurationManager,
  484. ResultFactory,
  485. _userManager,
  486. _libraryManager,
  487. _dtoService,
  488. _authContext)
  489. {
  490. Request = Request,
  491. }.GetSimilarItemsResult(request);
  492. }
  493. if (program != null && program.IsSeries)
  494. {
  495. return GetSimilarItemsResult(request, new[] { typeof(Series).Name });
  496. }
  497. if (item is Episode || (item is IItemByName && !(item is MusicArtist)))
  498. {
  499. return new QueryResult<BaseItemDto>();
  500. }
  501. return GetSimilarItemsResult(request, new[] { item.GetType().Name });
  502. }
  503. private QueryResult<BaseItemDto> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request, string[] includeItemTypes)
  504. {
  505. var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null;
  506. var item = string.IsNullOrEmpty(request.Id) ?
  507. (!request.UserId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() :
  508. _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
  509. var dtoOptions = GetDtoOptions(_authContext, request);
  510. var query = new InternalItemsQuery(user)
  511. {
  512. Limit = request.Limit,
  513. IncludeItemTypes = includeItemTypes,
  514. SimilarTo = item,
  515. DtoOptions = dtoOptions,
  516. EnableTotalRecordCount = false
  517. };
  518. // ExcludeArtistIds
  519. if (!string.IsNullOrEmpty(request.ExcludeArtistIds))
  520. {
  521. query.ExcludeArtistIds = GetGuids(request.ExcludeArtistIds);
  522. }
  523. List<BaseItem> itemsResult;
  524. if (item is MusicArtist)
  525. {
  526. query.IncludeItemTypes = Array.Empty<string>();
  527. itemsResult = _libraryManager.GetArtists(query).Items.Select(i => i.Item1).ToList();
  528. }
  529. else
  530. {
  531. itemsResult = _libraryManager.GetItemList(query);
  532. }
  533. var returnList = _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user);
  534. var result = new QueryResult<BaseItemDto>
  535. {
  536. Items = returnList,
  537. TotalRecordCount = itemsResult.Count
  538. };
  539. return result;
  540. }
  541. public object Get(GetMediaFolders request)
  542. {
  543. var items = _libraryManager.GetUserRootFolder().Children.Concat(_libraryManager.RootFolder.VirtualChildren).OrderBy(i => i.SortName).ToList();
  544. if (request.IsHidden.HasValue)
  545. {
  546. var val = request.IsHidden.Value;
  547. items = items.Where(i => i.IsHidden == val).ToList();
  548. }
  549. var dtoOptions = GetDtoOptions(_authContext, request);
  550. var result = new QueryResult<BaseItemDto>
  551. {
  552. TotalRecordCount = items.Count,
  553. Items = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions)).ToArray()
  554. };
  555. return result;
  556. }
  557. public void Post(PostUpdatedSeries request)
  558. {
  559. var series = _libraryManager.GetItemList(new InternalItemsQuery
  560. {
  561. IncludeItemTypes = new[] { typeof(Series).Name },
  562. DtoOptions = new DtoOptions(false)
  563. {
  564. EnableImages = false
  565. }
  566. }).Where(i => string.Equals(request.TvdbId, i.GetProviderId(MetadataProviders.Tvdb), StringComparison.OrdinalIgnoreCase)).ToArray();
  567. foreach (var item in series)
  568. {
  569. _libraryMonitor.ReportFileSystemChanged(item.Path);
  570. }
  571. }
  572. public void Post(PostUpdatedMedia request)
  573. {
  574. if (request.Updates != null)
  575. {
  576. foreach (var item in request.Updates)
  577. {
  578. _libraryMonitor.ReportFileSystemChanged(item.Path);
  579. }
  580. }
  581. }
  582. public void Post(PostUpdatedMovies request)
  583. {
  584. var movies = _libraryManager.GetItemList(new InternalItemsQuery
  585. {
  586. IncludeItemTypes = new[] { typeof(Movie).Name },
  587. DtoOptions = new DtoOptions(false)
  588. {
  589. EnableImages = false
  590. }
  591. });
  592. if (!string.IsNullOrWhiteSpace(request.ImdbId))
  593. {
  594. movies = movies.Where(i => string.Equals(request.ImdbId, i.GetProviderId(MetadataProviders.Imdb), StringComparison.OrdinalIgnoreCase)).ToList();
  595. }
  596. else if (!string.IsNullOrWhiteSpace(request.TmdbId))
  597. {
  598. movies = movies.Where(i => string.Equals(request.TmdbId, i.GetProviderId(MetadataProviders.Tmdb), StringComparison.OrdinalIgnoreCase)).ToList();
  599. }
  600. else
  601. {
  602. movies = new List<BaseItem>();
  603. }
  604. foreach (var item in movies)
  605. {
  606. _libraryMonitor.ReportFileSystemChanged(item.Path);
  607. }
  608. }
  609. public Task<object> Get(GetDownload request)
  610. {
  611. var item = _libraryManager.GetItemById(request.Id);
  612. var auth = _authContext.GetAuthorizationInfo(Request);
  613. var user = auth.User;
  614. if (user != null)
  615. {
  616. if (!item.CanDownload(user))
  617. {
  618. throw new ArgumentException("Item does not support downloading");
  619. }
  620. }
  621. else
  622. {
  623. if (!item.CanDownload())
  624. {
  625. throw new ArgumentException("Item does not support downloading");
  626. }
  627. }
  628. var headers = new Dictionary<string, string>();
  629. if (user != null)
  630. {
  631. LogDownload(item, user, auth);
  632. }
  633. var path = item.Path;
  634. // Quotes are valid in linux. They'll possibly cause issues here
  635. var filename = (Path.GetFileName(path) ?? string.Empty).Replace("\"", string.Empty);
  636. if (!string.IsNullOrWhiteSpace(filename))
  637. {
  638. // Kestrel doesn't support non-ASCII characters in headers
  639. if (Regex.IsMatch(filename, @"[^\p{IsBasicLatin}]"))
  640. {
  641. // Manually encoding non-ASCII characters, following https://tools.ietf.org/html/rfc5987#section-3.2.2
  642. headers[HeaderNames.ContentDisposition] = "attachment; filename*=UTF-8''" + WebUtility.UrlEncode(filename);
  643. }
  644. else
  645. {
  646. headers[HeaderNames.ContentDisposition] = "attachment; filename=\"" + filename + "\"";
  647. }
  648. }
  649. return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
  650. {
  651. Path = path,
  652. ResponseHeaders = headers
  653. });
  654. }
  655. private void LogDownload(BaseItem item, User user, AuthorizationInfo auth)
  656. {
  657. try
  658. {
  659. _activityManager.Create(new Jellyfin.Data.Entities.ActivityLog(
  660. string.Format(_localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Name, item.Name),
  661. "UserDownloadingContent",
  662. auth.UserId)
  663. {
  664. ShortOverview = string.Format(_localization.GetLocalizedString("AppDeviceValues"), auth.Client, auth.Device),
  665. });
  666. }
  667. catch
  668. {
  669. // Logged at lower levels
  670. }
  671. }
  672. public Task<object> Get(GetFile request)
  673. {
  674. var item = _libraryManager.GetItemById(request.Id);
  675. return ResultFactory.GetStaticFileResult(Request, item.Path);
  676. }
  677. /// <summary>
  678. /// Gets the specified request.
  679. /// </summary>
  680. /// <param name="request">The request.</param>
  681. /// <returns>System.Object.</returns>
  682. public object Get(GetPhyscialPaths request)
  683. {
  684. var result = _libraryManager.RootFolder.Children
  685. .SelectMany(c => c.PhysicalLocations)
  686. .ToList();
  687. return ToOptimizedResult(result);
  688. }
  689. /// <summary>
  690. /// Gets the specified request.
  691. /// </summary>
  692. /// <param name="request">The request.</param>
  693. /// <returns>System.Object.</returns>
  694. public object Get(GetAncestors request)
  695. {
  696. var result = GetAncestors(request);
  697. return ToOptimizedResult(result);
  698. }
  699. /// <summary>
  700. /// Gets the ancestors.
  701. /// </summary>
  702. /// <param name="request">The request.</param>
  703. /// <returns>Task{BaseItemDto[]}.</returns>
  704. public List<BaseItemDto> GetAncestors(GetAncestors request)
  705. {
  706. var item = _libraryManager.GetItemById(request.Id);
  707. var baseItemDtos = new List<BaseItemDto>();
  708. var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null;
  709. var dtoOptions = GetDtoOptions(_authContext, request);
  710. BaseItem parent = item.GetParent();
  711. while (parent != null)
  712. {
  713. if (user != null)
  714. {
  715. parent = TranslateParentItem(parent, user);
  716. }
  717. baseItemDtos.Add(_dtoService.GetBaseItemDto(parent, dtoOptions, user));
  718. parent = parent.GetParent();
  719. }
  720. return baseItemDtos;
  721. }
  722. private BaseItem TranslateParentItem(BaseItem item, User user)
  723. {
  724. return item.GetParent() is AggregateFolder
  725. ? _libraryManager.GetUserRootFolder().GetChildren(user, true)
  726. .FirstOrDefault(i => i.PhysicalLocations.Contains(item.Path))
  727. : item;
  728. }
  729. /// <summary>
  730. /// Gets the specified request.
  731. /// </summary>
  732. /// <param name="request">The request.</param>
  733. /// <returns>System.Object.</returns>
  734. public object Get(GetCriticReviews request)
  735. {
  736. return new QueryResult<BaseItemDto>();
  737. }
  738. /// <summary>
  739. /// Gets the specified request.
  740. /// </summary>
  741. /// <param name="request">The request.</param>
  742. /// <returns>System.Object.</returns>
  743. public object Get(GetItemCounts request)
  744. {
  745. var user = request.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(request.UserId);
  746. var counts = new ItemCounts
  747. {
  748. AlbumCount = GetCount(typeof(MusicAlbum), user, request),
  749. EpisodeCount = GetCount(typeof(Episode), user, request),
  750. MovieCount = GetCount(typeof(Movie), user, request),
  751. SeriesCount = GetCount(typeof(Series), user, request),
  752. SongCount = GetCount(typeof(Audio), user, request),
  753. MusicVideoCount = GetCount(typeof(MusicVideo), user, request),
  754. BoxSetCount = GetCount(typeof(BoxSet), user, request),
  755. BookCount = GetCount(typeof(Book), user, request)
  756. };
  757. return ToOptimizedResult(counts);
  758. }
  759. private int GetCount(Type type, User user, GetItemCounts request)
  760. {
  761. var query = new InternalItemsQuery(user)
  762. {
  763. IncludeItemTypes = new[] { type.Name },
  764. Limit = 0,
  765. Recursive = true,
  766. IsVirtualItem = false,
  767. IsFavorite = request.IsFavorite,
  768. DtoOptions = new DtoOptions(false)
  769. {
  770. EnableImages = false
  771. }
  772. };
  773. return _libraryManager.GetItemsResult(query).TotalRecordCount;
  774. }
  775. /// <summary>
  776. /// Posts the specified request.
  777. /// </summary>
  778. /// <param name="request">The request.</param>
  779. public async Task Post(RefreshLibrary request)
  780. {
  781. try
  782. {
  783. await _libraryManager.ValidateMediaLibrary(new SimpleProgress<double>(), CancellationToken.None).ConfigureAwait(false);
  784. }
  785. catch (Exception ex)
  786. {
  787. Logger.LogError(ex, "Error refreshing library");
  788. }
  789. }
  790. /// <summary>
  791. /// Deletes the specified request.
  792. /// </summary>
  793. /// <param name="request">The request.</param>
  794. public void Delete(DeleteItems request)
  795. {
  796. var ids = string.IsNullOrWhiteSpace(request.Ids)
  797. ? Array.Empty<string>()
  798. : request.Ids.Split(',');
  799. foreach (var i in ids)
  800. {
  801. var item = _libraryManager.GetItemById(i);
  802. var auth = _authContext.GetAuthorizationInfo(Request);
  803. var user = auth.User;
  804. if (!item.CanDelete(user))
  805. {
  806. if (ids.Length > 1)
  807. {
  808. throw new SecurityException("Unauthorized access");
  809. }
  810. continue;
  811. }
  812. _libraryManager.DeleteItem(item, new DeleteOptions
  813. {
  814. DeleteFileLocation = true
  815. }, true);
  816. }
  817. }
  818. /// <summary>
  819. /// Deletes the specified request.
  820. /// </summary>
  821. /// <param name="request">The request.</param>
  822. public void Delete(DeleteItem request)
  823. {
  824. Delete(new DeleteItems
  825. {
  826. Ids = request.Id
  827. });
  828. }
  829. public object Get(GetThemeMedia request)
  830. {
  831. var themeSongs = GetThemeSongs(new GetThemeSongs
  832. {
  833. InheritFromParent = request.InheritFromParent,
  834. Id = request.Id,
  835. UserId = request.UserId
  836. });
  837. var themeVideos = GetThemeVideos(new GetThemeVideos
  838. {
  839. InheritFromParent = request.InheritFromParent,
  840. Id = request.Id,
  841. UserId = request.UserId
  842. });
  843. return ToOptimizedResult(new AllThemeMediaResult
  844. {
  845. ThemeSongsResult = themeSongs,
  846. ThemeVideosResult = themeVideos,
  847. SoundtrackSongsResult = new ThemeMediaResult()
  848. });
  849. }
  850. /// <summary>
  851. /// Gets the specified request.
  852. /// </summary>
  853. /// <param name="request">The request.</param>
  854. /// <returns>System.Object.</returns>
  855. public object Get(GetThemeSongs request)
  856. {
  857. var result = GetThemeSongs(request);
  858. return ToOptimizedResult(result);
  859. }
  860. private ThemeMediaResult GetThemeSongs(GetThemeSongs request)
  861. {
  862. var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null;
  863. var item = string.IsNullOrEmpty(request.Id)
  864. ? (!request.UserId.Equals(Guid.Empty)
  865. ? _libraryManager.GetUserRootFolder()
  866. : _libraryManager.RootFolder)
  867. : _libraryManager.GetItemById(request.Id);
  868. if (item == null)
  869. {
  870. throw new ResourceNotFoundException("Item not found.");
  871. }
  872. IEnumerable<BaseItem> themeItems;
  873. while (true)
  874. {
  875. themeItems = item.GetThemeSongs();
  876. if (themeItems.Any() || !request.InheritFromParent)
  877. {
  878. break;
  879. }
  880. var parent = item.GetParent();
  881. if (parent == null)
  882. {
  883. break;
  884. }
  885. item = parent;
  886. }
  887. var dtoOptions = GetDtoOptions(_authContext, request);
  888. var items = themeItems
  889. .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))
  890. .ToArray();
  891. return new ThemeMediaResult
  892. {
  893. Items = items,
  894. TotalRecordCount = items.Length,
  895. OwnerId = item.Id
  896. };
  897. }
  898. /// <summary>
  899. /// Gets the specified request.
  900. /// </summary>
  901. /// <param name="request">The request.</param>
  902. /// <returns>System.Object.</returns>
  903. public object Get(GetThemeVideos request)
  904. {
  905. return ToOptimizedResult(GetThemeVideos(request));
  906. }
  907. public ThemeMediaResult GetThemeVideos(GetThemeVideos request)
  908. {
  909. var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null;
  910. var item = string.IsNullOrEmpty(request.Id)
  911. ? (!request.UserId.Equals(Guid.Empty)
  912. ? _libraryManager.GetUserRootFolder()
  913. : _libraryManager.RootFolder)
  914. : _libraryManager.GetItemById(request.Id);
  915. if (item == null)
  916. {
  917. throw new ResourceNotFoundException("Item not found.");
  918. }
  919. IEnumerable<BaseItem> themeItems;
  920. while (true)
  921. {
  922. themeItems = item.GetThemeVideos();
  923. if (themeItems.Any() || !request.InheritFromParent)
  924. {
  925. break;
  926. }
  927. var parent = item.GetParent();
  928. if (parent == null)
  929. {
  930. break;
  931. }
  932. item = parent;
  933. }
  934. var dtoOptions = GetDtoOptions(_authContext, request);
  935. var items = themeItems
  936. .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))
  937. .ToArray();
  938. return new ThemeMediaResult
  939. {
  940. Items = items,
  941. TotalRecordCount = items.Length,
  942. OwnerId = item.Id
  943. };
  944. }
  945. }
  946. }