MovieResolver.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. using MediaBrowser.Common.Extensions;
  2. using MediaBrowser.Controller;
  3. using MediaBrowser.Controller.Entities;
  4. using MediaBrowser.Controller.Entities.Movies;
  5. using MediaBrowser.Controller.Library;
  6. using MediaBrowser.Controller.Providers;
  7. using MediaBrowser.Controller.Resolvers;
  8. using MediaBrowser.Model.Entities;
  9. using System;
  10. using System.Collections.Generic;
  11. using System.IO;
  12. using System.Linq;
  13. namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
  14. {
  15. /// <summary>
  16. /// Class MovieResolver
  17. /// </summary>
  18. public class MovieResolver : BaseVideoResolver<Video>
  19. {
  20. private readonly IServerApplicationPaths _applicationPaths;
  21. private readonly ILibraryManager _libraryManager;
  22. public MovieResolver(IServerApplicationPaths appPaths, ILibraryManager libraryManager)
  23. {
  24. _applicationPaths = appPaths;
  25. _libraryManager = libraryManager;
  26. }
  27. /// <summary>
  28. /// Gets the priority.
  29. /// </summary>
  30. /// <value>The priority.</value>
  31. public override ResolverPriority Priority
  32. {
  33. get
  34. {
  35. // Give plugins a chance to catch iso's first
  36. // Also since we have to loop through child files looking for videos,
  37. // see if we can avoid some of that by letting other resolvers claim folders first
  38. return ResolverPriority.Second;
  39. }
  40. }
  41. /// <summary>
  42. /// Resolves the specified args.
  43. /// </summary>
  44. /// <param name="args">The args.</param>
  45. /// <returns>Video.</returns>
  46. protected override Video Resolve(ItemResolveArgs args)
  47. {
  48. // Avoid expensive tests against VF's and all their children by not allowing this
  49. if (args.Parent != null)
  50. {
  51. if (args.Parent.IsRoot)
  52. {
  53. return null;
  54. }
  55. }
  56. var isDirectory = args.IsDirectory;
  57. if (isDirectory)
  58. {
  59. // Since the looping is expensive, this is an optimization to help us avoid it
  60. if (args.ContainsMetaFileByName("series.xml"))
  61. {
  62. return null;
  63. }
  64. }
  65. var collectionType = args.GetCollectionType();
  66. // Find movies with their own folders
  67. if (isDirectory)
  68. {
  69. if (string.Equals(collectionType, CollectionType.Trailers, StringComparison.OrdinalIgnoreCase))
  70. {
  71. return FindMovie<Trailer>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, false);
  72. }
  73. if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
  74. {
  75. return FindMovie<MusicVideo>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, false);
  76. }
  77. if (string.Equals(collectionType, CollectionType.AdultVideos, StringComparison.OrdinalIgnoreCase))
  78. {
  79. return FindMovie<AdultVideo>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, true);
  80. }
  81. if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
  82. {
  83. return FindMovie<Video>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, true);
  84. }
  85. if (string.IsNullOrEmpty(collectionType) ||
  86. string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase) ||
  87. string.Equals(collectionType, CollectionType.BoxSets, StringComparison.OrdinalIgnoreCase))
  88. {
  89. return FindMovie<Movie>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, true);
  90. }
  91. return null;
  92. }
  93. var filename = Path.GetFileName(args.Path);
  94. // Don't misidentify xbmc trailers as a movie
  95. if (filename.IndexOf(BaseItem.XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase) != -1)
  96. {
  97. return null;
  98. }
  99. // Find movies that are mixed in the same folder
  100. if (args.Path.IndexOf("[trailers]", StringComparison.OrdinalIgnoreCase) != -1 ||
  101. string.Equals(collectionType, CollectionType.Trailers, StringComparison.OrdinalIgnoreCase))
  102. {
  103. return ResolveVideo<Trailer>(args);
  104. }
  105. Video item = null;
  106. if (args.Path.IndexOf("[musicvideos]", StringComparison.OrdinalIgnoreCase) != -1 ||
  107. string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
  108. {
  109. item = ResolveVideo<MusicVideo>(args);
  110. }
  111. if (args.Path.IndexOf("[adultvideos]", StringComparison.OrdinalIgnoreCase) != -1 ||
  112. string.Equals(collectionType, CollectionType.AdultVideos, StringComparison.OrdinalIgnoreCase))
  113. {
  114. item = ResolveVideo<AdultVideo>(args);
  115. }
  116. // To find a movie file, the collection type must be movies or boxsets
  117. // Otherwise we'll consider it a plain video and let the video resolver handle it
  118. if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase) ||
  119. string.Equals(collectionType, CollectionType.BoxSets, StringComparison.OrdinalIgnoreCase))
  120. {
  121. item = ResolveVideo<Movie>(args);
  122. }
  123. if (item != null)
  124. {
  125. item.IsInMixedFolder = true;
  126. }
  127. return item;
  128. }
  129. /// <summary>
  130. /// Sets the initial item values.
  131. /// </summary>
  132. /// <param name="item">The item.</param>
  133. /// <param name="args">The args.</param>
  134. protected override void SetInitialItemValues(Video item, ItemResolveArgs args)
  135. {
  136. base.SetInitialItemValues(item, args);
  137. SetProviderIdFromPath(item);
  138. }
  139. /// <summary>
  140. /// Sets the provider id from path.
  141. /// </summary>
  142. /// <param name="item">The item.</param>
  143. private void SetProviderIdFromPath(Video item)
  144. {
  145. //we need to only look at the name of this actual item (not parents)
  146. var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path) : Path.GetFileName(item.ContainingFolderPath);
  147. var id = justName.GetAttributeValue("tmdbid");
  148. if (!string.IsNullOrEmpty(id))
  149. {
  150. item.SetProviderId(MetadataProviders.Tmdb, id);
  151. }
  152. }
  153. /// <summary>
  154. /// Finds a movie based on a child file system entries
  155. /// </summary>
  156. /// <typeparam name="T"></typeparam>
  157. /// <param name="path">The path.</param>
  158. /// <param name="parent">The parent.</param>
  159. /// <param name="fileSystemEntries">The file system entries.</param>
  160. /// <param name="directoryService">The directory service.</param>
  161. /// <param name="supportMultiFileItems">if set to <c>true</c> [support multi file items].</param>
  162. /// <returns>Movie.</returns>
  163. private T FindMovie<T>(string path, Folder parent, IEnumerable<FileSystemInfo> fileSystemEntries, IDirectoryService directoryService, bool supportMultiFileItems)
  164. where T : Video, new()
  165. {
  166. var movies = new List<T>();
  167. var multiDiscFolders = new List<FileSystemInfo>();
  168. // Loop through each child file/folder and see if we find a video
  169. foreach (var child in fileSystemEntries)
  170. {
  171. var filename = child.Name;
  172. if ((child.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
  173. {
  174. if (IsDvdDirectory(filename))
  175. {
  176. return new T
  177. {
  178. Path = path,
  179. VideoType = VideoType.Dvd
  180. };
  181. }
  182. if (IsBluRayDirectory(filename))
  183. {
  184. return new T
  185. {
  186. Path = path,
  187. VideoType = VideoType.BluRay
  188. };
  189. }
  190. if (EntityResolutionHelper.IsMultiPartFile(filename))
  191. {
  192. multiDiscFolders.Add(child);
  193. }
  194. continue;
  195. }
  196. // Don't misidentify xbmc trailers as a movie
  197. if (filename.IndexOf(BaseItem.XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase) != -1)
  198. {
  199. continue;
  200. }
  201. var childArgs = new ItemResolveArgs(_applicationPaths, _libraryManager, directoryService)
  202. {
  203. FileInfo = child,
  204. Path = child.FullName,
  205. Parent = parent
  206. };
  207. var item = ResolveVideo<T>(childArgs);
  208. if (item != null)
  209. {
  210. item.IsInMixedFolder = false;
  211. movies.Add(item);
  212. }
  213. }
  214. if (movies.Count > 1 && supportMultiFileItems)
  215. {
  216. return GetMultiFileMovie(movies);
  217. }
  218. if (movies.Count == 1)
  219. {
  220. return movies[0];
  221. }
  222. if (multiDiscFolders.Count > 0)
  223. {
  224. var folders = fileSystemEntries.Where(child => (child.Attributes & FileAttributes.Directory) == FileAttributes.Directory);
  225. return GetMultiDiscMovie<T>(multiDiscFolders, folders);
  226. }
  227. return null;
  228. }
  229. /// <summary>
  230. /// Gets the multi disc movie.
  231. /// </summary>
  232. /// <typeparam name="T"></typeparam>
  233. /// <param name="multiDiscFolders">The folders.</param>
  234. /// <param name="allFolders">All folders.</param>
  235. /// <returns>``0.</returns>
  236. private T GetMultiDiscMovie<T>(List<FileSystemInfo> multiDiscFolders, IEnumerable<FileSystemInfo> allFolders)
  237. where T : Video, new()
  238. {
  239. var videoTypes = new List<VideoType>();
  240. var folderPaths = multiDiscFolders.Select(i => i.FullName).Where(i =>
  241. {
  242. var subfolders = Directory.GetDirectories(i).Select(Path.GetFileName).ToList();
  243. if (subfolders.Any(IsDvdDirectory))
  244. {
  245. videoTypes.Add(VideoType.Dvd);
  246. return true;
  247. }
  248. if (subfolders.Any(IsBluRayDirectory))
  249. {
  250. videoTypes.Add(VideoType.BluRay);
  251. return true;
  252. }
  253. return false;
  254. }).OrderBy(i => i).ToList();
  255. // If different video types were found, don't allow this
  256. if (videoTypes.Count > 0 && videoTypes.Any(i => i != videoTypes[0]))
  257. {
  258. return null;
  259. }
  260. if (folderPaths.Count == 0)
  261. {
  262. return null;
  263. }
  264. // If there are other folders side by side that are folder rips, don't allow it
  265. // TODO: Improve this to return null if any folder is present aside from our regularly ignored folders
  266. if (allFolders.Except(multiDiscFolders).Any(i =>
  267. {
  268. var subfolders = Directory.GetDirectories(i.FullName).Select(Path.GetFileName).ToList();
  269. if (subfolders.Any(IsDvdDirectory))
  270. {
  271. return true;
  272. }
  273. if (subfolders.Any(IsBluRayDirectory))
  274. {
  275. return true;
  276. }
  277. return false;
  278. }))
  279. {
  280. return null;
  281. }
  282. return new T
  283. {
  284. Path = folderPaths[0],
  285. IsMultiPart = true,
  286. VideoType = videoTypes[0]
  287. };
  288. }
  289. /// <summary>
  290. /// Gets the multi file movie.
  291. /// </summary>
  292. /// <typeparam name="T"></typeparam>
  293. /// <param name="movies">The movies.</param>
  294. /// <returns>``0.</returns>
  295. private T GetMultiFileMovie<T>(IEnumerable<T> movies)
  296. where T : Video, new()
  297. {
  298. var sortedMovies = movies.OrderBy(i => i.Path).ToList();
  299. var firstMovie = sortedMovies[0];
  300. // They must all be part of the sequence if we're going to consider it a multi-part movie
  301. // Only support up to 8 (matches Plex), to help avoid incorrect detection
  302. if (sortedMovies.All(i => EntityResolutionHelper.IsMultiPartFile(i.Path)) && sortedMovies.Count <= 8)
  303. {
  304. firstMovie.IsMultiPart = true;
  305. return firstMovie;
  306. }
  307. return null;
  308. }
  309. /// <summary>
  310. /// Determines whether [is DVD directory] [the specified directory name].
  311. /// </summary>
  312. /// <param name="directoryName">Name of the directory.</param>
  313. /// <returns><c>true</c> if [is DVD directory] [the specified directory name]; otherwise, <c>false</c>.</returns>
  314. private bool IsDvdDirectory(string directoryName)
  315. {
  316. return string.Equals(directoryName, "video_ts", StringComparison.OrdinalIgnoreCase);
  317. }
  318. /// <summary>
  319. /// Determines whether [is blu ray directory] [the specified directory name].
  320. /// </summary>
  321. /// <param name="directoryName">Name of the directory.</param>
  322. /// <returns><c>true</c> if [is blu ray directory] [the specified directory name]; otherwise, <c>false</c>.</returns>
  323. private bool IsBluRayDirectory(string directoryName)
  324. {
  325. return string.Equals(directoryName, "bdmv", StringComparison.OrdinalIgnoreCase);
  326. }
  327. }
  328. }