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 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
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 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
.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 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
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 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
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 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
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 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
.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 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
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 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
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 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
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 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
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 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
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 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
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 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
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 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
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 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
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 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
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.Common.Instrumentation.Extensions;
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 @@ namespace NzbDrone.Core.MediaFiles
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 @@ namespace NzbDrone.Core.MediaFiles
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 @@ namespace NzbDrone.Core.MediaFiles
_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 @@ namespace NzbDrone.Core.MediaFiles
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 @@ namespace NzbDrone.Core.MediaFiles
{ {
_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 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
} }
} }
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 @@ namespace NzbDrone.Core.MediaFiles.MovieImport
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);