using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; using Emby.Naming.Common; using MediaBrowser.Model.IO; namespace Emby.Naming.Video { public class VideoListResolver { private readonly NamingOptions _options; public VideoListResolver(NamingOptions options) { _options = options; } public IEnumerable Resolve(List files, bool supportMultiVersion = true) { var videoResolver = new VideoResolver(_options); var videoInfos = files .Select(i => videoResolver.Resolve(i.FullName, i.IsDirectory)) .Where(i => i != null) .ToList(); // Filter out all extras, otherwise they could cause stacks to not be resolved // See the unit test TestStackedWithTrailer var nonExtras = videoInfos .Where(i => string.IsNullOrEmpty(i.ExtraType)) .Select(i => new FileSystemMetadata { FullName = i.Path, IsDirectory = i.IsDirectory }); var stackResult = new StackResolver(_options) .Resolve(nonExtras); var remainingFiles = videoInfos .Where(i => !stackResult.Stacks.Any(s => s.ContainsFile(i.Path, i.IsDirectory))) .ToList(); var list = new List(); foreach (var stack in stackResult.Stacks) { var info = new VideoInfo { Files = stack.Files.Select(i => videoResolver.Resolve(i, stack.IsDirectoryStack)).ToList(), Name = stack.Name }; info.Year = info.Files.First().Year; var extraBaseNames = new List { stack.Name, Path.GetFileNameWithoutExtension(stack.Files[0]) }; var extras = GetExtras(remainingFiles, extraBaseNames); if (extras.Count > 0) { remainingFiles = remainingFiles .Except(extras) .ToList(); info.Extras = extras; } list.Add(info); } var standaloneMedia = remainingFiles .Where(i => string.IsNullOrEmpty(i.ExtraType)) .ToList(); foreach (var media in standaloneMedia) { var info = new VideoInfo { Files = new List { media }, Name = media.Name }; info.Year = info.Files.First().Year; var extras = GetExtras(remainingFiles, new List { media.FileNameWithoutExtension }); remainingFiles = remainingFiles .Except(extras.Concat(new[] { media })) .ToList(); info.Extras = extras; list.Add(info); } if (supportMultiVersion) { list = GetVideosGroupedByVersion(list) .ToList(); } // If there's only one resolved video, use the folder name as well to find extras if (list.Count == 1) { var info = list[0]; var videoPath = list[0].Files[0].Path; var parentPath = Path.GetDirectoryName(videoPath); if (!string.IsNullOrEmpty(parentPath)) { var folderName = Path.GetFileName(Path.GetDirectoryName(videoPath)); if (!string.IsNullOrEmpty(folderName)) { var extras = GetExtras(remainingFiles, new List { folderName }); remainingFiles = remainingFiles .Except(extras) .ToList(); info.Extras.AddRange(extras); } } // Add the extras that are just based on file name as well var extrasByFileName = remainingFiles .Where(i => i.ExtraRule != null && i.ExtraRule.RuleType == ExtraRuleType.Filename) .ToList(); remainingFiles = remainingFiles .Except(extrasByFileName) .ToList(); info.Extras.AddRange(extrasByFileName); } // If there's only one video, accept all trailers // Be lenient because people use all kinds of mish mash conventions with trailers if (list.Count == 1) { var trailers = remainingFiles .Where(i => string.Equals(i.ExtraType, "trailer", StringComparison.OrdinalIgnoreCase)) .ToList(); list[0].Extras.AddRange(trailers); remainingFiles = remainingFiles .Except(trailers) .ToList(); } // Whatever files are left, just add them list.AddRange(remainingFiles.Select(i => new VideoInfo { Files = new List { i }, Name = i.Name, Year = i.Year })); var orderedList = list.OrderBy(i => i.Name); return orderedList; } private IEnumerable GetVideosGroupedByVersion(List videos) { if (videos.Count == 0) { return videos; } var list = new List(); var folderName = Path.GetFileName(Path.GetDirectoryName(videos[0].Files[0].Path)); if (!string.IsNullOrEmpty(folderName) && folderName.Length > 1) { if (videos.All(i => i.Files.Count == 1 && IsEligibleForMultiVersion(folderName, i.Files[0].Path))) { if (HaveSameYear(videos)) { var ordered = videos.OrderBy(i => i.Name).ToList(); list.Add(ordered[0]); list[0].AlternateVersions = ordered.Skip(1).Select(i => i.Files[0]).ToList(); list[0].Name = folderName; list[0].Extras.AddRange(ordered.Skip(1).SelectMany(i => i.Extras)); return list; } } } return videos; } private bool HaveSameYear(List videos) { return videos.Select(i => i.Year ?? -1).Distinct().Count() < 2; } private bool IsEligibleForMultiVersion(string folderName, string testFilename) { testFilename = Path.GetFileNameWithoutExtension(testFilename) ?? string.Empty; if (testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase)) { testFilename = testFilename.Substring(folderName.Length).Trim(); return string.IsNullOrEmpty(testFilename) || testFilename.StartsWith("-") || string.IsNullOrEmpty(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty)) ; } return false; } private List GetExtras(IEnumerable remainingFiles, List baseNames) { foreach (var name in baseNames.ToList()) { var trimmedName = name.TrimEnd().TrimEnd(_options.VideoFlagDelimiters).TrimEnd(); baseNames.Add(trimmedName); } return remainingFiles .Where(i => !string.IsNullOrEmpty(i.ExtraType)) .Where(i => baseNames.Any(b => i.FileNameWithoutExtension.StartsWith(b, StringComparison.OrdinalIgnoreCase))) .ToList(); } } }