/// <summary> /// Determines whether [is series folder] [the specified path]. /// </summary> /// <param name="path">The path.</param> /// <param name="considerSeasonlessEntries">if set to <c>true</c> [consider seasonless entries].</param> /// <param name="fileSystemChildren">The file system children.</param> /// <param name="directoryService">The directory service.</param> /// <param name="fileSystem">The file system.</param> /// <returns><c>true</c> if [is series folder] [the specified path]; otherwise, <c>false</c>.</returns> public static bool IsSeriesFolder(string path, bool considerSeasonlessEntries, IEnumerable <FileSystemInfo> fileSystemChildren, IDirectoryService directoryService, IFileSystem fileSystem, ILogger logger) { // A folder with more than 3 non-season folders in will not becounted as a series var nonSeriesFolders = 0; foreach (var child in fileSystemChildren) { var attributes = child.Attributes; if ((attributes & FileAttributes.Hidden) == FileAttributes.Hidden) { //logger.Debug("Igoring series file or folder marked hidden: {0}", child.FullName); continue; } // Can't enforce this because files saved by Bitcasa are always marked System //if ((attributes & FileAttributes.System) == FileAttributes.System) //{ // logger.Debug("Igoring series subfolder marked system: {0}", child.FullName); // continue; //} if ((attributes & FileAttributes.Directory) == FileAttributes.Directory) { if (IsSeasonFolder(child.FullName, directoryService, fileSystem)) { logger.Debug("{0} is a series because of season folder {1}.", path, child.FullName); return(true); } if (IsBadFolder(child.Name)) { logger.Debug("Invalid folder under series: {0}", child.FullName); nonSeriesFolders++; } if (nonSeriesFolders >= 3) { logger.Debug("{0} not a series due to 3 or more invalid folders.", path); return(false); } } else { var fullName = child.FullName; if (EntityResolutionHelper.IsVideoFile(fullName) || EntityResolutionHelper.IsVideoPlaceHolder(fullName)) { if (GetEpisodeNumberFromFile(fullName, considerSeasonlessEntries).HasValue) { return(true); } } } } logger.Debug("{0} is not a series folder.", path); return(false); }
/// <summary> /// Determine if the supplied list contains what we should consider music /// </summary> /// <param name="list">The list.</param> /// <returns><c>true</c> if the specified list contains music; otherwise, <c>false</c>.</returns> public static bool ContainsMusic(IEnumerable <FileSystemInfo> list) { // If list contains at least 2 audio files or at least one and no video files consider it to contain music var foundAudio = 0; foreach (var file in list) { var fullName = file.FullName; if (EntityResolutionHelper.IsAudioFile(fullName)) { foundAudio++; } if (foundAudio >= 2) { return(true); } if (EntityResolutionHelper.IsVideoFile(fullName)) { return(false); } if (EntityResolutionHelper.IsVideoPlaceHolder(fullName)) { return(false); } } // or a single audio file and no video files return(foundAudio > 0); }
public void TestMultiPartFiles() { Assert.IsFalse(EntityResolutionHelper.IsMultiPartFile(@"Braveheart.mkv")); Assert.IsFalse(EntityResolutionHelper.IsMultiPartFile(@"Braveheart - 480p.mkv")); Assert.IsFalse(EntityResolutionHelper.IsMultiPartFile(@"Braveheart - 720p.mkv")); Assert.IsFalse(EntityResolutionHelper.IsMultiPartFile(@"blah blah.mkv")); Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - cd1.mkv")); Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - disc1.mkv")); Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - disk1.mkv")); Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - pt1.mkv")); Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - part1.mkv")); Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - dvd1.mkv")); // Add a space Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - cd 1.mkv")); Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - disc 1.mkv")); Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - disk 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 - dvd 1.mkv")); // Not case sensitive Assert.IsTrue(EntityResolutionHelper.IsMultiPartFile(@"blah blah - Disc1.mkv")); }
/// <summary> /// Determine if the supplied file data points to a music album /// </summary> /// <param name="path">The path.</param> /// <param name="directoryService">The directory service.</param> /// <returns><c>true</c> if [is music album] [the specified data]; otherwise, <c>false</c>.</returns> public static bool IsMusicAlbum(string path, IDirectoryService directoryService) { // If list contains at least 2 audio files or at least one and no video files consider it to contain music var foundAudio = 0; foreach (var file in directoryService.GetFiles(path)) { var fullName = file.FullName; if (EntityResolutionHelper.IsAudioFile(fullName)) { // Don't resolve these into audio files if (string.Equals(Path.GetFileNameWithoutExtension(fullName), BaseItem.ThemeSongFilename) && EntityResolutionHelper.IsAudioFile(fullName)) { continue; } foundAudio++; } if (foundAudio >= 2) { return(true); } if (EntityResolutionHelper.IsVideoFile(fullName)) { return(false); } } // or a single audio file and no video files return(foundAudio > 0); }
/// <summary> /// Determine if the supplied list contains what we should consider music /// </summary> /// <param name="list">The list.</param> /// <returns><c>true</c> if the specified list contains music; otherwise, <c>false</c>.</returns> public static bool ContainsMusic(IEnumerable <WIN32_FIND_DATA> list) { // If list contains at least 2 audio files or at least one and no video files consider it to contain music var foundAudio = 0; var foundVideo = 0; foreach (var file in list) { if (AudioResolver.IsAudioFile(file)) { foundAudio++; } if (foundAudio >= 2) { return(true); } if (EntityResolutionHelper.IsVideoFile(file.Path)) { foundVideo++; } } // or a single audio file and no video files if (foundAudio > 0 && foundVideo == 0) { return(true); } return(false); }
/// <summary> /// Downloads the trailer for item. /// </summary> /// <param name="item">The item.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> public async Task DownloadTrailerForItem(BaseItem item, CancellationToken cancellationToken) { var url = await GetTrailerUrl(item, cancellationToken).ConfigureAwait(false); if (string.IsNullOrEmpty(url)) { return; } var responseInfo = await _httpClient.GetTempFileResponse(new HttpRequestOptions { Url = url, CancellationToken = cancellationToken, Progress = new Progress <double>(), UserAgent = GetUserAgent(url) }); var extension = responseInfo.ContentType.Split('/').Last(); if (string.Equals("quicktime", extension, StringComparison.OrdinalIgnoreCase)) { extension = "mov"; } var savePath = Directory.Exists(item.Path) ? Path.Combine(item.Path, Path.GetFileNameWithoutExtension(item.Path) + "-trailer." + extension) : Path.Combine(Path.GetDirectoryName(item.Path), Path.GetFileNameWithoutExtension(item.Path) + "-trailer." + extension); if (!EntityResolutionHelper.IsVideoFile(savePath)) { _logger.Warn("Unrecognized video extension {0}", savePath); return; } _directoryWatchers.TemporarilyIgnore(savePath); _logger.Info("Moving {0} to {1}", responseInfo.TempFilePath, savePath); try { var parentPath = Path.GetDirectoryName(savePath); if (!Directory.Exists(parentPath)) { Directory.CreateDirectory(parentPath); } File.Move(responseInfo.TempFilePath, savePath); } finally { _directoryWatchers.RemoveTempIgnore(savePath); } await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); }
/// <summary> /// Determine if the supplied list contains what we should consider music /// </summary> /// <param name="list">The list.</param> /// <param name="isMusicMediaFolder">if set to <c>true</c> [is music media folder].</param> /// <param name="allowSubfolders">if set to <c>true</c> [allow subfolders].</param> /// <param name="directoryService">The directory service.</param> /// <param name="logger">The logger.</param> /// <param name="fileSystem">The file system.</param> /// <returns><c>true</c> if the specified list contains music; otherwise, <c>false</c>.</returns> private static bool ContainsMusic(IEnumerable <FileSystemInfo> list, bool isMusicMediaFolder, bool allowSubfolders, IDirectoryService directoryService, ILogger logger, IFileSystem fileSystem) { // If list contains at least 2 audio files or at least one and no video files consider it to contain music var foundAudio = 0; var discSubfolderCount = 0; foreach (var fileSystemInfo in list) { if ((fileSystemInfo.Attributes & FileAttributes.Directory) == FileAttributes.Directory) { if (isMusicMediaFolder && allowSubfolders && IsAlbumSubfolder(fileSystemInfo, true, directoryService, logger, fileSystem)) { discSubfolderCount++; } if (!IsAdditionalSubfolderAllowed(fileSystemInfo)) { return(false); } } var fullName = fileSystemInfo.FullName; if (EntityResolutionHelper.IsAudioFile(fullName)) { // Don't resolve these into audio files if (string.Equals(fileSystem.GetFileNameWithoutExtension(fullName), BaseItem.ThemeSongFilename)) { continue; } foundAudio++; } else if (EntityResolutionHelper.IsVideoFile(fullName)) { return(false); } else if (EntityResolutionHelper.IsVideoPlaceHolder(fullName)) { return(false); } if (foundAudio >= 2) { return(true); } } // or a single audio file and no video files return(foundAudio > 0 || discSubfolderCount > 0); }
/// <summary> /// Resolves the specified args. /// </summary> /// <param name="args">The args.</param> /// <returns>Entities.Audio.Audio.</returns> protected override Controller.Entities.Audio.Audio Resolve(ItemResolveArgs args) { // Return audio if the path is a file and has a matching extension if (!args.IsDirectory) { if (EntityResolutionHelper.IsAudioFile(args.Path)) { return(new Controller.Entities.Audio.Audio()); } } return(null); }
private List <string> GetOtherDuplicatePaths(string targetPath, Series series, int seasonNumber, int episodeNumber, int?endingEpisodeNumber) { var episodePaths = series.RecursiveChildren .OfType <Episode>() .Where(i => { var locationType = i.LocationType; // Must be file system based and match exactly if (locationType != LocationType.Remote && locationType != LocationType.Virtual && i.ParentIndexNumber.HasValue && i.ParentIndexNumber.Value == seasonNumber && i.IndexNumber.HasValue && i.IndexNumber.Value == episodeNumber) { if (endingEpisodeNumber.HasValue || i.IndexNumberEnd.HasValue) { return(endingEpisodeNumber.HasValue && i.IndexNumberEnd.HasValue && endingEpisodeNumber.Value == i.IndexNumberEnd.Value); } return(true); } return(false); }) .Select(i => i.Path) .ToList(); var folder = Path.GetDirectoryName(targetPath); var targetFileNameWithoutExtension = Path.GetFileNameWithoutExtension(targetPath); try { var filesOfOtherExtensions = Directory.EnumerateFiles(folder, "*", SearchOption.TopDirectoryOnly) .Where(i => EntityResolutionHelper.IsVideoFile(i) && string.Equals(Path.GetFileNameWithoutExtension(i), targetFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)); episodePaths.AddRange(filesOfOtherExtensions); } catch (DirectoryNotFoundException) { // No big deal. Maybe the season folder doesn't already exist. } return(episodePaths.Where(i => !string.Equals(i, targetPath, StringComparison.OrdinalIgnoreCase)) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList()); }
/// <summary> /// Determines whether [is series folder] [the specified path]. /// </summary> /// <param name="path">The path.</param> /// <param name="fileSystemChildren">The file system children.</param> /// <returns><c>true</c> if [is series folder] [the specified path]; otherwise, <c>false</c>.</returns> public static bool IsSeriesFolder(string path, IEnumerable <FileSystemInfo> fileSystemChildren, IDirectoryService directoryService) { // A folder with more than 3 non-season folders in will not becounted as a series var nonSeriesFolders = 0; foreach (var child in fileSystemChildren) { var attributes = child.Attributes; if ((attributes & FileAttributes.Hidden) == FileAttributes.Hidden) { continue; } if ((attributes & FileAttributes.System) == FileAttributes.System) { continue; } if ((attributes & FileAttributes.Directory) == FileAttributes.Directory) { if (IsSeasonFolder(child.FullName, directoryService)) { return(true); } nonSeriesFolders++; if (nonSeriesFolders >= 3) { return(false); } } else { var fullName = child.FullName; if (EntityResolutionHelper.IsVideoFile(fullName) || EntityResolutionHelper.IsVideoPlaceHolder(fullName)) { if (GetEpisodeNumberFromFile(fullName, false).HasValue) { return(true); } } } } return(false); }
public IEnumerable <MediaSourceInfo> GetCachedChannelItemMediaSources(IChannelMediaItem item) { var filenamePrefix = item.Id.ToString("N"); var parentPath = Path.Combine(ChannelDownloadPath, item.ChannelId); try { var files = new DirectoryInfo(parentPath).EnumerateFiles("*", SearchOption.TopDirectoryOnly); if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) { files = files.Where(i => EntityResolutionHelper.IsVideoFile(i.FullName)); } else { files = files.Where(i => EntityResolutionHelper.IsAudioFile(i.FullName)); } var file = files .FirstOrDefault(i => i.Name.StartsWith(filenamePrefix, StringComparison.OrdinalIgnoreCase)); if (file != null) { var cachedItem = _libraryManager.ResolvePath(file); if (cachedItem != null) { var hasMediaSources = _libraryManager.GetItemById(cachedItem.Id) as IHasMediaSources; if (hasMediaSources != null) { var source = hasMediaSources.GetMediaSources(true).FirstOrDefault(); if (source != null) { source.Type = MediaSourceType.Cache; return(new[] { source }); } } } } } catch (DirectoryNotFoundException) { } return(new List <MediaSourceInfo>()); }
/// <summary> /// Creates ResolveArgs on demand /// </summary> /// <param name="pathInfo">The path info.</param> /// <returns>ItemResolveArgs.</returns> /// <exception cref="System.IO.IOException">Unable to retrieve file system info for + path</exception> protected internal virtual ItemResolveArgs CreateResolveArgs(WIN32_FIND_DATA?pathInfo = null) { var path = Path; // non file-system entries will not have a path if (this.LocationType != LocationType.FileSystem || string.IsNullOrEmpty(path)) { return(new ItemResolveArgs(ConfigurationManager.ApplicationPaths) { FileInfo = new WIN32_FIND_DATA() }); } if (UseParentPathToCreateResolveArgs) { path = System.IO.Path.GetDirectoryName(path); } pathInfo = pathInfo ?? FileSystem.GetFileData(path); if (!pathInfo.HasValue) { throw new IOException("Unable to retrieve file system info for " + path); } var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths) { FileInfo = pathInfo.Value, Path = path, Parent = Parent }; // Gather child folder and files if (args.IsDirectory) { // When resolving the root, we need it's grandchildren (children of user views) var flattenFolderDepth = args.IsPhysicalRoot ? 2 : 0; args.FileSystemDictionary = FileData.GetFilteredFileSystemEntries(args.Path, Logger, flattenFolderDepth: flattenFolderDepth, args: args); } //update our dates EntityResolutionHelper.EnsureDates(this, args); return(args); }
/// <summary> /// Determines whether [is season folder] [the specified path]. /// </summary> /// <param name="path">The path.</param> /// <param name="directoryService">The directory service.</param> /// <param name="fileSystem">The file system.</param> /// <returns><c>true</c> if [is season folder] [the specified path]; otherwise, <c>false</c>.</returns> private static bool IsSeasonFolder(string path, IDirectoryService directoryService, IFileSystem fileSystem) { var seasonNumber = GetSeasonNumberFromPath(path); var hasSeasonNumber = seasonNumber != null; if (!hasSeasonNumber) { return(false); } // It's a season folder if it's named as such and does not contain any audio files, apart from theme.mp3 foreach (var fileSystemInfo in directoryService.GetFileSystemEntries(path)) { var attributes = fileSystemInfo.Attributes; if ((attributes & FileAttributes.Hidden) == FileAttributes.Hidden) { continue; } // Can't enforce this because files saved by Bitcasa are always marked System //if ((attributes & FileAttributes.System) == FileAttributes.System) //{ // continue; //} if ((attributes & FileAttributes.Directory) == FileAttributes.Directory) { //if (IsBadFolder(fileSystemInfo.Name)) //{ // return false; //} } else { if (EntityResolutionHelper.IsAudioFile(fileSystemInfo.FullName) && !string.Equals(fileSystem.GetFileNameWithoutExtension(fileSystemInfo), BaseItem.ThemeSongFilename)) { return(false); } } } return(true); }
/// <summary> /// Loads the additional parts. /// </summary> /// <returns>IEnumerable{Video}.</returns> private IEnumerable <Video> LoadAlternateVersionsWithinSameDirectory(IEnumerable <FileSystemInfo> fileSystemChildren, IDirectoryService directoryService) { IEnumerable <FileSystemInfo> files; // Only support this for video files. For folder rips, they'll have to use the linking feature if (VideoType == VideoType.VideoFile || VideoType == VideoType.Iso) { var path = Path; var filenamePrefix = System.IO.Path.GetFileName(System.IO.Path.GetDirectoryName(path)); files = fileSystemChildren.Where(i => { if ((i.Attributes & FileAttributes.Directory) == FileAttributes.Directory) { return(false); } return(!string.Equals(i.FullName, path, StringComparison.OrdinalIgnoreCase) && EntityResolutionHelper.IsVideoFile(i.FullName) && i.Name.StartsWith(filenamePrefix + " - ", StringComparison.OrdinalIgnoreCase)); }); } else { files = new List <FileSystemInfo>(); } return(LibraryManager.ResolvePaths <Video>(files, directoryService, null).Select(video => { // Try to retrieve it from the db. If we don't find it, use the resolved version var dbItem = LibraryManager.GetItemById(video.Id) as Video; if (dbItem != null) { video = dbItem; } video.PrimaryVersionId = Id; return video; // Sort them so that the list can be easily compared for changes }).OrderBy(i => i.Path).ToList()); }
/// <summary> /// Gets the multi file movie. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="movies">The movies.</param> /// <returns>``0.</returns> private T GetMultiFileMovie <T>(IEnumerable <T> movies) where T : Video, new() { var sortedMovies = movies.OrderBy(i => i.Path).ToList(); var firstMovie = sortedMovies[0]; // They must all be part of the sequence if we're going to consider it a multi-part movie // Only support up to 8 (matches Plex), to help avoid incorrect detection if (sortedMovies.All(i => EntityResolutionHelper.IsMultiPartFile(i.Path)) && sortedMovies.Count <= 8) { firstMovie.IsMultiPart = true; return(firstMovie); } return(null); }
/// <summary> /// Gets the multi file movie. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="movies">The movies.</param> /// <returns>``0.</returns> private T GetMultiFileMovie <T>(List <T> movies) where T : Video, new() { var multiPartMovies = movies.OrderBy(i => i.Path) .Where(i => EntityResolutionHelper.IsMultiPartFile(i.Path)) .ToList(); // They must all be part of the sequence if (multiPartMovies.Count != movies.Count) { return(null); } var firstPart = multiPartMovies[0]; firstPart.IsMultiPart = true; return(firstPart); }
public void TestMultiDiscAlbums() { Assert.IsFalse(EntityResolutionHelper.IsMultiDiscAlbumFolder(@"blah blah")); Assert.IsFalse(EntityResolutionHelper.IsMultiDiscAlbumFolder(@"d:\\music\weezer\\03 Pinkerton")); Assert.IsFalse(EntityResolutionHelper.IsMultiDiscAlbumFolder(@"d:\\music\\michael jackson\\Bad (2012 Remaster)")); Assert.IsTrue(EntityResolutionHelper.IsMultiDiscAlbumFolder(@"cd1")); Assert.IsTrue(EntityResolutionHelper.IsMultiDiscAlbumFolder(@"disc1")); Assert.IsTrue(EntityResolutionHelper.IsMultiDiscAlbumFolder(@"disk1")); // Add a space Assert.IsTrue(EntityResolutionHelper.IsMultiDiscAlbumFolder(@"cd 1")); Assert.IsTrue(EntityResolutionHelper.IsMultiDiscAlbumFolder(@"disc 1")); Assert.IsTrue(EntityResolutionHelper.IsMultiDiscAlbumFolder(@"disk 1")); Assert.IsTrue(EntityResolutionHelper.IsMultiDiscAlbumFolder(@"cd - 1")); Assert.IsTrue(EntityResolutionHelper.IsMultiDiscAlbumFolder(@"disc- 1")); Assert.IsTrue(EntityResolutionHelper.IsMultiDiscAlbumFolder(@"disk - 1")); }
/// <summary> /// Resolves the specified args. /// </summary> /// <param name="args">The args.</param> /// <returns>Entities.Audio.Audio.</returns> protected override Controller.Entities.Audio.Audio Resolve(ItemResolveArgs args) { // Return audio if the path is a file and has a matching extension if (!args.IsDirectory) { if (EntityResolutionHelper.IsAudioFile(args.Path)) { var collectionType = args.GetCollectionType(); if (string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase) || string.IsNullOrEmpty(collectionType)) { return(new Controller.Entities.Audio.Audio()); } } } return(null); }
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")); }
/// <summary> /// Determines whether [is series folder] [the specified path]. /// </summary> /// <param name="path">The path.</param> /// <param name="fileSystemChildren">The file system children.</param> /// <returns><c>true</c> if [is series folder] [the specified path]; otherwise, <c>false</c>.</returns> public static bool IsSeriesFolder(string path, IEnumerable <WIN32_FIND_DATA> fileSystemChildren) { // A folder with more than 3 non-season folders in will not becounted as a series var nonSeriesFolders = 0; foreach (var child in fileSystemChildren) { if (child.IsHidden || child.IsSystemFile) { continue; } if (child.IsDirectory) { if (IsSeasonFolder(child.Path)) { return(true); } nonSeriesFolders++; if (nonSeriesFolders >= 3) { return(false); } } else { if (EntityResolutionHelper.IsVideoFile(child.Path) && !string.IsNullOrEmpty(EpisodeNumberFromFile(child.Path, false))) { return(true); } } } return(false); }
/// <summary> /// Determine if the supplied file data points to a music album /// </summary> /// <param name="path">The path.</param> /// <returns><c>true</c> if [is music album] [the specified data]; otherwise, <c>false</c>.</returns> public static bool IsMusicAlbum(string path) { // If list contains at least 2 audio files or at least one and no video files consider it to contain music var foundAudio = 0; foreach (var fullName in Directory.EnumerateFiles(path)) { if (EntityResolutionHelper.IsAudioFile(fullName)) { foundAudio++; } if (foundAudio >= 2) { return(true); } if (EntityResolutionHelper.IsVideoFile(fullName)) { return(false); } } // or a single audio file and no video files return(foundAudio > 0); }
public void TestMultiPartFolders() { Assert.IsFalse(EntityResolutionHelper.IsMultiPartFolder(@"blah blah")); Assert.IsFalse(EntityResolutionHelper.IsMultiPartFolder(@"d:\\music\weezer\\03 Pinkerton")); Assert.IsFalse(EntityResolutionHelper.IsMultiPartFolder(@"d:\\music\\michael jackson\\Bad (2012 Remaster)")); Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - cd1")); Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - disc1")); Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - disk1")); Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - pt1")); Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - part1")); Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - dvd1")); // Add a space Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - cd 1")); Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - disc 1")); Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - disk 1")); Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - pt 1")); Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - part 1")); Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - dvd 1")); // Not case sensitive Assert.IsTrue(EntityResolutionHelper.IsMultiPartFolder(@"blah blah - Disc1")); }
/// <summary> /// Finds a movie based on a child file system entries /// </summary> /// <typeparam name="T"></typeparam> /// <param name="path">The path.</param> /// <param name="parent">The parent.</param> /// <param name="fileSystemEntries">The file system entries.</param> /// <returns>Movie.</returns> private T FindMovie <T>(string path, Folder parent, IEnumerable <FileSystemInfo> fileSystemEntries, IDirectoryService directoryService) where T : Video, new() { var movies = new List <T>(); var multiDiscFolders = new List <FileSystemInfo>(); // Loop through each child file/folder and see if we find a video foreach (var child in fileSystemEntries) { var filename = child.Name; if ((child.Attributes & FileAttributes.Directory) == FileAttributes.Directory) { if (IsDvdDirectory(filename)) { return(new T { Path = path, VideoType = VideoType.Dvd }); } if (IsBluRayDirectory(filename)) { return(new T { Path = path, VideoType = VideoType.BluRay }); } if (EntityResolutionHelper.IsMultiPartFile(filename)) { multiDiscFolders.Add(child); } continue; } // Don't misidentify xbmc trailers as a movie if (filename.IndexOf(BaseItem.XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase) != -1) { continue; } var childArgs = new ItemResolveArgs(_applicationPaths, _libraryManager, directoryService) { FileInfo = child, Path = child.FullName, Parent = parent }; var item = ResolveVideo <T>(childArgs); if (item != null) { item.IsInMixedFolder = false; movies.Add(item); } } if (movies.Count > 1) { return(GetMultiFileMovie(movies)); } if (movies.Count == 1) { return(movies[0]); } if (multiDiscFolders.Count > 0) { var folders = fileSystemEntries.Where(child => (child.Attributes & FileAttributes.Directory) == FileAttributes.Directory); return(GetMultiDiscMovie <T>(multiDiscFolders, folders)); } return(null); }
public async Task Organize(TvFileOrganizationOptions options, CancellationToken cancellationToken, IProgress <double> progress) { var minFileBytes = options.MinFileSizeMb * 1024 * 1024; var watchLocations = options.WatchLocations.ToList(); var eligibleFiles = watchLocations.SelectMany(GetFilesToOrganize) .OrderBy(_fileSystem.GetCreationTimeUtc) .Where(i => EntityResolutionHelper.IsVideoFile(i.FullName) && i.Length >= minFileBytes) .ToList(); progress.Report(10); var scanLibrary = false; if (eligibleFiles.Count > 0) { var numComplete = 0; foreach (var file in eligibleFiles) { var organizer = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager, _libraryMonitor, _providerManager); var result = await organizer.OrganizeEpisodeFile(file.FullName, options, options.OverwriteExistingEpisodes, cancellationToken).ConfigureAwait(false); if (result.Status == FileSortingStatus.Success) { scanLibrary = true; } numComplete++; double percent = numComplete; percent /= eligibleFiles.Count; progress.Report(10 + (89 * percent)); } } cancellationToken.ThrowIfCancellationRequested(); progress.Report(99); foreach (var path in watchLocations) { var deleteExtensions = options.LeftOverFileExtensionsToDelete .Select(i => i.Trim().TrimStart('.')) .Where(i => !string.IsNullOrEmpty(i)) .Select(i => "." + i) .ToList(); if (deleteExtensions.Count > 0) { DeleteLeftOverFiles(path, deleteExtensions); } if (options.DeleteEmptyFolders) { foreach (var subfolder in GetDirectories(path).ToList()) { DeleteEmptyFolders(subfolder); } } } if (scanLibrary) { await _libraryManager.ValidateMediaLibrary(new Progress <double>(), CancellationToken.None) .ConfigureAwait(false); } progress.Report(100); }
public static bool IsMultiDiscFolder(string path) { return(EntityResolutionHelper.IsMultiDiscAlbumFolder(path)); }
/// <summary> /// Compare our current children (presumably just read from the repo) with the current state of the file system and adjust for any changes /// ***Currently does not contain logic to maintain items that are unavailable in the file system*** /// </summary> /// <param name="progress">The progress.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <param name="recursive">if set to <c>true</c> [recursive].</param> /// <returns>Task.</returns> protected async virtual Task ValidateChildrenInternal(IProgress <double> progress, CancellationToken cancellationToken, bool?recursive = null) { // Nothing to do here if (LocationType != LocationType.FileSystem) { return; } cancellationToken.ThrowIfCancellationRequested(); var changedArgs = new ChildrenChangedEventArgs(this); //get the current valid children from filesystem (or wherever) var nonCachedChildren = GetNonCachedChildren(); if (nonCachedChildren == null) { return; //nothing to validate } progress.Report(5); //build a dictionary of the current children we have now by Id so we can compare quickly and easily var currentChildren = ActualChildren.ToDictionary(i => i.Id); //create a list for our validated children var validChildren = new ConcurrentBag <Tuple <BaseItem, bool> >(); cancellationToken.ThrowIfCancellationRequested(); Parallel.ForEach(nonCachedChildren, child => { BaseItem currentChild; if (currentChildren.TryGetValue(child.Id, out currentChild)) { currentChild.ResolveArgs = child.ResolveArgs; //existing item - check if it has changed if (currentChild.HasChanged(child)) { EntityResolutionHelper.EnsureDates(currentChild, child.ResolveArgs); changedArgs.AddUpdatedItem(currentChild); validChildren.Add(new Tuple <BaseItem, bool>(currentChild, true)); } else { validChildren.Add(new Tuple <BaseItem, bool>(currentChild, false)); } } else { //brand new item - needs to be added changedArgs.AddNewItem(child); validChildren.Add(new Tuple <BaseItem, bool>(child, true)); } }); // If any items were added or removed.... if (!changedArgs.ItemsAdded.IsEmpty || currentChildren.Count != validChildren.Count) { var newChildren = validChildren.Select(c => c.Item1).ToList(); //that's all the new and changed ones - now see if there are any that are missing changedArgs.ItemsRemoved = currentChildren.Values.Except(newChildren).ToList(); foreach (var item in changedArgs.ItemsRemoved) { Logger.Info("** " + item.Name + " Removed from library."); } var childrenReplaced = false; if (changedArgs.ItemsRemoved.Count > 0) { ActualChildren = new ConcurrentBag <BaseItem>(newChildren); childrenReplaced = true; } var saveTasks = new List <Task>(); foreach (var item in changedArgs.ItemsAdded) { Logger.Info("** " + item.Name + " Added to library."); if (!childrenReplaced) { _children.Add(item); } saveTasks.Add(Kernel.Instance.ItemRepository.SaveItem(item, CancellationToken.None)); } await Task.WhenAll(saveTasks).ConfigureAwait(false); //and save children in repo... Logger.Info("*** Saving " + newChildren.Count + " children for " + Name); await Kernel.Instance.ItemRepository.SaveChildren(Id, newChildren, CancellationToken.None).ConfigureAwait(false); } if (changedArgs.HasChange) { //force the indexes to rebuild next time IndexCache.Clear(); //and fire event LibraryManager.ReportLibraryChanged(changedArgs); } progress.Report(10); cancellationToken.ThrowIfCancellationRequested(); await RefreshChildren(validChildren, progress, cancellationToken, recursive).ConfigureAwait(false); progress.Report(100); }
/// <summary> /// Compare our current children (presumably just read from the repo) with the current state of the file system and adjust for any changes /// ***Currently does not contain logic to maintain items that are unavailable in the file system*** /// </summary> /// <param name="progress">The progress.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <param name="recursive">if set to <c>true</c> [recursive].</param> /// <param name="forceRefreshMetadata">if set to <c>true</c> [force refresh metadata].</param> /// <returns>Task.</returns> protected async virtual Task ValidateChildrenInternal(IProgress <double> progress, CancellationToken cancellationToken, bool?recursive = null, bool forceRefreshMetadata = false) { var locationType = LocationType; // Nothing to do here if (locationType == LocationType.Remote || locationType == LocationType.Virtual) { return; } cancellationToken.ThrowIfCancellationRequested(); IEnumerable <BaseItem> nonCachedChildren; try { nonCachedChildren = GetNonCachedChildren(); } catch (IOException ex) { nonCachedChildren = new BaseItem[] { }; Logger.ErrorException("Error getting file system entries for {0}", ex, Path); } if (nonCachedChildren == null) { return; //nothing to validate } progress.Report(5); //build a dictionary of the current children we have now by Id so we can compare quickly and easily var currentChildren = ActualChildren; //create a list for our validated children var validChildren = new ConcurrentBag <Tuple <BaseItem, bool> >(); var newItems = new ConcurrentBag <BaseItem>(); cancellationToken.ThrowIfCancellationRequested(); var options = new ParallelOptions { MaxDegreeOfParallelism = 20 }; Parallel.ForEach(nonCachedChildren, options, child => { BaseItem currentChild; if (currentChildren.TryGetValue(child.Id, out currentChild)) { currentChild.ResolveArgs = child.ResolveArgs; //existing item - check if it has changed if (currentChild.HasChanged(child)) { EntityResolutionHelper.EnsureDates(currentChild, child.ResolveArgs, false); validChildren.Add(new Tuple <BaseItem, bool>(currentChild, true)); } else { validChildren.Add(new Tuple <BaseItem, bool>(currentChild, false)); } currentChild.IsOffline = false; } else { //brand new item - needs to be added newItems.Add(child); validChildren.Add(new Tuple <BaseItem, bool>(child, true)); } }); // If any items were added or removed.... if (!newItems.IsEmpty || currentChildren.Count != validChildren.Count) { var newChildren = validChildren.Select(c => c.Item1).ToList(); //that's all the new and changed ones - now see if there are any that are missing var itemsRemoved = currentChildren.Values.Except(newChildren).ToList(); foreach (var item in itemsRemoved) { if (IsRootPathAvailable(item.Path)) { item.IsOffline = false; BaseItem removed; if (!_children.TryRemove(item.Id, out removed)) { Logger.Error("Failed to remove {0}", item.Name); } else { LibraryManager.ReportItemRemoved(item); } } else { item.IsOffline = true; validChildren.Add(new Tuple <BaseItem, bool>(item, false)); } } await LibraryManager.CreateItems(newItems, cancellationToken).ConfigureAwait(false); foreach (var item in newItems) { if (!_children.TryAdd(item.Id, item)) { Logger.Error("Failed to add {0}", item.Name); } else { Logger.Debug("** " + item.Name + " Added to library."); } } await ItemRepository.SaveChildren(Id, _children.Values.ToList().Select(i => i.Id), cancellationToken).ConfigureAwait(false); //force the indexes to rebuild next time IndexCache.Clear(); } progress.Report(10); cancellationToken.ThrowIfCancellationRequested(); await RefreshChildren(validChildren, progress, cancellationToken, recursive, forceRefreshMetadata).ConfigureAwait(false); progress.Report(100); }
/// <summary> /// Determines whether [is season folder] [the specified path]. /// </summary> /// <param name="path">The path.</param> /// <param name="directoryService">The directory service.</param> /// <returns><c>true</c> if [is season folder] [the specified path]; otherwise, <c>false</c>.</returns> private static bool IsSeasonFolder(string path, IDirectoryService directoryService) { // It's a season folder if it's named as such and does not contain any audio files, apart from theme.mp3 return(GetSeasonNumberFromPath(path) != null && !directoryService.GetFiles(path).Any(i => EntityResolutionHelper.IsAudioFile(i.FullName) && !string.Equals(Path.GetFileNameWithoutExtension(i.FullName), BaseItem.ThemeSongFilename))); }
/// <summary> /// Loads the additional parts. /// </summary> /// <returns>IEnumerable{Video}.</returns> private IEnumerable <Video> LoadAdditionalParts() { 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 { files = ResolveArgs.FileSystemChildren.Where(i => { if ((i.Attributes & FileAttributes.Directory) == FileAttributes.Directory) { return(false); } return(!string.Equals(i.FullName, Path, StringComparison.OrdinalIgnoreCase) && EntityResolutionHelper.IsVideoFile(i.FullName) && EntityResolutionHelper.IsMultiPartFile(i.Name)); }); } return(LibraryManager.ResolvePaths <Video>(files, null).Select(video => { // Try to retrieve it from the db. If we don't find it, use the resolved version var dbItem = LibraryManager.RetrieveItem(video.Id) as Video; if (dbItem != null) { dbItem.ResolveArgs = video.ResolveArgs; video = dbItem; } return video; }).ToList()); }
/// <summary> /// Loads the additional parts. /// </summary> /// <returns>IEnumerable{Video}.</returns> private IEnumerable <Video> LoadAdditionalParts(IEnumerable <FileSystemInfo> fileSystemChildren, IDirectoryService directoryService) { IEnumerable <FileSystemInfo> files; var path = Path; if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd) { files = fileSystemChildren.Where(i => { if ((i.Attributes & FileAttributes.Directory) == FileAttributes.Directory) { return(!string.Equals(i.FullName, path, StringComparison.OrdinalIgnoreCase) && EntityResolutionHelper.IsMultiPartFolder(i.FullName)); } return(false); }); } else { files = fileSystemChildren.Where(i => { if ((i.Attributes & FileAttributes.Directory) == FileAttributes.Directory) { return(false); } return(!string.Equals(i.FullName, path, StringComparison.OrdinalIgnoreCase) && EntityResolutionHelper.IsVideoFile(i.FullName) && EntityResolutionHelper.IsMultiPartFile(i.Name)); }); } return(LibraryManager.ResolvePaths <Video>(files, directoryService, null).Select(video => { // Try to retrieve it from the db. If we don't find it, use the resolved version var dbItem = LibraryManager.GetItemById(video.Id) as Video; if (dbItem != null) { video = dbItem; } return video; // Sort them so that the list can be easily compared for changes }).OrderBy(i => i.Path).ToList()); }