1
0
mirror of https://github.com/Radarr/Radarr.git synced 2024-10-03 22:57:18 +02:00

New: Reanalyze media files if file size changes

Fixes #6757
Fixes #6765
Fixes #4482

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
This commit is contained in:
Qstick 2021-11-28 15:42:44 -06:00
parent 507e8ec814
commit c538424229
5 changed files with 94 additions and 27 deletions

View File

@ -45,6 +45,10 @@ public void Setup()
Mocker.GetMock<IRootFolderService>() Mocker.GetMock<IRootFolderService>()
.Setup(s => s.GetBestRootFolderPath(It.IsAny<string>())) .Setup(s => s.GetBestRootFolderPath(It.IsAny<string>()))
.Returns(_rootFolder); .Returns(_rootFolder);
Mocker.GetMock<IMediaFileService>()
.Setup(s => s.GetFilesByMovie(It.IsAny<int>()))
.Returns(new List<MovieFile>());
} }
private void GivenRootFolder(params string[] subfolders) private void GivenRootFolder(params string[] subfolders)
@ -117,7 +121,7 @@ public void should_not_scan_if_movie_root_folder_is_empty()
.Verify(v => v.Clean(It.IsAny<Movie>(), It.IsAny<List<string>>()), Times.Never()); .Verify(v => v.Clean(It.IsAny<Movie>(), It.IsAny<List<string>>()), Times.Never());
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.IsAny<List<string>>(), _movie), Times.Never()); .Verify(v => v.GetImportDecisions(It.IsAny<List<string>>(), _movie, false), Times.Never());
} }
[Test] [Test]
@ -152,7 +156,7 @@ public void should_not_scan_extras_subfolder()
Subject.Scan(_movie); Subject.Scan(_movie);
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie), Times.Once()); .Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie, false), Times.Once());
} }
[Test] [Test]
@ -177,7 +181,7 @@ public void should_not_scan_various_extras_subfolders()
Subject.Scan(_movie); Subject.Scan(_movie);
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie), Times.Once()); .Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie, false), Times.Once());
} }
[Test] [Test]
@ -197,7 +201,7 @@ public void should_not_scan_featurettes_subfolders()
Subject.Scan(_movie); Subject.Scan(_movie);
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie), Times.Once()); .Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie, false), Times.Once());
} }
[Test] [Test]
@ -229,7 +233,7 @@ public void should_clean_but_not_import_if_movie_folder_does_not_exist()
.Verify(v => v.Clean(It.IsAny<Movie>(), It.IsAny<List<string>>()), Times.Once()); .Verify(v => v.Clean(It.IsAny<Movie>(), It.IsAny<List<string>>()), Times.Once());
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.IsAny<List<string>>(), _movie), Times.Never()); .Verify(v => v.GetImportDecisions(It.IsAny<List<string>>(), _movie, false), Times.Never());
} }
[Test] [Test]
@ -247,7 +251,7 @@ public void should_not_scan_AppleDouble_subfolder()
Subject.Scan(_movie); Subject.Scan(_movie);
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie), Times.Once()); .Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie, false), Times.Once());
} }
[Test] [Test]
@ -270,7 +274,7 @@ public void should_scan_extras_movie_and_subfolders()
Subject.Scan(_movie); Subject.Scan(_movie);
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 4), _movie), Times.Once()); .Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 4), _movie, false), Times.Once());
} }
[Test] [Test]
@ -289,7 +293,7 @@ public void should_not_scan_subfolders_that_start_with_period()
Subject.Scan(_movie); Subject.Scan(_movie);
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie), Times.Once()); .Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie, false), Times.Once());
} }
[Test] [Test]
@ -309,7 +313,7 @@ public void should_not_scan_subfolder_of_season_folder_that_starts_with_a_period
Subject.Scan(_movie); Subject.Scan(_movie);
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie), Times.Once()); .Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie, false), Times.Once());
} }
[Test] [Test]
@ -326,7 +330,7 @@ public void should_not_scan_Synology_eaDir()
Subject.Scan(_movie); Subject.Scan(_movie);
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie), Times.Once()); .Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie, false), Times.Once());
} }
[Test] [Test]
@ -343,7 +347,7 @@ public void should_not_scan_thumb_folder()
Subject.Scan(_movie); Subject.Scan(_movie);
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie), Times.Once()); .Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie, false), Times.Once());
} }
[Test] [Test]
@ -362,7 +366,7 @@ public void should_scan_dotHack_folder()
Subject.Scan(_movie); Subject.Scan(_movie);
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 2), _movie), Times.Once()); .Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 2), _movie, false), Times.Once());
} }
[Test] [Test]
@ -379,7 +383,7 @@ public void should_find_files_at_root_of_movie_folder()
Subject.Scan(_movie); Subject.Scan(_movie);
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 2), _movie), Times.Once()); .Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 2), _movie, false), Times.Once());
} }
[Test] [Test]
@ -397,7 +401,7 @@ public void should_exclude_inline_extra_files()
Subject.Scan(_movie); Subject.Scan(_movie);
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie), Times.Once()); .Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie, false), Times.Once());
} }
[Test] [Test]
@ -414,7 +418,7 @@ public void should_exclude_osx_metadata_files()
Subject.Scan(_movie); Subject.Scan(_movie);
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie), Times.Once()); .Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie, false), Times.Once());
} }
} }
} }

View File

@ -11,6 +11,7 @@
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.MediaFiles.Commands; using NzbDrone.Core.MediaFiles.Commands;
using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Core.MediaFiles.MovieImport; using NzbDrone.Core.MediaFiles.MovieImport;
using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
@ -36,8 +37,10 @@ public class DiskScanService :
private readonly IImportApprovedMovie _importApprovedMovies; private readonly IImportApprovedMovie _importApprovedMovies;
private readonly IConfigService _configService; private readonly IConfigService _configService;
private readonly IMovieService _movieService; private readonly IMovieService _movieService;
private readonly IMediaFileService _mediaFileService;
private readonly IMediaFileTableCleanupService _mediaFileTableCleanupService; private readonly IMediaFileTableCleanupService _mediaFileTableCleanupService;
private readonly IRootFolderService _rootFolderService; private readonly IRootFolderService _rootFolderService;
private readonly IUpdateMediaInfo _updateMediaInfoService;
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger; private readonly Logger _logger;
@ -46,8 +49,10 @@ public DiskScanService(IDiskProvider diskProvider,
IImportApprovedMovie importApprovedMovies, IImportApprovedMovie importApprovedMovies,
IConfigService configService, IConfigService configService,
IMovieService movieService, IMovieService movieService,
IMediaFileService mediaFileService,
IMediaFileTableCleanupService mediaFileTableCleanupService, IMediaFileTableCleanupService mediaFileTableCleanupService,
IRootFolderService rootFolderService, IRootFolderService rootFolderService,
IUpdateMediaInfo updateMediaInfoService,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
Logger logger) Logger logger)
{ {
@ -56,8 +61,10 @@ public DiskScanService(IDiskProvider diskProvider,
_importApprovedMovies = importApprovedMovies; _importApprovedMovies = importApprovedMovies;
_configService = configService; _configService = configService;
_movieService = movieService; _movieService = movieService;
_mediaFileService = mediaFileService;
_mediaFileTableCleanupService = mediaFileTableCleanupService; _mediaFileTableCleanupService = mediaFileTableCleanupService;
_rootFolderService = rootFolderService; _rootFolderService = rootFolderService;
_updateMediaInfoService = updateMediaInfoService;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_logger = logger; _logger = logger;
} }
@ -126,12 +133,46 @@ public void Scan(Movie movie)
CleanMediaFiles(movie, mediaFileList); CleanMediaFiles(movie, mediaFileList);
var movieFiles = _mediaFileService.GetFilesByMovie(movie.Id);
var unmappedFiles = MediaFileService.FilterExistingFiles(mediaFileList, movieFiles, movie);
var decisionsStopwatch = Stopwatch.StartNew(); var decisionsStopwatch = Stopwatch.StartNew();
var decisions = _importDecisionMaker.GetImportDecisions(mediaFileList, movie); var decisions = _importDecisionMaker.GetImportDecisions(unmappedFiles, movie, false);
decisionsStopwatch.Stop(); decisionsStopwatch.Stop();
_logger.Trace("Import decisions complete for: {0} [{1}]", movie, decisionsStopwatch.Elapsed); _logger.Trace("Import decisions complete for: {0} [{1}]", movie, decisionsStopwatch.Elapsed);
_importApprovedMovies.Import(decisions, false); _importApprovedMovies.Import(decisions, false);
// Update existing files that have a different file size
var fileInfoStopwatch = Stopwatch.StartNew();
var filesToUpdate = new List<MovieFile>();
foreach (var file in movieFiles)
{
var path = Path.Combine(movie.Path, file.RelativePath);
var fileSize = _diskProvider.GetFileSize(path);
if (file.Size == fileSize)
{
continue;
}
file.Size = fileSize;
if (!_updateMediaInfoService.Update(file, movie))
{
filesToUpdate.Add(file);
}
}
// Update any files that had a file size change, but didn't get media info updated.
if (filesToUpdate.Any())
{
_mediaFileService.Update(filesToUpdate);
}
fileInfoStopwatch.Stop();
_logger.Trace("Reprocessing existing files complete for: {0} [{1}]", movie, decisionsStopwatch.Elapsed);
RemoveEmptyMovieFolder(movie.Path); RemoveEmptyMovieFolder(movie.Path);
CompletedScanning(movie); CompletedScanning(movie);
} }

View File

@ -110,5 +110,17 @@ public void HandleAsync(MoviesDeletedEvent message)
{ {
_mediaFileRepository.DeleteForMovies(message.Movies.Select(m => m.Id).ToList()); _mediaFileRepository.DeleteForMovies(message.Movies.Select(m => m.Id).ToList());
} }
public static List<string> FilterExistingFiles(List<string> files, List<MovieFile> movieFiles, Movie movie)
{
var seriesFilePaths = movieFiles.Select(f => Path.Combine(movie.Path, f.RelativePath)).ToList();
if (!seriesFilePaths.Any())
{
return files;
}
return files.Except(seriesFilePaths, PathEqualityComparer.Instance).ToList();
}
} }
} }

View File

@ -11,10 +11,10 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
{ {
public interface IUpdateMediaInfo public interface IUpdateMediaInfo
{ {
void Update(MovieFile movieFile, Movie movie); bool Update(MovieFile movieFile, Movie movie);
} }
public class UpdateMediaInfoService : IHandle<MovieScannedEvent>, IUpdateMediaInfo public class UpdateMediaInfoService : IUpdateMediaInfo, IHandle<MovieScannedEvent>
{ {
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly IMediaFileService _mediaFileService; private readonly IMediaFileService _mediaFileService;
@ -54,35 +54,39 @@ public void Handle(MovieScannedEvent message)
} }
} }
public void Update(MovieFile movieFile, Movie movie) public bool Update(MovieFile movieFile, Movie movie)
{ {
if (!_configService.EnableMediaInfo) if (!_configService.EnableMediaInfo)
{ {
_logger.Debug("MediaInfo is disabled"); _logger.Debug("MediaInfo is disabled");
return; return false;
} }
UpdateMediaInfo(movieFile, movie); return UpdateMediaInfo(movieFile, movie);
} }
private void UpdateMediaInfo(MovieFile movieFile, Movie movie) private bool UpdateMediaInfo(MovieFile movieFile, Movie movie)
{ {
var path = Path.Combine(movie.Path, movieFile.RelativePath); var path = Path.Combine(movie.Path, movieFile.RelativePath);
if (!_diskProvider.FileExists(path)) if (!_diskProvider.FileExists(path))
{ {
_logger.Debug("Can't update MediaInfo because '{0}' does not exist", path); _logger.Debug("Can't update MediaInfo because '{0}' does not exist", path);
return; return false;
} }
var updatedMediaInfo = _videoFileInfoReader.GetMediaInfo(path); var updatedMediaInfo = _videoFileInfoReader.GetMediaInfo(path);
if (updatedMediaInfo != null) if (updatedMediaInfo == null)
{ {
movieFile.MediaInfo = updatedMediaInfo; return false;
_mediaFileService.Update(movieFile);
_logger.Debug("Updated MediaInfo for '{0}'", path);
} }
movieFile.MediaInfo = updatedMediaInfo;
_mediaFileService.Update(movieFile);
_logger.Debug("Updated MediaInfo for '{0}'", path);
return true;
} }
} }
} }

View File

@ -16,6 +16,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport
public interface IMakeImportDecision public interface IMakeImportDecision
{ {
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie); List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie);
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie, bool filterExistingFiles);
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie, DownloadClientItem downloadClientItem, ParsedMovieInfo folderInfo, bool sceneSource); List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie, DownloadClientItem downloadClientItem, ParsedMovieInfo folderInfo, bool sceneSource);
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie, DownloadClientItem downloadClientItem, ParsedMovieInfo folderInfo, bool sceneSource, bool filterExistingFiles); List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie, DownloadClientItem downloadClientItem, ParsedMovieInfo folderInfo, bool sceneSource, bool filterExistingFiles);
ImportDecision GetDecision(LocalMovie localMovie, DownloadClientItem downloadClientItem); ImportDecision GetDecision(LocalMovie localMovie, DownloadClientItem downloadClientItem);
@ -53,6 +54,11 @@ public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie mo
return GetImportDecisions(videoFiles, movie, null, null, false); return GetImportDecisions(videoFiles, movie, null, null, false);
} }
public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie, bool filterExistingFiles)
{
return GetImportDecisions(videoFiles, movie, null, null, false, filterExistingFiles);
}
public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie, DownloadClientItem downloadClientItem, ParsedMovieInfo folderInfo, bool sceneSource) public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie, DownloadClientItem downloadClientItem, ParsedMovieInfo folderInfo, bool sceneSource)
{ {
return GetImportDecisions(videoFiles, movie, downloadClientItem, folderInfo, sceneSource, true); return GetImportDecisions(videoFiles, movie, downloadClientItem, folderInfo, sceneSource, true);