MovieResolver.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. using MediaBrowser.Controller.Entities;
  2. using MediaBrowser.Controller.Entities.Movies;
  3. using MediaBrowser.Controller.Library;
  4. using MediaBrowser.Controller.Providers;
  5. using MediaBrowser.Controller.Resolvers;
  6. using MediaBrowser.Model.Entities;
  7. using MediaBrowser.Naming.Common;
  8. using MediaBrowser.Naming.IO;
  9. using MediaBrowser.Naming.Video;
  10. using System;
  11. using System.Collections.Generic;
  12. using System.IO;
  13. using System.Linq;
  14. namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
  15. {
  16. /// <summary>
  17. /// Class MovieResolver
  18. /// </summary>
  19. public class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver
  20. {
  21. public MovieResolver(ILibraryManager libraryManager)
  22. : base(libraryManager)
  23. {
  24. }
  25. /// <summary>
  26. /// Gets the priority.
  27. /// </summary>
  28. /// <value>The priority.</value>
  29. public override ResolverPriority Priority
  30. {
  31. get
  32. {
  33. // Give plugins a chance to catch iso's first
  34. // Also since we have to loop through child files looking for videos,
  35. // see if we can avoid some of that by letting other resolvers claim folders first
  36. // Also run after series resolver
  37. return ResolverPriority.Third;
  38. }
  39. }
  40. public MultiItemResolverResult ResolveMultiple(Folder parent,
  41. List<FileSystemInfo> files,
  42. string collectionType,
  43. IDirectoryService directoryService)
  44. {
  45. if (IsInvalid(parent, collectionType, files))
  46. {
  47. return null;
  48. }
  49. if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
  50. {
  51. return ResolveVideos<MusicVideo>(parent, files, directoryService, collectionType, false);
  52. }
  53. if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
  54. {
  55. return ResolveVideos<Video>(parent, files, directoryService, collectionType, false);
  56. }
  57. if (string.IsNullOrEmpty(collectionType))
  58. {
  59. // Owned items should just use the plain video type
  60. if (parent == null)
  61. {
  62. return ResolveVideos<Video>(parent, files, directoryService, collectionType, false);
  63. }
  64. return ResolveVideos<Video>(parent, files, directoryService, collectionType, false);
  65. }
  66. if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
  67. {
  68. return ResolveVideos<Movie>(parent, files, directoryService, collectionType, false);
  69. }
  70. return null;
  71. }
  72. private MultiItemResolverResult ResolveVideos<T>(Folder parent, IEnumerable<FileSystemInfo> fileSystemEntries, IDirectoryService directoryService, string collectionType, bool suppportMultiEditions)
  73. where T : Video, new()
  74. {
  75. var files = new List<FileSystemInfo>();
  76. var videos = new List<BaseItem>();
  77. var leftOver = new List<FileSystemInfo>();
  78. // Loop through each child file/folder and see if we find a video
  79. foreach (var child in fileSystemEntries)
  80. {
  81. if ((child.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
  82. {
  83. leftOver.Add(child);
  84. }
  85. else
  86. {
  87. files.Add(child);
  88. }
  89. }
  90. var resolver = new VideoListResolver(new ExtendedNamingOptions(), new Naming.Logging.NullLogger());
  91. var resolverResult = resolver.Resolve(files.Select(i => new PortableFileInfo
  92. {
  93. FullName = i.FullName,
  94. Type = FileInfoType.File
  95. }).ToList(), suppportMultiEditions).ToList();
  96. var result = new MultiItemResolverResult
  97. {
  98. ExtraFiles = leftOver,
  99. Items = videos
  100. };
  101. var isInMixedFolder = resolverResult.Count > 0;
  102. foreach (var video in resolverResult)
  103. {
  104. var firstVideo = video.Files.First();
  105. var videoItem = new T
  106. {
  107. Path = video.Files[0].Path,
  108. IsInMixedFolder = isInMixedFolder,
  109. ProductionYear = video.Year,
  110. Name = video.Name,
  111. AdditionalParts = video.Files.Skip(1).Select(i => i.Path).ToList(),
  112. LocalAlternateVersions = video.AlternateVersions.Select(i => i.Path).ToList()
  113. };
  114. SetVideoType(videoItem, firstVideo);
  115. Set3DFormat(videoItem, firstVideo);
  116. result.Items.Add(videoItem);
  117. }
  118. return result;
  119. }
  120. /// <summary>
  121. /// Resolves the specified args.
  122. /// </summary>
  123. /// <param name="args">The args.</param>
  124. /// <returns>Video.</returns>
  125. protected override Video Resolve(ItemResolveArgs args)
  126. {
  127. var collectionType = args.GetCollectionType();
  128. if (IsInvalid(args.Parent, collectionType, args.FileSystemChildren))
  129. {
  130. return null;
  131. }
  132. // Find movies with their own folders
  133. if (args.IsDirectory)
  134. {
  135. if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
  136. {
  137. return FindMovie<MusicVideo>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType);
  138. }
  139. if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
  140. {
  141. return FindMovie<Video>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType);
  142. }
  143. if (string.IsNullOrEmpty(collectionType))
  144. {
  145. // Owned items should just use the plain video type
  146. if (args.Parent == null)
  147. {
  148. return FindMovie<Video>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType);
  149. }
  150. return FindMovie<Video>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType);
  151. }
  152. if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
  153. {
  154. return FindMovie<Movie>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType);
  155. }
  156. return null;
  157. }
  158. // Owned items will be caught by the plain video resolver
  159. if (args.Parent == null)
  160. {
  161. return null;
  162. }
  163. Video item = null;
  164. if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
  165. {
  166. item = ResolveVideo<MusicVideo>(args, false);
  167. }
  168. // To find a movie file, the collection type must be movies or boxsets
  169. else if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
  170. {
  171. item = ResolveVideo<Movie>(args, true);
  172. }
  173. else if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
  174. {
  175. item = ResolveVideo<Video>(args, false);
  176. }
  177. else if (string.IsNullOrEmpty(collectionType))
  178. {
  179. item = ResolveVideo<Video>(args, false);
  180. }
  181. if (item != null)
  182. {
  183. item.IsInMixedFolder = true;
  184. }
  185. return item;
  186. }
  187. /// <summary>
  188. /// Sets the initial item values.
  189. /// </summary>
  190. /// <param name="item">The item.</param>
  191. /// <param name="args">The args.</param>
  192. protected override void SetInitialItemValues(Video item, ItemResolveArgs args)
  193. {
  194. base.SetInitialItemValues(item, args);
  195. SetProviderIdsFromPath(item);
  196. }
  197. /// <summary>
  198. /// Sets the provider id from path.
  199. /// </summary>
  200. /// <param name="item">The item.</param>
  201. private void SetProviderIdsFromPath(Video item)
  202. {
  203. if (item is Movie || item is MusicVideo)
  204. {
  205. //we need to only look at the name of this actual item (not parents)
  206. var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path) : Path.GetFileName(item.ContainingFolderPath);
  207. // check for tmdb id
  208. var tmdbid = justName.GetAttributeValue("tmdbid");
  209. if (!string.IsNullOrEmpty(tmdbid))
  210. {
  211. item.SetProviderId(MetadataProviders.Tmdb, tmdbid);
  212. }
  213. // check for imdb id - we use full media path, as we can assume, that this will match in any use case (wither id in parent dir or in file name)
  214. var imdbid = item.Path.GetAttributeValue("imdbid");
  215. if (!string.IsNullOrEmpty(imdbid))
  216. {
  217. item.SetProviderId(MetadataProviders.Imdb, imdbid);
  218. }
  219. }
  220. }
  221. /// <summary>
  222. /// Finds a movie based on a child file system entries
  223. /// </summary>
  224. /// <typeparam name="T"></typeparam>
  225. /// <param name="path">The path.</param>
  226. /// <param name="parent">The parent.</param>
  227. /// <param name="fileSystemEntries">The file system entries.</param>
  228. /// <param name="directoryService">The directory service.</param>
  229. /// <param name="collectionType">Type of the collection.</param>
  230. /// <returns>Movie.</returns>
  231. private T FindMovie<T>(string path, Folder parent, List<FileSystemInfo> fileSystemEntries, IDirectoryService directoryService, string collectionType)
  232. where T : Video, new()
  233. {
  234. var multiDiscFolders = new List<FileSystemInfo>();
  235. // Search for a folder rip
  236. foreach (var child in fileSystemEntries)
  237. {
  238. var filename = child.Name;
  239. if ((child.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
  240. {
  241. if (IsDvdDirectory(filename))
  242. {
  243. var movie = new T
  244. {
  245. Path = path,
  246. VideoType = VideoType.Dvd
  247. };
  248. Set3DFormat(movie);
  249. return movie;
  250. }
  251. if (IsBluRayDirectory(filename))
  252. {
  253. var movie = new T
  254. {
  255. Path = path,
  256. VideoType = VideoType.BluRay
  257. };
  258. Set3DFormat(movie);
  259. return movie;
  260. }
  261. multiDiscFolders.Add(child);
  262. }
  263. else if (IsDvdFile(filename))
  264. {
  265. var movie = new T
  266. {
  267. Path = path,
  268. VideoType = VideoType.Dvd
  269. };
  270. Set3DFormat(movie);
  271. return movie;
  272. }
  273. }
  274. var supportsMultiVersion = !string.Equals(collectionType, CollectionType.HomeVideos) &&
  275. !string.Equals(collectionType, CollectionType.MusicVideos);
  276. var result = ResolveVideos<T>(parent, fileSystemEntries, directoryService, collectionType, supportsMultiVersion);
  277. if (result.Items.Count == 1)
  278. {
  279. var movie = (T)result.Items[0];
  280. movie.IsInMixedFolder = false;
  281. movie.Name = Path.GetFileName(movie.ContainingFolderPath);
  282. return movie;
  283. }
  284. if (result.Items.Count == 0 && multiDiscFolders.Count > 0)
  285. {
  286. return GetMultiDiscMovie<T>(multiDiscFolders, directoryService);
  287. }
  288. return null;
  289. }
  290. /// <summary>
  291. /// Gets the multi disc movie.
  292. /// </summary>
  293. /// <typeparam name="T"></typeparam>
  294. /// <param name="multiDiscFolders">The folders.</param>
  295. /// <param name="directoryService">The directory service.</param>
  296. /// <returns>``0.</returns>
  297. private T GetMultiDiscMovie<T>(List<FileSystemInfo> multiDiscFolders, IDirectoryService directoryService)
  298. where T : Video, new()
  299. {
  300. var videoTypes = new List<VideoType>();
  301. var folderPaths = multiDiscFolders.Select(i => i.FullName).Where(i =>
  302. {
  303. var subFileEntries = directoryService.GetFileSystemEntries(i)
  304. .ToList();
  305. var subfolders = subFileEntries
  306. .Where(e => (e.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
  307. .Select(d => d.Name)
  308. .ToList();
  309. if (subfolders.Any(IsDvdDirectory))
  310. {
  311. videoTypes.Add(VideoType.Dvd);
  312. return true;
  313. }
  314. if (subfolders.Any(IsBluRayDirectory))
  315. {
  316. videoTypes.Add(VideoType.BluRay);
  317. return true;
  318. }
  319. var subFiles = subFileEntries
  320. .Where(e => (e.Attributes & FileAttributes.Directory) != FileAttributes.Directory)
  321. .Select(d => d.Name);
  322. if (subFiles.Any(IsDvdFile))
  323. {
  324. videoTypes.Add(VideoType.Dvd);
  325. return true;
  326. }
  327. return false;
  328. }).OrderBy(i => i).ToList();
  329. // If different video types were found, don't allow this
  330. if (videoTypes.Distinct().Count() > 1)
  331. {
  332. return null;
  333. }
  334. if (folderPaths.Count == 0)
  335. {
  336. return null;
  337. }
  338. var resolver = new StackResolver(new ExtendedNamingOptions(), new Naming.Logging.NullLogger());
  339. var result = resolver.ResolveDirectories(folderPaths);
  340. if (result.Stacks.Count != 1)
  341. {
  342. return null;
  343. }
  344. return new T
  345. {
  346. Path = folderPaths[0],
  347. AdditionalParts = folderPaths.Skip(1).ToList(),
  348. VideoType = videoTypes[0],
  349. Name = result.Stacks[0].Name
  350. };
  351. }
  352. private bool IsInvalid(Folder parent, string collectionType, IEnumerable<FileSystemInfo> files)
  353. {
  354. if (parent != null)
  355. {
  356. if (parent.IsRoot)
  357. {
  358. return true;
  359. }
  360. }
  361. var validCollectionTypes = new[]
  362. {
  363. string.Empty,
  364. CollectionType.Movies,
  365. CollectionType.HomeVideos,
  366. CollectionType.MusicVideos,
  367. CollectionType.Movies
  368. };
  369. return !validCollectionTypes.Contains(collectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase);
  370. }
  371. }
  372. }