MovieResolver.cs 15 KB

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