Added multi-disc movie support

This commit is contained in:
Luke Pulverenti 2013-06-16 15:02:57 -04:00
parent e231bd4d32
commit c5b00dec8e
6 changed files with 152 additions and 66 deletions

View File

@ -30,7 +30,7 @@ namespace MediaBrowser.Controller.Entities.Movies
{ {
get get
{ {
return VideoType == VideoType.VideoFile || VideoType == VideoType.Iso ? System.IO.Path.GetDirectoryName(Path) : Path; return VideoType == VideoType.VideoFile || VideoType == VideoType.Iso || IsMultiPart ? System.IO.Path.GetDirectoryName(Path) : Path;
} }
} }
@ -51,7 +51,7 @@ namespace MediaBrowser.Controller.Entities.Movies
{ {
get get
{ {
return VideoType == VideoType.VideoFile || VideoType == VideoType.Iso; return VideoType == VideoType.VideoFile || VideoType == VideoType.Iso || IsMultiPart;
} }
} }

View File

@ -14,7 +14,7 @@ namespace MediaBrowser.Controller.Entities
{ {
get get
{ {
return VideoType == VideoType.VideoFile || VideoType == VideoType.Iso ? System.IO.Path.GetDirectoryName(Path) : Path; return VideoType == VideoType.VideoFile || VideoType == VideoType.Iso || IsMultiPart ? System.IO.Path.GetDirectoryName(Path) : Path;
} }
} }
@ -35,7 +35,7 @@ namespace MediaBrowser.Controller.Entities
{ {
get get
{ {
return VideoType == VideoType.VideoFile || VideoType == VideoType.Iso; return VideoType == VideoType.VideoFile || VideoType == VideoType.Iso || IsMultiPart;
} }
} }
} }

View File

@ -86,7 +86,7 @@ namespace MediaBrowser.Controller.Entities
var allFiles = Directory.EnumerateFiles(rootPath, "*", SearchOption.AllDirectories).ToList(); var allFiles = Directory.EnumerateFiles(rootPath, "*", SearchOption.AllDirectories).ToList();
return PlayableStreamFileNames.Select(name => allFiles.FirstOrDefault(f => string.Equals(System.IO.Path.GetFileName(f), name, System.StringComparison.OrdinalIgnoreCase))) return PlayableStreamFileNames.Select(name => allFiles.FirstOrDefault(f => string.Equals(System.IO.Path.GetFileName(f), name, StringComparison.OrdinalIgnoreCase)))
.Where(f => !string.IsNullOrEmpty(f)) .Where(f => !string.IsNullOrEmpty(f))
.ToList(); .ToList();
} }
@ -176,6 +176,16 @@ namespace MediaBrowser.Controller.Entities
return new List<Video>(); return new List<Video>();
} }
IEnumerable<FileSystemInfo> files;
if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd)
{
files = new DirectoryInfo(System.IO.Path.GetDirectoryName(Path))
.EnumerateDirectories()
.Where(i => !string.Equals(i.FullName, Path, StringComparison.OrdinalIgnoreCase) && EntityResolutionHelper.IsMultiPartFile(i.Name));
}
else
{
ItemResolveArgs resolveArgs; ItemResolveArgs resolveArgs;
try try
@ -188,20 +198,16 @@ namespace MediaBrowser.Controller.Entities
return new List<Video>(); return new List<Video>();
} }
if (!resolveArgs.IsDirectory) files = resolveArgs.FileSystemChildren.Where(i =>
{
return new List<Video>();
}
var files = resolveArgs.FileSystemChildren.Where(i =>
{ {
if ((i.Attributes & FileAttributes.Directory) == FileAttributes.Directory) if ((i.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
{ {
return false; return false;
} }
return !string.Equals(i.FullName, Path, StringComparison.OrdinalIgnoreCase) && EntityResolutionHelper.IsVideoFile(i.FullName) && EntityResolutionHelper.IsMultiPartFile(i.FullName); return !string.Equals(i.FullName, Path, StringComparison.OrdinalIgnoreCase) && EntityResolutionHelper.IsVideoFile(i.FullName) && EntityResolutionHelper.IsMultiPartFile(i.Name);
}); });
}
return LibraryManager.ResolvePaths<Video>(files, null).Select(video => return LibraryManager.ResolvePaths<Video>(files, null).Select(video =>
{ {

View File

@ -48,7 +48,11 @@ namespace MediaBrowser.Controller.Resolvers
private static readonly Regex MultiFileRegex = new Regex( private static readonly Regex MultiFileRegex = new Regex(
@"(.*?)([ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck]|d)[ _.-]*[0-9]+)(.*?)(\.[^.]+)$", @"(.*?)([ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck]|d)[ _.-]*[0-9]+)(.*?)(\.[^.]+)$",
RegexOptions.Compiled); RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex MultiFolderRegex = new Regex(
@"(.*?)([ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck]|d)[ _.-]*[0-9]+)$",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary> /// <summary>
/// Determines whether [is multi part file] [the specified path]. /// Determines whether [is multi part file] [the specified path].
@ -57,7 +61,7 @@ namespace MediaBrowser.Controller.Resolvers
/// <returns><c>true</c> if [is multi part file] [the specified path]; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if [is multi part file] [the specified path]; otherwise, <c>false</c>.</returns>
public static bool IsMultiPartFile(string path) public static bool IsMultiPartFile(string path)
{ {
return MultiFileRegex.Match(path).Success; return MultiFileRegex.Match(path).Success || MultiFolderRegex.Match(path).Success;
} }
/// <summary> /// <summary>

View File

@ -50,7 +50,9 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
if (args.IsDirectory) if (args.IsDirectory)
{ {
// Avoid expensive tests against VF's and all their children by not allowing this // Avoid expensive tests against VF's and all their children by not allowing this
if (args.Parent == null || args.Parent.IsRoot) if (args.Parent != null)
{
if (args.Parent.IsRoot)
{ {
return null; return null;
} }
@ -63,11 +65,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
return null; return null;
} }
} }
// Optimization to avoid running all these tests against Top folders
if (args.Parent != null && args.Parent.IsRoot)
{
return null;
} }
// Since the looping is expensive, this is an optimization to help us avoid it // Since the looping is expensive, this is an optimization to help us avoid it
@ -76,16 +73,20 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
return null; return null;
} }
// A shortcut to help us resolve faster in some cases
var isKnownMovie = args.ContainsMetaFileByName("movie.xml") || args.ContainsMetaFileByName("tmdb3.json") ||
args.Path.IndexOf("[tmdbid", StringComparison.OrdinalIgnoreCase) != -1;
if (args.Path.IndexOf("[trailers]", StringComparison.OrdinalIgnoreCase) != -1) if (args.Path.IndexOf("[trailers]", StringComparison.OrdinalIgnoreCase) != -1)
{ {
return FindMovie<Trailer>(args); return FindMovie<Trailer>(args.Path, args.FileSystemChildren, isKnownMovie);
} }
if (args.Path.IndexOf("[musicvideos]", StringComparison.OrdinalIgnoreCase) != -1) if (args.Path.IndexOf("[musicvideos]", StringComparison.OrdinalIgnoreCase) != -1)
{ {
return FindMovie<MusicVideo>(args); return FindMovie<MusicVideo>(args.Path, args.FileSystemChildren, isKnownMovie);
} }
return FindMovie<Movie>(args); return FindMovie<Movie>(args.Path, args.FileSystemChildren, isKnownMovie);
} }
return null; return null;
@ -123,18 +124,20 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
/// <summary> /// <summary>
/// Finds a movie based on a child file system entries /// Finds a movie based on a child file system entries
/// </summary> /// </summary>
/// <param name="args">The args.</param> /// <typeparam name="T"></typeparam>
/// <param name="path">The path.</param>
/// <param name="fileSystemEntries">The file system entries.</param>
/// <param name="isKnownMovie">if set to <c>true</c> [is known movie].</param>
/// <returns>Movie.</returns> /// <returns>Movie.</returns>
private T FindMovie<T>(ItemResolveArgs args) private T FindMovie<T>(string path, IEnumerable<FileSystemInfo> fileSystemEntries, bool isKnownMovie)
where T : Video, new() where T : Video, new()
{ {
// Optimization to avoid having to resolve every file
bool? isKnownMovie = null;
var movies = new List<T>(); var movies = new List<T>();
var multiDiscFolders = new List<FileSystemInfo>();
// Loop through each child file/folder and see if we find a video // Loop through each child file/folder and see if we find a video
foreach (var child in args.FileSystemChildren) foreach (var child in fileSystemEntries)
{ {
if ((child.Attributes & FileAttributes.Directory) == FileAttributes.Directory) if ((child.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
{ {
@ -142,7 +145,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
{ {
return new T return new T
{ {
Path = args.Path, Path = path,
VideoType = VideoType.Dvd VideoType = VideoType.Dvd
}; };
} }
@ -150,17 +153,14 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
{ {
return new T return new T
{ {
Path = args.Path, Path = path,
VideoType = VideoType.BluRay VideoType = VideoType.BluRay
}; };
} }
if (IsHdDvdDirectory(child.Name))
if (EntityResolutionHelper.IsMultiPartFile(child.Name))
{ {
return new T multiDiscFolders.Add(child);
{
Path = args.Path,
VideoType = VideoType.HdDvd
};
} }
continue; continue;
@ -183,12 +183,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
if (item != null) if (item != null)
{ {
// If we already know it's a movie, we can stop looping // If we already know it's a movie, we can stop looping
if (!isKnownMovie.HasValue) if (isKnownMovie)
{
isKnownMovie = args.ContainsMetaFileByName("movie.xml") || args.ContainsMetaFileByName("tmdb3.json") || args.Path.IndexOf("[tmdbid", StringComparison.OrdinalIgnoreCase) != -1;
}
if (isKnownMovie.Value)
{ {
return item; return item;
} }
@ -202,9 +197,63 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
return GetMultiFileMovie(movies); return GetMultiFileMovie(movies);
} }
return movies.Count == 1 ? movies[0] : null; if (movies.Count == 1)
{
return movies[0];
} }
if (multiDiscFolders.Count > 0)
{
return GetMultiDiscMovie<T>(multiDiscFolders);
}
return null;
}
/// <summary>
/// Gets the multi disc movie.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="folders">The folders.</param>
/// <returns>``0.</returns>
private T GetMultiDiscMovie<T>(List<FileSystemInfo> folders)
where T : Video, new()
{
var videoType = VideoType.BluRay;
folders = folders.Where(i =>
{
var subfolders = Directory.GetDirectories(i.FullName).Select(Path.GetFileName).ToList();
if (subfolders.Any(IsDvdDirectory))
{
videoType = VideoType.Dvd;
return true;
}
if (subfolders.Any(IsBluRayDirectory))
{
videoType = VideoType.BluRay;
return true;
}
return false;
}).OrderBy(i => i.FullName).ToList();
if (folders.Count == 0)
{
return null;
}
return new T
{
Path = folders[0].FullName,
IsMultiPart = true,
VideoType = videoType
};
}
/// <summary> /// <summary>
/// Gets the multi file movie. /// Gets the multi file movie.
@ -216,7 +265,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
where T : Video, new() where T : Video, new()
{ {
var multiPartMovies = movies.OrderBy(i => i.Path) var multiPartMovies = movies.OrderBy(i => i.Path)
.Where(i => EntityResolutionHelper.IsMultiPartFile(i.Path)) .Where(i => EntityResolutionHelper.IsMultiPartFile(i.Name))
.ToList(); .ToList();
// They must all be part of the sequence // They must all be part of the sequence

View File

@ -25,6 +25,33 @@ namespace MediaBrowser.Tests.Resolvers
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - pt 1.mkv")); Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - pt 1.mkv"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - part 1.mkv")); Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - part 1.mkv"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - dvd 1.mkv")); Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - dvd 1.mkv"));
// Not case sensitive
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - Disc1.mkv"));
}
[TestMethod]
public void TestMultiPartFolders()
{
Assert.IsFalse(EntityResolutionHelper.IsMultiPartFile(@"blah blah"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - cd1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - disc1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - disk1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - pt1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - part1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - dvd1"));
// Add a space
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - cd 1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - disc 1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - disk 1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - pt 1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - part 1"));
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - dvd 1"));
// Not case sensitive
Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - Disc1"));
} }
} }
} }