Browse Source

duration analysis on files with missing duration metadata (#683)

* first pass

* analyze zero-duration files
pull/684/head
Jason Dove 3 years ago committed by GitHub
parent
commit
52a8b7db81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      CHANGELOG.md
  2. 28
      ErsatzTV.Application/Emby/Commands/SynchronizeEmbyLibraryByIdHandler.cs
  3. 28
      ErsatzTV.Application/Jellyfin/Commands/SynchronizeJellyfinLibraryByIdHandler.cs
  4. 4
      ErsatzTV.Application/MediaSources/Commands/ScanLocalLibraryHandler.cs
  5. 28
      ErsatzTV.Application/Plex/Commands/SynchronizePlexLibraryByIdHandler.cs
  6. 1
      ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs
  7. 31
      ErsatzTV.Core.Tests/Metadata/MovieFolderScannerTests.cs
  8. 7
      ErsatzTV.Core/Emby/EmbyMovieLibraryScanner.cs
  9. 18
      ErsatzTV.Core/Emby/EmbyTelevisionLibraryScanner.cs
  10. 1
      ErsatzTV.Core/Interfaces/Emby/IEmbyMovieLibraryScanner.cs
  11. 1
      ErsatzTV.Core/Interfaces/Emby/IEmbyTelevisionLibraryScanner.cs
  12. 1
      ErsatzTV.Core/Interfaces/Jellyfin/IJellyfinMovieLibraryScanner.cs
  13. 1
      ErsatzTV.Core/Interfaces/Jellyfin/IJellyfinTelevisionLibraryScanner.cs
  14. 9
      ErsatzTV.Core/Interfaces/Metadata/ILocalStatisticsProvider.cs
  15. 1
      ErsatzTV.Core/Interfaces/Metadata/IMovieFolderScanner.cs
  16. 1
      ErsatzTV.Core/Interfaces/Metadata/IMusicVideoFolderScanner.cs
  17. 1
      ErsatzTV.Core/Interfaces/Metadata/IOtherVideoFolderScanner.cs
  18. 1
      ErsatzTV.Core/Interfaces/Metadata/ITelevisionFolderScanner.cs
  19. 1
      ErsatzTV.Core/Interfaces/Plex/IPlexMovieLibraryScanner.cs
  20. 1
      ErsatzTV.Core/Interfaces/Plex/IPlexTelevisionLibraryScanner.cs
  21. 7
      ErsatzTV.Core/Jellyfin/JellyfinMovieLibraryScanner.cs
  22. 18
      ErsatzTV.Core/Jellyfin/JellyfinTelevisionLibraryScanner.cs
  23. 3
      ErsatzTV.Core/Metadata/LocalFolderScanner.cs
  24. 86
      ErsatzTV.Core/Metadata/LocalStatisticsProvider.cs
  25. 3
      ErsatzTV.Core/Metadata/MovieFolderScanner.cs
  26. 5
      ErsatzTV.Core/Metadata/MusicVideoFolderScanner.cs
  27. 3
      ErsatzTV.Core/Metadata/OtherVideoFolderScanner.cs
  28. 2
      ErsatzTV.Core/Metadata/SongFolderScanner.cs
  29. 8
      ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs
  30. 7
      ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs
  31. 17
      ErsatzTV.Core/Plex/PlexTelevisionLibraryScanner.cs
  32. 3886
      ErsatzTV.Infrastructure/Migrations/20220308033129_Analyze_ZeroDurationFiles.Designer.cs
  33. 50
      ErsatzTV.Infrastructure/Migrations/20220308033129_Analyze_ZeroDurationFiles.cs

3
CHANGELOG.md

@ -7,6 +7,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -7,6 +7,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Fixed
- Fix `HLS Direct` streaming mode
### Added
- Perform additional duration analysis on files with missing duration metadata
## [0.4.3-alpha] - 2022-03-05
### Fixed
- Fix song sorting with `Chronological` and `Shuffle In Order` playback orders

28
ErsatzTV.Application/Emby/Commands/SynchronizeEmbyLibraryByIdHandler.cs

@ -70,6 +70,7 @@ public class SynchronizeEmbyLibraryByIdHandler : @@ -70,6 +70,7 @@ public class SynchronizeEmbyLibraryByIdHandler :
parameters.ConnectionParameters.ActiveConnection.Address,
parameters.ConnectionParameters.ApiKey,
parameters.Library,
parameters.FFmpegPath,
parameters.FFprobePath);
break;
case LibraryMediaKind.Shows:
@ -77,6 +78,7 @@ public class SynchronizeEmbyLibraryByIdHandler : @@ -77,6 +78,7 @@ public class SynchronizeEmbyLibraryByIdHandler :
parameters.ConnectionParameters.ActiveConnection.Address,
parameters.ConnectionParameters.ApiKey,
parameters.Library,
parameters.FFmpegPath,
parameters.FFprobePath);
break;
}
@ -98,15 +100,17 @@ public class SynchronizeEmbyLibraryByIdHandler : @@ -98,15 +100,17 @@ public class SynchronizeEmbyLibraryByIdHandler :
private async Task<Validation<BaseError, RequestParameters>> Validate(
ISynchronizeEmbyLibraryById request) =>
(await ValidateConnection(request), await EmbyLibraryMustExist(request),
await ValidateLibraryRefreshInterval(), await ValidateFFprobePath())
await ValidateLibraryRefreshInterval(), await ValidateFFmpegPath(), await ValidateFFprobePath())
.Apply(
(connectionParameters, embyLibrary, libraryRefreshInterval, ffprobePath) => new RequestParameters(
connectionParameters,
embyLibrary,
request.ForceScan,
libraryRefreshInterval,
ffprobePath
));
(connectionParameters, embyLibrary, libraryRefreshInterval, ffmpegPath, ffprobePath) =>
new RequestParameters(
connectionParameters,
embyLibrary,
request.ForceScan,
libraryRefreshInterval,
ffmpegPath,
ffprobePath
));
private Task<Validation<BaseError, ConnectionParameters>> ValidateConnection(
ISynchronizeEmbyLibraryById request) =>
@ -149,6 +153,13 @@ public class SynchronizeEmbyLibraryByIdHandler : @@ -149,6 +153,13 @@ public class SynchronizeEmbyLibraryByIdHandler :
.FilterT(lri => lri > 0)
.Map(lri => lri.ToValidation<BaseError>("Library refresh interval is invalid"));
private Task<Validation<BaseError, string>> ValidateFFmpegPath() =>
_configElementRepository.GetValue<string>(ConfigElementKey.FFmpegPath)
.FilterT(File.Exists)
.Map(
ffmpegPath =>
ffmpegPath.ToValidation<BaseError>("FFmpeg path does not exist on the file system"));
private Task<Validation<BaseError, string>> ValidateFFprobePath() =>
_configElementRepository.GetValue<string>(ConfigElementKey.FFprobePath)
.FilterT(File.Exists)
@ -161,6 +172,7 @@ public class SynchronizeEmbyLibraryByIdHandler : @@ -161,6 +172,7 @@ public class SynchronizeEmbyLibraryByIdHandler :
EmbyLibrary Library,
bool ForceScan,
int LibraryRefreshInterval,
string FFmpegPath,
string FFprobePath);
private record ConnectionParameters(

28
ErsatzTV.Application/Jellyfin/Commands/SynchronizeJellyfinLibraryByIdHandler.cs

@ -70,6 +70,7 @@ public class SynchronizeJellyfinLibraryByIdHandler : @@ -70,6 +70,7 @@ public class SynchronizeJellyfinLibraryByIdHandler :
parameters.ConnectionParameters.ActiveConnection.Address,
parameters.ConnectionParameters.ApiKey,
parameters.Library,
parameters.FFmpegPath,
parameters.FFprobePath);
break;
case LibraryMediaKind.Shows:
@ -77,6 +78,7 @@ public class SynchronizeJellyfinLibraryByIdHandler : @@ -77,6 +78,7 @@ public class SynchronizeJellyfinLibraryByIdHandler :
parameters.ConnectionParameters.ActiveConnection.Address,
parameters.ConnectionParameters.ApiKey,
parameters.Library,
parameters.FFmpegPath,
parameters.FFprobePath);
break;
}
@ -98,15 +100,17 @@ public class SynchronizeJellyfinLibraryByIdHandler : @@ -98,15 +100,17 @@ public class SynchronizeJellyfinLibraryByIdHandler :
private async Task<Validation<BaseError, RequestParameters>> Validate(
ISynchronizeJellyfinLibraryById request) =>
(await ValidateConnection(request), await JellyfinLibraryMustExist(request),
await ValidateLibraryRefreshInterval(), await ValidateFFprobePath())
await ValidateLibraryRefreshInterval(), await ValidateFFmpegPath(), await ValidateFFprobePath())
.Apply(
(connectionParameters, jellyfinLibrary, libraryRefreshInterval, ffprobePath) => new RequestParameters(
connectionParameters,
jellyfinLibrary,
request.ForceScan,
libraryRefreshInterval,
ffprobePath
));
(connectionParameters, jellyfinLibrary, libraryRefreshInterval, ffmpegPath, ffprobePath) =>
new RequestParameters(
connectionParameters,
jellyfinLibrary,
request.ForceScan,
libraryRefreshInterval,
ffmpegPath,
ffprobePath
));
private Task<Validation<BaseError, ConnectionParameters>> ValidateConnection(
ISynchronizeJellyfinLibraryById request) =>
@ -149,6 +153,13 @@ public class SynchronizeJellyfinLibraryByIdHandler : @@ -149,6 +153,13 @@ public class SynchronizeJellyfinLibraryByIdHandler :
.FilterT(lri => lri > 0)
.Map(lri => lri.ToValidation<BaseError>("Library refresh interval is invalid"));
private Task<Validation<BaseError, string>> ValidateFFmpegPath() =>
_configElementRepository.GetValue<string>(ConfigElementKey.FFmpegPath)
.FilterT(File.Exists)
.Map(
ffmpegPath =>
ffmpegPath.ToValidation<BaseError>("FFmpeg path does not exist on the file system"));
private Task<Validation<BaseError, string>> ValidateFFprobePath() =>
_configElementRepository.GetValue<string>(ConfigElementKey.FFprobePath)
.FilterT(File.Exists)
@ -161,6 +172,7 @@ public class SynchronizeJellyfinLibraryByIdHandler : @@ -161,6 +172,7 @@ public class SynchronizeJellyfinLibraryByIdHandler :
JellyfinLibrary Library,
bool ForceScan,
int LibraryRefreshInterval,
string FFmpegPath,
string FFprobePath);
private record ConnectionParameters(

4
ErsatzTV.Application/MediaSources/Commands/ScanLocalLibraryHandler.cs

@ -89,6 +89,7 @@ public class ScanLocalLibraryHandler : IRequestHandler<ForceScanLocalLibrary, Ei @@ -89,6 +89,7 @@ public class ScanLocalLibraryHandler : IRequestHandler<ForceScanLocalLibrary, Ei
case LibraryMediaKind.Movies:
await _movieFolderScanner.ScanFolder(
libraryPath,
ffmpegPath,
ffprobePath,
progressMin,
progressMax);
@ -96,6 +97,7 @@ public class ScanLocalLibraryHandler : IRequestHandler<ForceScanLocalLibrary, Ei @@ -96,6 +97,7 @@ public class ScanLocalLibraryHandler : IRequestHandler<ForceScanLocalLibrary, Ei
case LibraryMediaKind.Shows:
await _televisionFolderScanner.ScanFolder(
libraryPath,
ffmpegPath,
ffprobePath,
progressMin,
progressMax);
@ -103,6 +105,7 @@ public class ScanLocalLibraryHandler : IRequestHandler<ForceScanLocalLibrary, Ei @@ -103,6 +105,7 @@ public class ScanLocalLibraryHandler : IRequestHandler<ForceScanLocalLibrary, Ei
case LibraryMediaKind.MusicVideos:
await _musicVideoFolderScanner.ScanFolder(
libraryPath,
ffmpegPath,
ffprobePath,
progressMin,
progressMax);
@ -110,6 +113,7 @@ public class ScanLocalLibraryHandler : IRequestHandler<ForceScanLocalLibrary, Ei @@ -110,6 +113,7 @@ public class ScanLocalLibraryHandler : IRequestHandler<ForceScanLocalLibrary, Ei
case LibraryMediaKind.OtherVideos:
await _otherVideoFolderScanner.ScanFolder(
libraryPath,
ffmpegPath,
ffprobePath,
progressMin,
progressMax);

28
ErsatzTV.Application/Plex/Commands/SynchronizePlexLibraryByIdHandler.cs

@ -68,6 +68,7 @@ public class @@ -68,6 +68,7 @@ public class
parameters.ConnectionParameters.ActiveConnection,
parameters.ConnectionParameters.PlexServerAuthToken,
parameters.Library,
parameters.FFmpegPath,
parameters.FFprobePath);
break;
case LibraryMediaKind.Shows:
@ -75,6 +76,7 @@ public class @@ -75,6 +76,7 @@ public class
parameters.ConnectionParameters.ActiveConnection,
parameters.ConnectionParameters.PlexServerAuthToken,
parameters.Library,
parameters.FFmpegPath,
parameters.FFprobePath);
break;
}
@ -95,15 +97,17 @@ public class @@ -95,15 +97,17 @@ public class
private async Task<Validation<BaseError, RequestParameters>> Validate(ISynchronizePlexLibraryById request) =>
(await ValidateConnection(request), await PlexLibraryMustExist(request),
await ValidateLibraryRefreshInterval(), await ValidateFFprobePath())
await ValidateLibraryRefreshInterval(), await ValidateFFmpegPath(), await ValidateFFprobePath())
.Apply(
(connectionParameters, plexLibrary, libraryRefreshInterval, ffprobePath) => new RequestParameters(
connectionParameters,
plexLibrary,
request.ForceScan,
libraryRefreshInterval,
ffprobePath
));
(connectionParameters, plexLibrary, libraryRefreshInterval, ffmpegPath, ffprobePath) =>
new RequestParameters(
connectionParameters,
plexLibrary,
request.ForceScan,
libraryRefreshInterval,
ffmpegPath,
ffprobePath
));
private Task<Validation<BaseError, ConnectionParameters>> ValidateConnection(
ISynchronizePlexLibraryById request) =>
@ -146,6 +150,13 @@ public class @@ -146,6 +150,13 @@ public class
.FilterT(lri => lri > 0)
.Map(lri => lri.ToValidation<BaseError>("Library refresh interval is invalid"));
private Task<Validation<BaseError, string>> ValidateFFmpegPath() =>
_configElementRepository.GetValue<string>(ConfigElementKey.FFmpegPath)
.FilterT(File.Exists)
.Map(
ffmpegPath =>
ffmpegPath.ToValidation<BaseError>("FFmpeg path does not exist on the file system"));
private Task<Validation<BaseError, string>> ValidateFFprobePath() =>
_configElementRepository.GetValue<string>(ConfigElementKey.FFprobePath)
.FilterT(File.Exists)
@ -158,6 +169,7 @@ public class @@ -158,6 +169,7 @@ public class
PlexLibrary Library,
bool ForceScan,
int LibraryRefreshInterval,
string FFmpegPath,
string FFprobePath);
private record ConnectionParameters(PlexMediaSource PlexMediaSource, PlexConnection ActiveConnection)

1
ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs

@ -282,6 +282,7 @@ public class TranscodingTests @@ -282,6 +282,7 @@ public class TranscodingTests
LoggerFactory.CreateLogger<LocalStatisticsProvider>());
await localStatisticsProvider.RefreshStatistics(
ExecutableName("ffmpeg"),
ExecutableName("ffprobe"),
new Movie
{

31
ErsatzTV.Core.Tests/Metadata/MovieFolderScannerTests.cs

@ -27,6 +27,10 @@ public class MovieFolderScannerTests @@ -27,6 +27,10 @@ public class MovieFolderScannerTests
? @"C:\Movies"
: "/movies";
private static readonly string FFmpegPath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? @"C:\bin\ffmpeg.exe"
: "/bin/ffmpeg";
private static readonly string FFprobePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? @"C:\bin\ffprobe.exe"
: "/bin/ffprobe";
@ -52,8 +56,9 @@ public class MovieFolderScannerTests @@ -52,8 +56,9 @@ public class MovieFolderScannerTests
_localStatisticsProvider = new Mock<ILocalStatisticsProvider>();
_localMetadataProvider = new Mock<ILocalMetadataProvider>();
_localStatisticsProvider.Setup(x => x.RefreshStatistics(It.IsAny<string>(), It.IsAny<MediaItem>()))
.Returns<string, MediaItem>((_, _) => Right<BaseError, bool>(true).AsTask());
_localStatisticsProvider.Setup(
x => x.RefreshStatistics(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<MediaItem>()))
.Returns<string, string, MediaItem>((_, _, _) => Right<BaseError, bool>(true).AsTask());
// fallback metadata adds metadata to a movie, so we need to replicate that here
_localMetadataProvider.Setup(x => x.RefreshFallbackMetadata(It.IsAny<Movie>()))
@ -90,6 +95,7 @@ public class MovieFolderScannerTests @@ -90,6 +95,7 @@ public class MovieFolderScannerTests
Either<BaseError, Unit> result = await service.ScanFolder(
libraryPath,
FFmpegPath,
FFprobePath,
0,
1);
@ -101,6 +107,7 @@ public class MovieFolderScannerTests @@ -101,6 +107,7 @@ public class MovieFolderScannerTests
_localStatisticsProvider.Verify(
x => x.RefreshStatistics(
FFmpegPath,
FFprobePath,
It.Is<Movie>(i => i.MediaVersions.Head().MediaFiles.Head().Path == moviePath)),
Times.Once);
@ -131,6 +138,7 @@ public class MovieFolderScannerTests @@ -131,6 +138,7 @@ public class MovieFolderScannerTests
Either<BaseError, Unit> result = await service.ScanFolder(
libraryPath,
FFmpegPath,
FFprobePath,
0,
1);
@ -142,6 +150,7 @@ public class MovieFolderScannerTests @@ -142,6 +150,7 @@ public class MovieFolderScannerTests
_localStatisticsProvider.Verify(
x => x.RefreshStatistics(
FFmpegPath,
FFprobePath,
It.Is<Movie>(i => i.MediaVersions.Head().MediaFiles.Head().Path == moviePath)),
Times.Once);
@ -173,6 +182,7 @@ public class MovieFolderScannerTests @@ -173,6 +182,7 @@ public class MovieFolderScannerTests
Either<BaseError, Unit> result = await service.ScanFolder(
libraryPath,
FFmpegPath,
FFprobePath,
0,
1);
@ -184,6 +194,7 @@ public class MovieFolderScannerTests @@ -184,6 +194,7 @@ public class MovieFolderScannerTests
_localStatisticsProvider.Verify(
x => x.RefreshStatistics(
FFmpegPath,
FFprobePath,
It.Is<Movie>(i => i.MediaVersions.Head().MediaFiles.Head().Path == moviePath)),
Times.Once);
@ -219,6 +230,7 @@ public class MovieFolderScannerTests @@ -219,6 +230,7 @@ public class MovieFolderScannerTests
Either<BaseError, Unit> result = await service.ScanFolder(
libraryPath,
FFmpegPath,
FFprobePath,
0,
1);
@ -230,6 +242,7 @@ public class MovieFolderScannerTests @@ -230,6 +242,7 @@ public class MovieFolderScannerTests
_localStatisticsProvider.Verify(
x => x.RefreshStatistics(
FFmpegPath,
FFprobePath,
It.Is<Movie>(i => i.MediaVersions.Head().MediaFiles.Head().Path == moviePath)),
Times.Once);
@ -268,6 +281,7 @@ public class MovieFolderScannerTests @@ -268,6 +281,7 @@ public class MovieFolderScannerTests
Either<BaseError, Unit> result = await service.ScanFolder(
libraryPath,
FFmpegPath,
FFprobePath,
0,
1);
@ -279,6 +293,7 @@ public class MovieFolderScannerTests @@ -279,6 +293,7 @@ public class MovieFolderScannerTests
_localStatisticsProvider.Verify(
x => x.RefreshStatistics(
FFmpegPath,
FFprobePath,
It.Is<Movie>(i => i.MediaVersions.Head().MediaFiles.Head().Path == moviePath)),
Times.Once);
@ -317,6 +332,7 @@ public class MovieFolderScannerTests @@ -317,6 +332,7 @@ public class MovieFolderScannerTests
Either<BaseError, Unit> result = await service.ScanFolder(
libraryPath,
FFmpegPath,
FFprobePath,
0,
1);
@ -328,6 +344,7 @@ public class MovieFolderScannerTests @@ -328,6 +344,7 @@ public class MovieFolderScannerTests
_localStatisticsProvider.Verify(
x => x.RefreshStatistics(
FFmpegPath,
FFprobePath,
It.Is<Movie>(i => i.MediaVersions.Head().MediaFiles.Head().Path == moviePath)),
Times.Once);
@ -365,6 +382,7 @@ public class MovieFolderScannerTests @@ -365,6 +382,7 @@ public class MovieFolderScannerTests
Either<BaseError, Unit> result = await service.ScanFolder(
libraryPath,
FFmpegPath,
FFprobePath,
0,
1);
@ -376,6 +394,7 @@ public class MovieFolderScannerTests @@ -376,6 +394,7 @@ public class MovieFolderScannerTests
_localStatisticsProvider.Verify(
x => x.RefreshStatistics(
FFmpegPath,
FFprobePath,
It.Is<Movie>(i => i.MediaVersions.Head().MediaFiles.Head().Path == moviePath)),
Times.Once);
@ -407,6 +426,7 @@ public class MovieFolderScannerTests @@ -407,6 +426,7 @@ public class MovieFolderScannerTests
Either<BaseError, Unit> result = await service.ScanFolder(
libraryPath,
FFmpegPath,
FFprobePath,
0,
1);
@ -418,6 +438,7 @@ public class MovieFolderScannerTests @@ -418,6 +438,7 @@ public class MovieFolderScannerTests
_localStatisticsProvider.Verify(
x => x.RefreshStatistics(
FFmpegPath,
FFprobePath,
It.Is<Movie>(i => i.MediaVersions.Head().MediaFiles.Head().Path == moviePath)),
Times.Once);
@ -451,6 +472,7 @@ public class MovieFolderScannerTests @@ -451,6 +472,7 @@ public class MovieFolderScannerTests
Either<BaseError, Unit> result = await service.ScanFolder(
libraryPath,
FFmpegPath,
FFprobePath,
0,
1);
@ -462,6 +484,7 @@ public class MovieFolderScannerTests @@ -462,6 +484,7 @@ public class MovieFolderScannerTests
_localStatisticsProvider.Verify(
x => x.RefreshStatistics(
FFmpegPath,
FFprobePath,
It.Is<Movie>(i => i.MediaVersions.Head().MediaFiles.Head().Path == moviePath)),
Times.Once);
@ -489,6 +512,7 @@ public class MovieFolderScannerTests @@ -489,6 +512,7 @@ public class MovieFolderScannerTests
Either<BaseError, Unit> result = await service.ScanFolder(
libraryPath,
FFmpegPath,
FFprobePath,
0,
1);
@ -500,6 +524,7 @@ public class MovieFolderScannerTests @@ -500,6 +524,7 @@ public class MovieFolderScannerTests
_localStatisticsProvider.Verify(
x => x.RefreshStatistics(
FFmpegPath,
FFprobePath,
It.Is<Movie>(i => i.MediaVersions.Head().MediaFiles.Head().Path == moviePath)),
Times.Once);
@ -532,6 +557,7 @@ public class MovieFolderScannerTests @@ -532,6 +557,7 @@ public class MovieFolderScannerTests
Either<BaseError, Unit> result = await service.ScanFolder(
libraryPath,
FFmpegPath,
FFprobePath,
0,
1);
@ -561,6 +587,7 @@ public class MovieFolderScannerTests @@ -561,6 +587,7 @@ public class MovieFolderScannerTests
Either<BaseError, Unit> result = await service.ScanFolder(
libraryPath,
FFmpegPath,
FFprobePath,
0,
1);

7
ErsatzTV.Core/Emby/EmbyMovieLibraryScanner.cs

@ -51,6 +51,7 @@ public class EmbyMovieLibraryScanner : IEmbyMovieLibraryScanner @@ -51,6 +51,7 @@ public class EmbyMovieLibraryScanner : IEmbyMovieLibraryScanner
string address,
string apiKey,
EmbyLibrary library,
string ffmpegPath,
string ffprobePath)
{
List<EmbyItemEtag> existingMovies = await _movieRepository.GetExistingEmbyMovies(library);
@ -172,7 +173,11 @@ public class EmbyMovieLibraryScanner : IEmbyMovieLibraryScanner @@ -172,7 +173,11 @@ public class EmbyMovieLibraryScanner : IEmbyMovieLibraryScanner
_logger.LogDebug("Refreshing {Attribute} for {Path}", "Statistics", localPath);
Either<BaseError, bool> refreshResult =
await _localStatisticsProvider.RefreshStatistics(ffprobePath, incomingMovie, localPath);
await _localStatisticsProvider.RefreshStatistics(
ffmpegPath,
ffprobePath,
incomingMovie,
localPath);
await refreshResult.Match(
async _ =>

18
ErsatzTV.Core/Emby/EmbyTelevisionLibraryScanner.cs

@ -51,6 +51,7 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner @@ -51,6 +51,7 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner
string address,
string apiKey,
EmbyLibrary library,
string ffmpegPath,
string ffprobePath)
{
List<EmbyItemEtag> existingShows = await _televisionRepository.GetExistingShows(library);
@ -70,7 +71,15 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner @@ -70,7 +71,15 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner
await maybeShows.Match(
async shows =>
{
await ProcessShows(address, apiKey, library, ffprobePath, pathReplacements, existingShows, shows);
await ProcessShows(
address,
apiKey,
library,
ffmpegPath,
ffprobePath,
pathReplacements,
existingShows,
shows);
var incomingShowIds = shows.Map(s => s.ItemId).ToList();
var showIds = existingShows
@ -104,6 +113,7 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner @@ -104,6 +113,7 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner
string address,
string apiKey,
EmbyLibrary library,
string ffmpegPath,
string ffprobePath,
List<EmbyPathReplacement> pathReplacements,
List<EmbyItemEtag> existingShows,
@ -167,6 +177,7 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner @@ -167,6 +177,7 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner
address,
apiKey,
library,
ffmpegPath,
ffprobePath,
pathReplacements,
incoming,
@ -196,6 +207,7 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner @@ -196,6 +207,7 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner
string address,
string apiKey,
EmbyLibrary library,
string ffmpegPath,
string ffprobePath,
List<EmbyPathReplacement> pathReplacements,
EmbyShow show,
@ -286,6 +298,7 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner @@ -286,6 +298,7 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner
show.ShowMetadata.Head().Title,
incoming.SeasonMetadata.Head().Title,
library,
ffmpegPath,
ffprobePath,
pathReplacements,
incoming,
@ -318,6 +331,7 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner @@ -318,6 +331,7 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner
string showName,
string seasonName,
EmbyLibrary library,
string ffmpegPath,
string ffprobePath,
List<EmbyPathReplacement> pathReplacements,
EmbySeason season,
@ -406,7 +420,7 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner @@ -406,7 +420,7 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner
_logger.LogDebug("Refreshing {Attribute} for {Path}", "Statistics", localPath);
Either<BaseError, bool> refreshResult =
await _localStatisticsProvider.RefreshStatistics(ffprobePath, incomingEpisode, localPath);
await _localStatisticsProvider.RefreshStatistics(ffmpegPath, ffprobePath, incomingEpisode, localPath);
refreshResult.Match(
_ => { },

1
ErsatzTV.Core/Interfaces/Emby/IEmbyMovieLibraryScanner.cs

@ -8,5 +8,6 @@ public interface IEmbyMovieLibraryScanner @@ -8,5 +8,6 @@ public interface IEmbyMovieLibraryScanner
string address,
string apiKey,
EmbyLibrary library,
string ffmpegPath,
string ffprobePath);
}

1
ErsatzTV.Core/Interfaces/Emby/IEmbyTelevisionLibraryScanner.cs

@ -8,5 +8,6 @@ public interface IEmbyTelevisionLibraryScanner @@ -8,5 +8,6 @@ public interface IEmbyTelevisionLibraryScanner
string address,
string apiKey,
EmbyLibrary library,
string ffmpegPath,
string ffprobePath);
}

1
ErsatzTV.Core/Interfaces/Jellyfin/IJellyfinMovieLibraryScanner.cs

@ -8,5 +8,6 @@ public interface IJellyfinMovieLibraryScanner @@ -8,5 +8,6 @@ public interface IJellyfinMovieLibraryScanner
string address,
string apiKey,
JellyfinLibrary library,
string ffmpegPath,
string ffprobePath);
}

1
ErsatzTV.Core/Interfaces/Jellyfin/IJellyfinTelevisionLibraryScanner.cs

@ -8,5 +8,6 @@ public interface IJellyfinTelevisionLibraryScanner @@ -8,5 +8,6 @@ public interface IJellyfinTelevisionLibraryScanner
string address,
string apiKey,
JellyfinLibrary library,
string ffmpegPath,
string ffprobePath);
}

9
ErsatzTV.Core/Interfaces/Metadata/ILocalStatisticsProvider.cs

@ -4,8 +4,13 @@ namespace ErsatzTV.Core.Interfaces.Metadata; @@ -4,8 +4,13 @@ namespace ErsatzTV.Core.Interfaces.Metadata;
public interface ILocalStatisticsProvider
{
Task<Either<BaseError, bool>> RefreshStatistics(string ffprobePath, MediaItem mediaItem);
Task<Either<BaseError, bool>> RefreshStatistics(string ffprobePath, MediaItem mediaItem, string mediaItemPath);
Task<Either<BaseError, bool>> RefreshStatistics(string ffmpegPath, string ffprobePath, MediaItem mediaItem);
Task<Either<BaseError, bool>> RefreshStatistics(
string ffmpegPath,
string ffprobePath,
MediaItem mediaItem,
string mediaItemPath);
Task<Either<BaseError, Dictionary<string, string>>> GetFormatTags(string ffprobePath, MediaItem mediaItem);
}

1
ErsatzTV.Core/Interfaces/Metadata/IMovieFolderScanner.cs

@ -6,6 +6,7 @@ public interface IMovieFolderScanner @@ -6,6 +6,7 @@ public interface IMovieFolderScanner
{
Task<Either<BaseError, Unit>> ScanFolder(
LibraryPath libraryPath,
string ffmpegPath,
string ffprobePath,
decimal progressMin,
decimal progressMax);

1
ErsatzTV.Core/Interfaces/Metadata/IMusicVideoFolderScanner.cs

@ -6,6 +6,7 @@ public interface IMusicVideoFolderScanner @@ -6,6 +6,7 @@ public interface IMusicVideoFolderScanner
{
Task<Either<BaseError, Unit>> ScanFolder(
LibraryPath libraryPath,
string ffmpegPath,
string ffprobePath,
decimal progressMin,
decimal progressMax);

1
ErsatzTV.Core/Interfaces/Metadata/IOtherVideoFolderScanner.cs

@ -6,6 +6,7 @@ public interface IOtherVideoFolderScanner @@ -6,6 +6,7 @@ public interface IOtherVideoFolderScanner
{
Task<Either<BaseError, Unit>> ScanFolder(
LibraryPath libraryPath,
string ffmpegPath,
string ffprobePath,
decimal progressMin,
decimal progressMax);

1
ErsatzTV.Core/Interfaces/Metadata/ITelevisionFolderScanner.cs

@ -6,6 +6,7 @@ public interface ITelevisionFolderScanner @@ -6,6 +6,7 @@ public interface ITelevisionFolderScanner
{
Task<Either<BaseError, Unit>> ScanFolder(
LibraryPath libraryPath,
string ffmpegPath,
string ffprobePath,
decimal progressMin,
decimal progressMax);

1
ErsatzTV.Core/Interfaces/Plex/IPlexMovieLibraryScanner.cs

@ -9,5 +9,6 @@ public interface IPlexMovieLibraryScanner @@ -9,5 +9,6 @@ public interface IPlexMovieLibraryScanner
PlexConnection connection,
PlexServerAuthToken token,
PlexLibrary library,
string ffmpegPath,
string ffprobePath);
}

1
ErsatzTV.Core/Interfaces/Plex/IPlexTelevisionLibraryScanner.cs

@ -9,5 +9,6 @@ public interface IPlexTelevisionLibraryScanner @@ -9,5 +9,6 @@ public interface IPlexTelevisionLibraryScanner
PlexConnection connection,
PlexServerAuthToken token,
PlexLibrary library,
string ffmpegPath,
string ffprobePath);
}

7
ErsatzTV.Core/Jellyfin/JellyfinMovieLibraryScanner.cs

@ -51,6 +51,7 @@ public class JellyfinMovieLibraryScanner : IJellyfinMovieLibraryScanner @@ -51,6 +51,7 @@ public class JellyfinMovieLibraryScanner : IJellyfinMovieLibraryScanner
string address,
string apiKey,
JellyfinLibrary library,
string ffmpegPath,
string ffprobePath)
{
List<JellyfinItemEtag> existingMovies = await _movieRepository.GetExistingJellyfinMovies(library);
@ -172,7 +173,11 @@ public class JellyfinMovieLibraryScanner : IJellyfinMovieLibraryScanner @@ -172,7 +173,11 @@ public class JellyfinMovieLibraryScanner : IJellyfinMovieLibraryScanner
_logger.LogDebug("Refreshing {Attribute} for {Path}", "Statistics", localPath);
Either<BaseError, bool> refreshResult =
await _localStatisticsProvider.RefreshStatistics(ffprobePath, incomingMovie, localPath);
await _localStatisticsProvider.RefreshStatistics(
ffmpegPath,
ffprobePath,
incomingMovie,
localPath);
await refreshResult.Match(
async _ =>

18
ErsatzTV.Core/Jellyfin/JellyfinTelevisionLibraryScanner.cs

@ -51,6 +51,7 @@ public class JellyfinTelevisionLibraryScanner : IJellyfinTelevisionLibraryScanne @@ -51,6 +51,7 @@ public class JellyfinTelevisionLibraryScanner : IJellyfinTelevisionLibraryScanne
string address,
string apiKey,
JellyfinLibrary library,
string ffmpegPath,
string ffprobePath)
{
List<JellyfinItemEtag> existingShows = await _televisionRepository.GetExistingShows(library);
@ -70,7 +71,15 @@ public class JellyfinTelevisionLibraryScanner : IJellyfinTelevisionLibraryScanne @@ -70,7 +71,15 @@ public class JellyfinTelevisionLibraryScanner : IJellyfinTelevisionLibraryScanne
await maybeShows.Match(
async shows =>
{
await ProcessShows(address, apiKey, library, ffprobePath, pathReplacements, existingShows, shows);
await ProcessShows(
address,
apiKey,
library,
ffmpegPath,
ffprobePath,
pathReplacements,
existingShows,
shows);
var incomingShowIds = shows.Map(s => s.ItemId).ToList();
var showIds = existingShows
@ -104,6 +113,7 @@ public class JellyfinTelevisionLibraryScanner : IJellyfinTelevisionLibraryScanne @@ -104,6 +113,7 @@ public class JellyfinTelevisionLibraryScanner : IJellyfinTelevisionLibraryScanne
string address,
string apiKey,
JellyfinLibrary library,
string ffmpegPath,
string ffprobePath,
List<JellyfinPathReplacement> pathReplacements,
List<JellyfinItemEtag> existingShows,
@ -167,6 +177,7 @@ public class JellyfinTelevisionLibraryScanner : IJellyfinTelevisionLibraryScanne @@ -167,6 +177,7 @@ public class JellyfinTelevisionLibraryScanner : IJellyfinTelevisionLibraryScanne
address,
apiKey,
library,
ffmpegPath,
ffprobePath,
pathReplacements,
incoming,
@ -196,6 +207,7 @@ public class JellyfinTelevisionLibraryScanner : IJellyfinTelevisionLibraryScanne @@ -196,6 +207,7 @@ public class JellyfinTelevisionLibraryScanner : IJellyfinTelevisionLibraryScanne
string address,
string apiKey,
JellyfinLibrary library,
string ffmpegPath,
string ffprobePath,
List<JellyfinPathReplacement> pathReplacements,
JellyfinShow show,
@ -286,6 +298,7 @@ public class JellyfinTelevisionLibraryScanner : IJellyfinTelevisionLibraryScanne @@ -286,6 +298,7 @@ public class JellyfinTelevisionLibraryScanner : IJellyfinTelevisionLibraryScanne
show.ShowMetadata.Head().Title,
incoming.SeasonMetadata.Head().Title,
library,
ffmpegPath,
ffprobePath,
pathReplacements,
incoming,
@ -319,6 +332,7 @@ public class JellyfinTelevisionLibraryScanner : IJellyfinTelevisionLibraryScanne @@ -319,6 +332,7 @@ public class JellyfinTelevisionLibraryScanner : IJellyfinTelevisionLibraryScanne
string showName,
string seasonName,
JellyfinLibrary library,
string ffmpegPath,
string ffprobePath,
List<JellyfinPathReplacement> pathReplacements,
JellyfinSeason season,
@ -408,7 +422,7 @@ public class JellyfinTelevisionLibraryScanner : IJellyfinTelevisionLibraryScanne @@ -408,7 +422,7 @@ public class JellyfinTelevisionLibraryScanner : IJellyfinTelevisionLibraryScanne
_logger.LogDebug("Refreshing {Attribute} for {Path}", "Statistics", localPath);
Either<BaseError, bool> refreshResult =
await _localStatisticsProvider.RefreshStatistics(ffprobePath, incomingEpisode, localPath);
await _localStatisticsProvider.RefreshStatistics(ffmpegPath, ffprobePath, incomingEpisode, localPath);
refreshResult.Match(
_ => { },

3
ErsatzTV.Core/Metadata/LocalFolderScanner.cs

@ -79,6 +79,7 @@ public abstract class LocalFolderScanner @@ -79,6 +79,7 @@ public abstract class LocalFolderScanner
protected async Task<Either<BaseError, MediaItemScanResult<T>>> UpdateStatistics<T>(
MediaItemScanResult<T> mediaItem,
string ffmpegPath,
string ffprobePath)
where T : MediaItem
{
@ -92,7 +93,7 @@ public abstract class LocalFolderScanner @@ -92,7 +93,7 @@ public abstract class LocalFolderScanner
{
_logger.LogDebug("Refreshing {Attribute} for {Path}", "Statistics", path);
Either<BaseError, bool> refreshResult =
await _localStatisticsProvider.RefreshStatistics(ffprobePath, mediaItem.Item);
await _localStatisticsProvider.RefreshStatistics(ffmpegPath, ffprobePath, mediaItem.Item);
refreshResult.Match(
result =>
{

86
ErsatzTV.Core/Metadata/LocalStatisticsProvider.cs

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
using System.Diagnostics;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using Bugsnag;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Extensions;
@ -30,12 +31,12 @@ public class LocalStatisticsProvider : ILocalStatisticsProvider @@ -30,12 +31,12 @@ public class LocalStatisticsProvider : ILocalStatisticsProvider
_logger = logger;
}
public async Task<Either<BaseError, bool>> RefreshStatistics(string ffprobePath, MediaItem mediaItem)
public async Task<Either<BaseError, bool>> RefreshStatistics(string ffmpegPath, string ffprobePath, MediaItem mediaItem)
{
try
{
string filePath = mediaItem.GetHeadVersion().MediaFiles.Head().Path;
return await RefreshStatistics(ffprobePath, mediaItem, filePath);
return await RefreshStatistics(ffmpegPath, ffprobePath, mediaItem, filePath);
}
catch (Exception ex)
{
@ -46,6 +47,7 @@ public class LocalStatisticsProvider : ILocalStatisticsProvider @@ -46,6 +47,7 @@ public class LocalStatisticsProvider : ILocalStatisticsProvider
}
public async Task<Either<BaseError, bool>> RefreshStatistics(
string ffmpegPath,
string ffprobePath,
MediaItem mediaItem,
string mediaItemPath)
@ -57,6 +59,11 @@ public class LocalStatisticsProvider : ILocalStatisticsProvider @@ -57,6 +59,11 @@ public class LocalStatisticsProvider : ILocalStatisticsProvider
async ffprobe =>
{
MediaVersion version = ProjectToMediaVersion(mediaItemPath, ffprobe);
if (version.Duration.TotalSeconds < 1)
{
await AnalyzeDuration(ffmpegPath, mediaItemPath, version);
}
bool result = await ApplyVersionUpdate(mediaItem, version, mediaItemPath);
return Right<BaseError, bool>(result);
},
@ -185,6 +192,67 @@ public class LocalStatisticsProvider : ILocalStatisticsProvider @@ -185,6 +192,67 @@ public class LocalStatisticsProvider : ILocalStatisticsProvider
});
}
private async Task AnalyzeDuration(string ffmpegPath, string path, MediaVersion version)
{
try
{
_logger.LogInformation(
"Media item at {Path} is missing duration metadata and requires additional analysis",
path);
var startInfo = new ProcessStartInfo
{
FileName = ffmpegPath,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8
};
startInfo.ArgumentList.Add("-i");
startInfo.ArgumentList.Add(path);
startInfo.ArgumentList.Add("-f");
startInfo.ArgumentList.Add("null");
startInfo.ArgumentList.Add("-");
var probe = new Process
{
StartInfo = startInfo
};
probe.Start();
string output = await probe.StandardError.ReadToEndAsync();
await probe.WaitForExitAsync();
if (probe.ExitCode == 0)
{
const string PATTERN = @"time=([^ ]+)";
IEnumerable<string> reversed = output.Split("\n").Reverse();
foreach (string line in reversed)
{
Match match = Regex.Match(line, PATTERN);
if (match.Success)
{
string time = match.Groups[1].Value;
var duration = TimeSpan.Parse(time, NumberFormatInfo.InvariantInfo);
_logger.LogInformation("Analyzed duration is {Duration}", duration);
version.Duration = duration;
return;
}
}
}
else
{
_logger.LogError("Duration analysis failed for media item at {Path}", path);
}
}
catch (Exception ex)
{
_client.Notify(ex);
_logger.LogError("Duration analysis failed for media item at {Path}", path);
}
}
internal MediaVersion ProjectToMediaVersion(string path, FFprobe probeOutput) =>
Optional(probeOutput)
.Filter(json => json?.format != null && json.streams != null)
@ -210,13 +278,13 @@ public class LocalStatisticsProvider : ILocalStatisticsProvider @@ -210,13 +278,13 @@ public class LocalStatisticsProvider : ILocalStatisticsProvider
var seconds = TimeSpan.FromSeconds(duration);
version.Duration = seconds;
}
else
{
_logger.LogWarning(
"Media item at {Path} has a missing or invalid duration {Duration} and will cause scheduling issues",
path,
json.format.duration);
}
// else
// {
// _logger.LogWarning(
// "Media item at {Path} has a missing or invalid duration {Duration} and will cause scheduling issues",
// path,
// json.format.duration);
// }
foreach (FFprobeStream audioStream in json.streams.Filter(s => s.codec_type == "audio"))
{

3
ErsatzTV.Core/Metadata/MovieFolderScanner.cs

@ -64,6 +64,7 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner @@ -64,6 +64,7 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner
public async Task<Either<BaseError, Unit>> ScanFolder(
LibraryPath libraryPath,
string ffmpegPath,
string ffprobePath,
decimal progressMin,
decimal progressMax)
@ -131,7 +132,7 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner @@ -131,7 +132,7 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner
// TODO: figure out how to rebuild playlists
Either<BaseError, MediaItemScanResult<Movie>> maybeMovie = await _movieRepository
.GetOrAdd(libraryPath, file)
.BindT(movie => UpdateStatistics(movie, ffprobePath))
.BindT(movie => UpdateStatistics(movie, ffmpegPath, ffprobePath))
.BindT(UpdateMetadata)
.BindT(movie => UpdateArtwork(movie, ArtworkKind.Poster))
.BindT(movie => UpdateArtwork(movie, ArtworkKind.FanArt))

5
ErsatzTV.Core/Metadata/MusicVideoFolderScanner.cs

@ -65,6 +65,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan @@ -65,6 +65,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan
public async Task<Either<BaseError, Unit>> ScanFolder(
LibraryPath libraryPath,
string ffmpegPath,
string ffprobePath,
decimal progressMin,
decimal progressMax)
@ -95,6 +96,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan @@ -95,6 +96,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan
{
await ScanMusicVideos(
libraryPath,
ffmpegPath,
ffprobePath,
result.Item,
artistFolder);
@ -233,6 +235,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan @@ -233,6 +235,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan
private async Task ScanMusicVideos(
LibraryPath libraryPath,
string ffmpegPath,
string ffprobePath,
Artist artist,
string artistFolder)
@ -272,7 +275,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan @@ -272,7 +275,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan
// TODO: figure out how to rebuild playouts
Either<BaseError, MediaItemScanResult<MusicVideo>> maybeMusicVideo = await _musicVideoRepository
.GetOrAdd(artist, libraryPath, file)
.BindT(musicVideo => UpdateStatistics(musicVideo, ffprobePath))
.BindT(musicVideo => UpdateStatistics(musicVideo, ffmpegPath, ffprobePath))
.BindT(UpdateMetadata)
.BindT(UpdateThumbnail)
.BindT(FlagNormal);

3
ErsatzTV.Core/Metadata/OtherVideoFolderScanner.cs

@ -62,6 +62,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan @@ -62,6 +62,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan
public async Task<Either<BaseError, Unit>> ScanFolder(
LibraryPath libraryPath,
string ffmpegPath,
string ffprobePath,
decimal progressMin,
decimal progressMax)
@ -126,7 +127,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan @@ -126,7 +127,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan
{
Either<BaseError, MediaItemScanResult<OtherVideo>> maybeVideo = await _otherVideoRepository
.GetOrAdd(libraryPath, file)
.BindT(video => UpdateStatistics(video, ffprobePath))
.BindT(video => UpdateStatistics(video, ffmpegPath, ffprobePath))
.BindT(UpdateMetadata)
.BindT(FlagNormal);

2
ErsatzTV.Core/Metadata/SongFolderScanner.cs

@ -128,7 +128,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner @@ -128,7 +128,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner
{
Either<BaseError, MediaItemScanResult<Song>> maybeSong = await _songRepository
.GetOrAdd(libraryPath, file)
.BindT(video => UpdateStatistics(video, ffprobePath))
.BindT(video => UpdateStatistics(video, ffmpegPath, ffprobePath))
.BindT(video => UpdateMetadata(video, ffprobePath))
.BindT(video => UpdateThumbnail(video, ffmpegPath))
.BindT(FlagNormal);

8
ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs

@ -64,6 +64,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan @@ -64,6 +64,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
public async Task<Either<BaseError, Unit>> ScanFolder(
LibraryPath libraryPath,
string ffmpegPath,
string ffprobePath,
decimal progressMin,
decimal progressMax)
@ -93,6 +94,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan @@ -93,6 +94,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
{
await ScanSeasons(
libraryPath,
ffmpegPath,
ffprobePath,
result.Item,
showFolder);
@ -154,6 +156,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan @@ -154,6 +156,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
private async Task<Unit> ScanSeasons(
LibraryPath libraryPath,
string ffmpegPath,
string ffprobePath,
Show show,
string showFolder)
@ -184,7 +187,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan @@ -184,7 +187,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
await maybeSeason.Match(
async season =>
{
await ScanEpisodes(libraryPath, ffprobePath, season, seasonFolder);
await ScanEpisodes(libraryPath, ffmpegPath, ffprobePath, season, seasonFolder);
await _libraryRepository.SetEtag(libraryPath, knownFolder, seasonFolder, etag);
season.Show = show;
@ -206,6 +209,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan @@ -206,6 +209,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
private async Task<Unit> ScanEpisodes(
LibraryPath libraryPath,
string ffmpegPath,
string ffprobePath,
Season season,
string seasonPath)
@ -225,7 +229,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan @@ -225,7 +229,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
Either<BaseError, Episode> maybeEpisode = await _televisionRepository
.GetOrAddEpisode(season, libraryPath, file)
.BindT(
episode => UpdateStatistics(new MediaItemScanResult<Episode>(episode), ffprobePath)
episode => UpdateStatistics(new MediaItemScanResult<Episode>(episode), ffmpegPath, ffprobePath)
.MapT(_ => episode))
.BindT(UpdateMetadata)
.BindT(UpdateThumbnail)

7
ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs

@ -54,6 +54,7 @@ public class PlexMovieLibraryScanner : PlexLibraryScanner, IPlexMovieLibraryScan @@ -54,6 +54,7 @@ public class PlexMovieLibraryScanner : PlexLibraryScanner, IPlexMovieLibraryScan
PlexConnection connection,
PlexServerAuthToken token,
PlexLibrary library,
string ffmpegPath,
string ffprobePath)
{
List<PlexPathReplacement> pathReplacements = await _mediaSourceRepository
@ -93,7 +94,8 @@ public class PlexMovieLibraryScanner : PlexLibraryScanner, IPlexMovieLibraryScan @@ -93,7 +94,8 @@ public class PlexMovieLibraryScanner : PlexLibraryScanner, IPlexMovieLibraryScan
// TODO: figure out how to rebuild playlists
Either<BaseError, MediaItemScanResult<PlexMovie>> maybeMovie = await _movieRepository
.GetOrAdd(library, incoming)
.BindT(existing => UpdateStatistics(pathReplacements, existing, incoming, ffprobePath))
.BindT(
existing => UpdateStatistics(pathReplacements, existing, incoming, ffmpegPath, ffprobePath))
.BindT(existing => UpdateMetadata(existing, incoming, library, connection, token))
.BindT(existing => UpdateArtwork(existing, incoming));
@ -145,6 +147,7 @@ public class PlexMovieLibraryScanner : PlexLibraryScanner, IPlexMovieLibraryScan @@ -145,6 +147,7 @@ public class PlexMovieLibraryScanner : PlexLibraryScanner, IPlexMovieLibraryScan
List<PlexPathReplacement> pathReplacements,
MediaItemScanResult<PlexMovie> result,
PlexMovie incoming,
string ffmpegPath,
string ffprobePath)
{
PlexMovie existing = result.Item;
@ -178,7 +181,7 @@ public class PlexMovieLibraryScanner : PlexLibraryScanner, IPlexMovieLibraryScan @@ -178,7 +181,7 @@ public class PlexMovieLibraryScanner : PlexLibraryScanner, IPlexMovieLibraryScan
_logger.LogDebug("Refreshing {Attribute} for {Path}", "Statistics", localPath);
Either<BaseError, bool> refreshResult =
await _localStatisticsProvider.RefreshStatistics(ffprobePath, existing, localPath);
await _localStatisticsProvider.RefreshStatistics(ffmpegPath, ffprobePath, existing, localPath);
await refreshResult.Match(
async _ =>

17
ErsatzTV.Core/Plex/PlexTelevisionLibraryScanner.cs

@ -55,6 +55,7 @@ public class PlexTelevisionLibraryScanner : PlexLibraryScanner, IPlexTelevisionL @@ -55,6 +55,7 @@ public class PlexTelevisionLibraryScanner : PlexLibraryScanner, IPlexTelevisionL
PlexConnection connection,
PlexServerAuthToken token,
PlexLibrary library,
string ffmpegPath,
string ffprobePath)
{
List<PlexPathReplacement> pathReplacements = await _mediaSourceRepository
@ -82,7 +83,14 @@ public class PlexTelevisionLibraryScanner : PlexLibraryScanner, IPlexTelevisionL @@ -82,7 +83,14 @@ public class PlexTelevisionLibraryScanner : PlexLibraryScanner, IPlexTelevisionL
await maybeShow.Match(
async result =>
{
await ScanSeasons(library, pathReplacements, result.Item, connection, token, ffprobePath);
await ScanSeasons(
library,
pathReplacements,
result.Item,
connection,
token,
ffmpegPath,
ffprobePath);
if (result.IsAdded)
{
@ -286,6 +294,7 @@ public class PlexTelevisionLibraryScanner : PlexLibraryScanner, IPlexTelevisionL @@ -286,6 +294,7 @@ public class PlexTelevisionLibraryScanner : PlexLibraryScanner, IPlexTelevisionL
PlexShow show,
PlexConnection connection,
PlexServerAuthToken token,
string ffmpegPath,
string ffprobePath)
{
Either<BaseError, List<PlexSeason>> entries = await _plexServerApiClient.GetShowSeasons(
@ -315,6 +324,7 @@ public class PlexTelevisionLibraryScanner : PlexLibraryScanner, IPlexTelevisionL @@ -315,6 +324,7 @@ public class PlexTelevisionLibraryScanner : PlexLibraryScanner, IPlexTelevisionL
season,
connection,
token,
ffmpegPath,
ffprobePath);
season.Show = show;
@ -384,6 +394,7 @@ public class PlexTelevisionLibraryScanner : PlexLibraryScanner, IPlexTelevisionL @@ -384,6 +394,7 @@ public class PlexTelevisionLibraryScanner : PlexLibraryScanner, IPlexTelevisionL
PlexSeason season,
PlexConnection connection,
PlexServerAuthToken token,
string ffmpegPath,
string ffprobePath)
{
Either<BaseError, List<PlexEpisode>> entries = await _plexServerApiClient.GetSeasonEpisodes(
@ -431,6 +442,7 @@ public class PlexTelevisionLibraryScanner : PlexLibraryScanner, IPlexTelevisionL @@ -431,6 +442,7 @@ public class PlexTelevisionLibraryScanner : PlexLibraryScanner, IPlexTelevisionL
library,
connection,
token,
ffmpegPath,
ffprobePath))
.BindT(existing => UpdateArtwork(existing, incoming));
@ -503,6 +515,7 @@ public class PlexTelevisionLibraryScanner : PlexLibraryScanner, IPlexTelevisionL @@ -503,6 +515,7 @@ public class PlexTelevisionLibraryScanner : PlexLibraryScanner, IPlexTelevisionL
PlexLibrary library,
PlexConnection connection,
PlexServerAuthToken token,
string ffmpegPath,
string ffprobePath)
{
MediaVersion existingVersion = existing.MediaVersions.Head();
@ -535,7 +548,7 @@ public class PlexTelevisionLibraryScanner : PlexLibraryScanner, IPlexTelevisionL @@ -535,7 +548,7 @@ public class PlexTelevisionLibraryScanner : PlexLibraryScanner, IPlexTelevisionL
_logger.LogDebug("Refreshing {Attribute} for {Path}", "Statistics", localPath);
Either<BaseError, bool> refreshResult =
await _localStatisticsProvider.RefreshStatistics(ffprobePath, existing, localPath);
await _localStatisticsProvider.RefreshStatistics(ffmpegPath, ffprobePath, existing, localPath);
await refreshResult.Match(
async _ =>

3886
ErsatzTV.Infrastructure/Migrations/20220308033129_Analyze_ZeroDurationFiles.Designer.cs generated

File diff suppressed because it is too large Load Diff

50
ErsatzTV.Infrastructure/Migrations/20220308033129_Analyze_ZeroDurationFiles.cs

@ -0,0 +1,50 @@ @@ -0,0 +1,50 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Migrations
{
public partial class Analyze_ZeroDurationFiles : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
@"UPDATE LibraryFolder SET Etag = NULL WHERE Id IN
(
SELECT LF.Id FROM LibraryFolder LF
INNER JOIN LibraryPath LP on LF.LibraryPathId = LP.Id
INNER JOIN Library L on LP.LibraryId = L.Id
INNER JOIN MediaItem MI on LP.Id = MI.LibraryPathId
INNER JOIN MediaVersion MV on MI.Id = COALESCE(MovieId, MusicVideoId, OtherVideoId, SongId, EpisodeId)
WHERE MV.Duration = '00:00:00.0000000'
)");
migrationBuilder.Sql(
@"UPDATE LibraryPath SET LastScan = '0001-01-01 00:00:00' WHERE Id IN
(
SELECT LP.Id FROM LibraryPath LP
INNER JOIN Library L on LP.LibraryId = L.Id
INNER JOIN MediaItem MI on LP.Id = MI.LibraryPathId
INNER JOIN MediaVersion MV on MI.Id = COALESCE(MovieId, MusicVideoId, OtherVideoId, SongId, EpisodeId)
WHERE MV.Duration = '00:00:00.0000000'
)");
migrationBuilder.Sql(
@"UPDATE Library SET LastScan = '0001-01-01 00:00:00' WHERE Id IN
(
SELECT L.Id FROM Library L
INNER JOIN LibraryPath LP on L.Id = LP.LibraryId
INNER JOIN MediaItem MI on LP.Id = MI.LibraryPathId
INNER JOIN MediaVersion MV on MI.Id = COALESCE(MovieId, MusicVideoId, OtherVideoId, SongId, EpisodeId)
WHERE MV.Duration = '00:00:00.0000000'
)");
migrationBuilder.Sql(
@"UPDATE MediaVersion SET DateUpdated = '0001-01-01 00:00:00' WHERE Duration = '00:00:00.0000000'");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}
Loading…
Cancel
Save