mirror of https://github.com/ErsatzTV/ErsatzTV.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
140 lines
5.7 KiB
140 lines
5.7 KiB
using System; |
|
using System.Collections.Generic; |
|
using System.IO; |
|
using System.Threading.Tasks; |
|
using ErsatzTV.Core.Domain; |
|
using ErsatzTV.Core.Interfaces.Images; |
|
using ErsatzTV.Core.Interfaces.Metadata; |
|
using ErsatzTV.Core.Interfaces.Repositories; |
|
using LanguageExt; |
|
using Microsoft.Extensions.Logging; |
|
|
|
namespace ErsatzTV.Core.Metadata |
|
{ |
|
public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner |
|
{ |
|
private readonly ILocalFileSystem _localFileSystem; |
|
private readonly ILocalMetadataProvider _localMetadataProvider; |
|
private readonly ILogger<MovieFolderScanner> _logger; |
|
private readonly IMovieRepository _movieRepository; |
|
|
|
public MovieFolderScanner( |
|
ILocalFileSystem localFileSystem, |
|
IMovieRepository movieRepository, |
|
ILocalStatisticsProvider localStatisticsProvider, |
|
ILocalMetadataProvider localMetadataProvider, |
|
IImageCache imageCache, |
|
ILogger<MovieFolderScanner> logger) |
|
: base(localFileSystem, localStatisticsProvider, imageCache, logger) |
|
{ |
|
_localFileSystem = localFileSystem; |
|
_movieRepository = movieRepository; |
|
_localMetadataProvider = localMetadataProvider; |
|
_logger = logger; |
|
} |
|
|
|
public async Task<Unit> ScanFolder(LocalMediaSource localMediaSource, string ffprobePath) |
|
{ |
|
if (!_localFileSystem.IsMediaSourceAccessible(localMediaSource)) |
|
{ |
|
_logger.LogWarning( |
|
"Media source is not accessible or missing; skipping scan of {Folder}", |
|
localMediaSource.Folder); |
|
return Unit.Default; |
|
} |
|
|
|
foreach (string movieFolder in _localFileSystem.ListSubdirectories(localMediaSource.Folder)) |
|
{ |
|
foreach (string file in _localFileSystem.ListFiles(movieFolder) |
|
.Filter(f => VideoFileExtensions.Contains(Path.GetExtension(f)))) |
|
{ |
|
// TODO: figure out how to rebuild playlists |
|
Either<BaseError, MovieMediaItem> maybeMovie = await _movieRepository |
|
.GetOrAdd(localMediaSource.Id, file) |
|
.BindT(movie => UpdateStatistics(movie, ffprobePath).MapT(_ => movie)) |
|
.BindT(UpdateMetadata) |
|
.BindT(UpdatePoster); |
|
|
|
maybeMovie.IfLeft( |
|
error => _logger.LogWarning("Error processing movie at {Path}: {Error}", file, error.Value)); |
|
} |
|
} |
|
|
|
return Unit.Default; |
|
} |
|
|
|
private async Task<Either<BaseError, MovieMediaItem>> UpdateMetadata(MovieMediaItem movie) |
|
{ |
|
try |
|
{ |
|
await LocateNfoFile(movie).Match( |
|
async nfoFile => |
|
{ |
|
if (movie.Metadata == null || movie.Metadata.Source == MetadataSource.Fallback || |
|
(movie.Metadata.LastWriteTime ?? DateTime.MinValue) < |
|
_localFileSystem.GetLastWriteTime(nfoFile)) |
|
{ |
|
_logger.LogDebug("Refreshing {Attribute} from {Path}", "Sidecar Metadata", nfoFile); |
|
await _localMetadataProvider.RefreshSidecarMetadata(movie, nfoFile); |
|
} |
|
}, |
|
async () => |
|
{ |
|
if (movie.Metadata == null) |
|
{ |
|
_logger.LogDebug("Refreshing {Attribute} for {Path}", "Fallback Metadata", movie.Path); |
|
await _localMetadataProvider.RefreshFallbackMetadata(movie); |
|
} |
|
}); |
|
|
|
return movie; |
|
} |
|
catch (Exception ex) |
|
{ |
|
return BaseError.New(ex.Message); |
|
} |
|
} |
|
|
|
private async Task<Either<BaseError, MovieMediaItem>> UpdatePoster(MovieMediaItem movie) |
|
{ |
|
try |
|
{ |
|
await LocatePoster(movie).IfSomeAsync( |
|
async posterFile => |
|
{ |
|
if (string.IsNullOrWhiteSpace(movie.Poster) || |
|
(movie.PosterLastWriteTime ?? DateTime.MinValue) < |
|
_localFileSystem.GetLastWriteTime(posterFile)) |
|
{ |
|
_logger.LogDebug("Refreshing {Attribute} from {Path}", "Poster", posterFile); |
|
await SavePosterToDisk(movie, posterFile, _movieRepository.Update, 440); |
|
} |
|
}); |
|
|
|
return movie; |
|
} |
|
catch (Exception ex) |
|
{ |
|
return BaseError.New(ex.Message); |
|
} |
|
} |
|
|
|
private Option<string> LocateNfoFile(MovieMediaItem movie) |
|
{ |
|
string movieAsNfo = Path.ChangeExtension(movie.Path, "nfo"); |
|
string movieNfo = Path.Combine(Path.GetDirectoryName(movie.Path) ?? string.Empty, "movie.nfo"); |
|
return Seq.create(movieAsNfo, movieNfo) |
|
.Filter(s => _localFileSystem.FileExists(s)) |
|
.HeadOrNone(); |
|
} |
|
|
|
private Option<string> LocatePoster(MovieMediaItem movie) |
|
{ |
|
string folder = Path.GetDirectoryName(movie.Path) ?? string.Empty; |
|
IEnumerable<string> possibleMoviePosters = ImageFileExtensions.Collect( |
|
ext => new[] { $"poster.{ext}", Path.GetFileNameWithoutExtension(movie.Path) + $"-poster.{ext}" }) |
|
.Map(f => Path.Combine(folder, f)); |
|
return possibleMoviePosters.Filter(p => _localFileSystem.FileExists(p)).HeadOrNone(); |
|
} |
|
} |
|
}
|
|
|