diff --git a/CHANGELOG.md b/CHANGELOG.md index 469d27ecd..ef2606b46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - If it tests well for others, it *may* replace the current `HLS Segmenter` in a future release - Add setting to change XMLTV data from `Local` time zone to `UTC` - This is needed because some clients (incorrectly) ignore time zone specifier and require UTC times +- Support `.ogv` video files in local libraries ### Fixed - Fix antiforgery error caused by reusing existing browser tabs across docker container restarts diff --git a/ErsatzTV.Application/Libraries/Commands/CallLibraryScannerHandler.cs b/ErsatzTV.Application/Libraries/Commands/CallLibraryScannerHandler.cs index 4bf22fd3b..ef7b91d81 100644 --- a/ErsatzTV.Application/Libraries/Commands/CallLibraryScannerHandler.cs +++ b/ErsatzTV.Application/Libraries/Commands/CallLibraryScannerHandler.cs @@ -193,9 +193,14 @@ public abstract class CallLibraryScannerHandler : "ErsatzTV.Scanner"; string processFileName = Environment.ProcessPath ?? string.Empty; - if (!string.IsNullOrWhiteSpace(processFileName)) + string processExecutable = Path.GetFileNameWithoutExtension(processFileName); + string folderName = Path.GetDirectoryName( + "dotnet".Equals(processExecutable, StringComparison.OrdinalIgnoreCase) + ? typeof(EntityIdResult).Assembly.Location + : processFileName); + if (!string.IsNullOrWhiteSpace(folderName)) { - string localFileName = Path.Combine(Path.GetDirectoryName(processFileName) ?? string.Empty, executable); + string localFileName = Path.Combine(folderName, executable); if (File.Exists(localFileName)) { return localFileName; diff --git a/ErsatzTV.Core/Interfaces/Repositories/ILibraryRepository.cs b/ErsatzTV.Core/Interfaces/Repositories/ILibraryRepository.cs index 98568d865..d7a5c170d 100644 --- a/ErsatzTV.Core/Interfaces/Repositories/ILibraryRepository.cs +++ b/ErsatzTV.Core/Interfaces/Repositories/ILibraryRepository.cs @@ -18,4 +18,5 @@ public interface ILibraryRepository Task GetOrAddFolder(LibraryPath libraryPath, Option maybeParentFolder, string folder); Task UpdateLibraryFolderId(MediaFile mediaFile, int libraryFolderId); Task UpdatePath(LibraryPath libraryPath, string normalizedLibraryPath); + Task> FindAllMissingFiles(LibraryPath libraryPath); } diff --git a/ErsatzTV.Infrastructure/Data/Repositories/LibraryRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/LibraryRepository.cs index 4380a5f37..5c704eaf1 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/LibraryRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/LibraryRepository.cs @@ -1,5 +1,6 @@ using Dapper; using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Extensions; using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Infrastructure.Extensions; @@ -211,6 +212,41 @@ public class LibraryRepository : ILibraryRepository new { Path = normalizedLibraryPath, libraryPath.Id }); } + public async Task> FindAllMissingFiles(LibraryPath libraryPath) + { + var result = new System.Collections.Generic.HashSet(); + + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); + IAsyncEnumerable items = dbContext.MediaItems + .AsNoTracking() + .Filter(mi => mi.LibraryPathId == libraryPath.Id) + .Filter(mi => mi.State == MediaItemState.FileNotFound) + .Include(mi => (mi as Movie).MediaVersions) + .ThenInclude(mv => mv.MediaFiles) + .Include(mi => (mi as Episode).MediaVersions) + .ThenInclude(mv => mv.MediaFiles) + .Include(mi => (mi as Song).MediaVersions) + .ThenInclude(mv => mv.MediaFiles) + .Include(mi => (mi as MusicVideo).MediaVersions) + .ThenInclude(mv => mv.MediaFiles) + .Include(mi => (mi as OtherVideo).MediaVersions) + .ThenInclude(mv => mv.MediaFiles) + .Include(mi => (mi as Image).MediaVersions) + .ThenInclude(mv => mv.MediaFiles) + .AsAsyncEnumerable(); + + await foreach (MediaItem item in items) + { + MediaVersion version = item.GetHeadVersion(); + foreach (MediaFile file in version.MediaFiles) + { + result.Add(file.Path); + } + } + + return result; + } + private static LibraryFolder CreateNewFolder(LibraryPath libraryPath, Option maybeParentFolder, string folder) { int? parentId = null; diff --git a/ErsatzTV.Infrastructure/Health/Checks/HardwareAccelerationHealthCheck.cs b/ErsatzTV.Infrastructure/Health/Checks/HardwareAccelerationHealthCheck.cs index 97103ddc5..bcdcd1f3c 100644 --- a/ErsatzTV.Infrastructure/Health/Checks/HardwareAccelerationHealthCheck.cs +++ b/ErsatzTV.Infrastructure/Health/Checks/HardwareAccelerationHealthCheck.cs @@ -94,7 +94,7 @@ public class HardwareAccelerationHealthCheck : BaseHealthCheck, IHardwareAcceler var accel = string.Join(", ", accelerationKinds); var channels = string.Join(", ", badChannels.Map(c => $"{c.Number} - {c.Name}")); return WarningResult( - $"The following channels are transcoding without hardware acceleration ({accel}): {channels}"); + $"The following channels use ffmpeg profiles that are not configured for hardware acceleration ({accel}): {channels}"); } return None; diff --git a/ErsatzTV.Scanner/Core/Metadata/LocalFolderScanner.cs b/ErsatzTV.Scanner/Core/Metadata/LocalFolderScanner.cs index 2647ca199..0bc248176 100644 --- a/ErsatzTV.Scanner/Core/Metadata/LocalFolderScanner.cs +++ b/ErsatzTV.Scanner/Core/Metadata/LocalFolderScanner.cs @@ -19,7 +19,7 @@ public abstract class LocalFolderScanner { public static readonly ImmutableHashSet VideoFileExtensions = new[] { - ".mpg", ".mp2", ".mpeg", ".mpe", ".mpv", ".ogg", ".mp4", + ".mpg", ".mp2", ".mpeg", ".mpe", ".mpv", ".ogg", ".ogv", ".mp4", ".m4p", ".m4v", ".avi", ".wmv", ".mov", ".mkv", ".m2ts", ".ts", ".webm" }.ToImmutableHashSet(StringComparer.OrdinalIgnoreCase); diff --git a/ErsatzTV.Scanner/Core/Metadata/OtherVideoFolderScanner.cs b/ErsatzTV.Scanner/Core/Metadata/OtherVideoFolderScanner.cs index 77a01422d..f40c2fdb7 100644 --- a/ErsatzTV.Scanner/Core/Metadata/OtherVideoFolderScanner.cs +++ b/ErsatzTV.Scanner/Core/Metadata/OtherVideoFolderScanner.cs @@ -78,16 +78,32 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan var allFolders = new System.Collections.Generic.HashSet(); var folderQueue = new Queue(); + System.Collections.Generic.HashSet allMissingFiles = + await _libraryRepository.FindAllMissingFiles(libraryPath); + + if (allMissingFiles.Count > 0) + { + _logger.LogDebug( + "Library path {Path} has {Count} missing files", + libraryPath.Path, + allMissingFiles.Count); + } + string normalizedLibraryPath = libraryPath.Path.TrimEnd( Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); if (libraryPath.Path != normalizedLibraryPath) { + _logger.LogDebug( + "Normalizing library path from {Original} to {Normalized}", + libraryPath.Path, + normalizedLibraryPath); await _libraryRepository.UpdatePath(libraryPath, normalizedLibraryPath); } if (ShouldIncludeFolder(libraryPath.Path) && allFolders.Add(libraryPath.Path)) { + _logger.LogDebug("Adding folder to scanner queue: {Folder}", libraryPath.Path); folderQueue.Enqueue(libraryPath.Path); } @@ -96,6 +112,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan .Filter(allFolders.Add) .OrderBy(identity)) { + _logger.LogDebug("Adding folder to scanner queue: {Folder}", folder); folderQueue.Enqueue(folder); } @@ -133,6 +150,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan .Filter(allFolders.Add) .OrderBy(identity)) { + _logger.LogDebug("Adding folder to scanner queue: {Folder}", subdirectory); folderQueue.Enqueue(subdirectory); } @@ -142,8 +160,18 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan maybeParentFolder, otherVideoFolder); + bool hasMissingFiles = allFiles.Any(allMissingFiles.Contains); + bool isSameEtag = knownFolder.Etag == etag && !hasMissingFiles; + + _logger.LogDebug( + "Scanning other video folder {Folder}; etag {Etag}; last etag {LastEtag}; has missing files {HasMissingFiles}", + otherVideoFolder, + etag, + knownFolder.Etag, + hasMissingFiles); + // skip folder if etag matches - if (allFiles.Count == 0 || knownFolder.Etag == etag) + if (allFiles.Count == 0 || isSameEtag) { continue; } @@ -156,6 +184,8 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan foreach (string file in allFiles.OrderBy(identity)) { + _logger.LogDebug("Processing other video file {File}", file); + Either> maybeVideo = await _otherVideoRepository .GetOrAdd(libraryPath, knownFolder, file) .BindT(video => UpdateStatistics(video, ffmpegPath, ffprobePath))