Browse Source

limit disk use and keep cover art aspect ratio (#502)

* use temp file pool to limit disk use

* keep aspect ratio and crop when scaling cover art for blurred background

* fix typo
pull/504/head
Jason Dove 5 years ago committed by GitHub
parent
commit
0edfb71f8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs
  2. 1
      ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs
  3. 2
      ErsatzTV.Core.Tests/Metadata/MovieFolderScannerTests.cs
  4. 2
      ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs
  5. 6
      ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs
  6. 9
      ErsatzTV.Core/FFmpeg/TempFileCategory.cs
  7. 35
      ErsatzTV.Core/FFmpeg/TempFilePool.cs
  8. 1
      ErsatzTV.Core/FileSystemLayout.cs
  9. 9
      ErsatzTV.Core/Interfaces/FFmpeg/ITempFilePool.cs
  10. 6
      ErsatzTV.Core/Metadata/LocalFolderScanner.cs
  11. 10
      ErsatzTV.Core/Metadata/MovieFolderScanner.cs
  12. 2
      ErsatzTV.Core/Metadata/MusicVideoFolderScanner.cs
  13. 2
      ErsatzTV.Core/Metadata/OtherVideoFolderScanner.cs
  14. 2
      ErsatzTV.Core/Metadata/SongFolderScanner.cs
  15. 2
      ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs
  16. 6
      ErsatzTV/Startup.cs

8
ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs

@ -11,6 +11,7 @@ using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Filler; using ErsatzTV.Core.Domain.Filler;
using ErsatzTV.Core.Errors; using ErsatzTV.Core.Errors;
using ErsatzTV.Core.Extensions; using ErsatzTV.Core.Extensions;
using ErsatzTV.Core.FFmpeg;
using ErsatzTV.Core.Interfaces.Emby; using ErsatzTV.Core.Interfaces.Emby;
using ErsatzTV.Core.Interfaces.FFmpeg; using ErsatzTV.Core.Interfaces.FFmpeg;
using ErsatzTV.Core.Interfaces.Images; using ErsatzTV.Core.Interfaces.Images;
@ -41,6 +42,7 @@ namespace ErsatzTV.Application.Streaming.Queries
private readonly IPlexPathReplacementService _plexPathReplacementService; private readonly IPlexPathReplacementService _plexPathReplacementService;
private readonly IRuntimeInfo _runtimeInfo; private readonly IRuntimeInfo _runtimeInfo;
private readonly IImageCache _imageCache; private readonly IImageCache _imageCache;
private readonly ITempFilePool _tempFilePool;
public GetPlayoutItemProcessByChannelNumberHandler( public GetPlayoutItemProcessByChannelNumberHandler(
IDbContextFactory<TvContext> dbContextFactory, IDbContextFactory<TvContext> dbContextFactory,
@ -53,7 +55,8 @@ namespace ErsatzTV.Application.Streaming.Queries
ITelevisionRepository televisionRepository, ITelevisionRepository televisionRepository,
IArtistRepository artistRepository, IArtistRepository artistRepository,
IRuntimeInfo runtimeInfo, IRuntimeInfo runtimeInfo,
IImageCache imageCache) IImageCache imageCache,
ITempFilePool tempFilePool)
: base(dbContextFactory) : base(dbContextFactory)
{ {
_ffmpegProcessService = ffmpegProcessService; _ffmpegProcessService = ffmpegProcessService;
@ -66,6 +69,7 @@ namespace ErsatzTV.Application.Streaming.Queries
_artistRepository = artistRepository; _artistRepository = artistRepository;
_runtimeInfo = runtimeInfo; _runtimeInfo = runtimeInfo;
_imageCache = imageCache; _imageCache = imageCache;
_tempFilePool = tempFilePool;
} }
protected override async Task<Either<BaseError, PlayoutItemProcessModel>> GetProcess( protected override async Task<Either<BaseError, PlayoutItemProcessModel>> GetProcess(
@ -159,7 +163,7 @@ namespace ErsatzTV.Application.Streaming.Queries
// use thumbnail (cover art) if present // use thumbnail (cover art) if present
foreach (SongMetadata metadata in song.SongMetadata) foreach (SongMetadata metadata in song.SongMetadata)
{ {
string fileName = Path.GetTempFileName(); string fileName = _tempFilePool.GetNextTempFile(TempFileCategory.DrawText);
drawtextFile = fileName; drawtextFile = fileName;
var sb = new StringBuilder(); var sb = new StringBuilder();

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

@ -139,6 +139,7 @@ namespace ErsatzTV.Core.Tests.FFmpeg
new FFmpegPlaybackSettingsCalculator(), new FFmpegPlaybackSettingsCalculator(),
new FakeStreamSelector(), new FakeStreamSelector(),
new Mock<IImageCache>().Object, new Mock<IImageCache>().Object,
new Mock<ITempFilePool>().Object,
new Mock<ILogger<FFmpegProcessService>>().Object); new Mock<ILogger<FFmpegProcessService>>().Object);
MediaVersion v = new MediaVersion(); MediaVersion v = new MediaVersion();

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

@ -7,6 +7,7 @@ using System.Threading.Tasks;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Errors; using ErsatzTV.Core.Errors;
using ErsatzTV.Core.FFmpeg; using ErsatzTV.Core.FFmpeg;
using ErsatzTV.Core.Interfaces.FFmpeg;
using ErsatzTV.Core.Interfaces.Images; using ErsatzTV.Core.Interfaces.Images;
using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Repositories;
@ -603,6 +604,7 @@ namespace ErsatzTV.Core.Tests.Metadata
new Mock<ILibraryRepository>().Object, new Mock<ILibraryRepository>().Object,
new Mock<IMediator>().Object, new Mock<IMediator>().Object,
null, null,
new Mock<ITempFilePool>().Object,
new Mock<ILogger<MovieFolderScanner>>().Object new Mock<ILogger<MovieFolderScanner>>().Object
); );
} }

2
ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs

@ -239,9 +239,9 @@ namespace ErsatzTV.Core.FFmpeg
HardwareAccelerationKind.Qsv => $"scale_qsv=w={size.Width}:h={size.Height}", HardwareAccelerationKind.Qsv => $"scale_qsv=w={size.Width}:h={size.Height}",
HardwareAccelerationKind.Nvenc when _pixelFormat is "yuv420p10le" => HardwareAccelerationKind.Nvenc when _pixelFormat is "yuv420p10le" =>
$"hwupload_cuda,scale_cuda={size.Width}:{size.Height}", $"hwupload_cuda,scale_cuda={size.Width}:{size.Height}",
HardwareAccelerationKind.Nvenc when isSong => $"scale_cuda={size.Width}:{size.Height}:format=yuv420p",
HardwareAccelerationKind.Nvenc => $"scale_cuda={size.Width}:{size.Height}", HardwareAccelerationKind.Nvenc => $"scale_cuda={size.Width}:{size.Height}",
HardwareAccelerationKind.Vaapi => $"scale_vaapi=format=nv12:w={size.Width}:h={size.Height}", HardwareAccelerationKind.Vaapi => $"scale_vaapi=format=nv12:w={size.Width}:h={size.Height}",
_ when videoOnly => $"scale={size.Width}:{size.Height}:force_original_aspect_ratio=increase,crop={size.Width}:{size.Height}",
_ => $"scale={size.Width}:{size.Height}:flags=fast_bilinear" _ => $"scale={size.Width}:{size.Height}:flags=fast_bilinear"
}; };

6
ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs

@ -16,6 +16,7 @@ namespace ErsatzTV.Core.FFmpeg
{ {
private readonly IFFmpegStreamSelector _ffmpegStreamSelector; private readonly IFFmpegStreamSelector _ffmpegStreamSelector;
private readonly IImageCache _imageCache; private readonly IImageCache _imageCache;
private readonly ITempFilePool _tempFilePool;
private readonly ILogger<FFmpegProcessService> _logger; private readonly ILogger<FFmpegProcessService> _logger;
private readonly FFmpegPlaybackSettingsCalculator _playbackSettingsCalculator; private readonly FFmpegPlaybackSettingsCalculator _playbackSettingsCalculator;
@ -23,11 +24,13 @@ namespace ErsatzTV.Core.FFmpeg
FFmpegPlaybackSettingsCalculator ffmpegPlaybackSettingsService, FFmpegPlaybackSettingsCalculator ffmpegPlaybackSettingsService,
IFFmpegStreamSelector ffmpegStreamSelector, IFFmpegStreamSelector ffmpegStreamSelector,
IImageCache imageCache, IImageCache imageCache,
ITempFilePool tempFilePool,
ILogger<FFmpegProcessService> logger) ILogger<FFmpegProcessService> logger)
{ {
_playbackSettingsCalculator = ffmpegPlaybackSettingsService; _playbackSettingsCalculator = ffmpegPlaybackSettingsService;
_ffmpegStreamSelector = ffmpegStreamSelector; _ffmpegStreamSelector = ffmpegStreamSelector;
_imageCache = imageCache; _imageCache = imageCache;
_tempFilePool = tempFilePool;
_logger = logger; _logger = logger;
} }
@ -242,8 +245,7 @@ namespace ErsatzTV.Core.FFmpeg
{ {
try try
{ {
// todo: generate name by channel? clean up old images? string outputFile = _tempFilePool.GetNextTempFile(TempFileCategory.SongBackground);
string outputFile = Path.GetTempFileName();
MediaStream videoStream = await _ffmpegStreamSelector.SelectVideoStream(channel, videoVersion); MediaStream videoStream = await _ffmpegStreamSelector.SelectVideoStream(channel, videoVersion);

9
ErsatzTV.Core/FFmpeg/TempFileCategory.cs

@ -0,0 +1,9 @@
namespace ErsatzTV.Core.FFmpeg
{
public enum TempFileCategory
{
DrawText = 0,
SongBackground = 1,
CoverArt = 2
}
}

35
ErsatzTV.Core/FFmpeg/TempFilePool.cs

@ -0,0 +1,35 @@
using System.Collections.Generic;
using System.IO;
using ErsatzTV.Core.Interfaces.FFmpeg;
namespace ErsatzTV.Core.FFmpeg
{
public class TempFilePool : ITempFilePool
{
private const int ItemLimit = 10;
private readonly Dictionary<TempFileCategory, int> _state = new();
private readonly object _lock = new();
public string GetNextTempFile(TempFileCategory category)
{
lock (_lock)
{
var index = 0;
if (_state.TryGetValue(category, out int current))
{
index = (current + 1) % ItemLimit;
}
_state[category] = index;
return GetFileName(category, index);
}
}
private static string GetFileName(TempFileCategory category, int index)
{
return Path.Combine(FileSystemLayout.TempFilePoolFolder, $"{category}_{index}".ToLowerInvariant());
}
}
}

1
ErsatzTV.Core/FileSystemLayout.cs

@ -31,6 +31,7 @@ namespace ErsatzTV.Core
public static readonly string FFmpegReportsFolder = Path.Combine(AppDataFolder, "ffmpeg-reports"); public static readonly string FFmpegReportsFolder = Path.Combine(AppDataFolder, "ffmpeg-reports");
public static readonly string SearchIndexFolder = Path.Combine(AppDataFolder, "search-index"); public static readonly string SearchIndexFolder = Path.Combine(AppDataFolder, "search-index");
public static readonly string TempFilePoolFolder = Path.Combine(AppDataFolder, "temp-pool");
public static readonly string ArtworkCacheFolder = Path.Combine(AppDataFolder, "cache", "artwork"); public static readonly string ArtworkCacheFolder = Path.Combine(AppDataFolder, "cache", "artwork");

9
ErsatzTV.Core/Interfaces/FFmpeg/ITempFilePool.cs

@ -0,0 +1,9 @@
using ErsatzTV.Core.FFmpeg;
namespace ErsatzTV.Core.Interfaces.FFmpeg
{
public interface ITempFilePool
{
string GetNextTempFile(TempFileCategory category);
}
}

6
ErsatzTV.Core/Metadata/LocalFolderScanner.cs

@ -6,6 +6,7 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Extensions; using ErsatzTV.Core.Extensions;
using ErsatzTV.Core.FFmpeg;
using ErsatzTV.Core.Interfaces.FFmpeg; using ErsatzTV.Core.Interfaces.FFmpeg;
using ErsatzTV.Core.Interfaces.Images; using ErsatzTV.Core.Interfaces.Images;
using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Metadata;
@ -50,6 +51,7 @@ namespace ErsatzTV.Core.Metadata
private readonly IImageCache _imageCache; private readonly IImageCache _imageCache;
private readonly IFFmpegProcessService _ffmpegProcessService; private readonly IFFmpegProcessService _ffmpegProcessService;
private readonly ITempFilePool _tempFilePool;
private readonly ILocalFileSystem _localFileSystem; private readonly ILocalFileSystem _localFileSystem;
private readonly ILocalStatisticsProvider _localStatisticsProvider; private readonly ILocalStatisticsProvider _localStatisticsProvider;
@ -62,6 +64,7 @@ namespace ErsatzTV.Core.Metadata
IMetadataRepository metadataRepository, IMetadataRepository metadataRepository,
IImageCache imageCache, IImageCache imageCache,
IFFmpegProcessService ffmpegProcessService, IFFmpegProcessService ffmpegProcessService,
ITempFilePool tempFilePool,
ILogger logger) ILogger logger)
{ {
_localFileSystem = localFileSystem; _localFileSystem = localFileSystem;
@ -69,6 +72,7 @@ namespace ErsatzTV.Core.Metadata
_metadataRepository = metadataRepository; _metadataRepository = metadataRepository;
_imageCache = imageCache; _imageCache = imageCache;
_ffmpegProcessService = ffmpegProcessService; _ffmpegProcessService = ffmpegProcessService;
_tempFilePool = tempFilePool;
_logger = logger; _logger = logger;
} }
@ -133,7 +137,7 @@ namespace ErsatzTV.Core.Metadata
// if ffmpeg path is passed, we want to convert to png // if ffmpeg path is passed, we want to convert to png
foreach (string path in ffmpegPath) foreach (string path in ffmpegPath)
{ {
string tempName = Path.GetTempFileName(); string tempName = _tempFilePool.GetNextTempFile(TempFileCategory.CoverArt);
using Process process = _ffmpegProcessService.ConvertToPng(path, artworkFile, tempName); using Process process = _ffmpegProcessService.ConvertToPng(path, artworkFile, tempName);
process.Start(); process.Start();
await process.WaitForExitAsync(); await process.WaitForExitAsync();

10
ErsatzTV.Core/Metadata/MovieFolderScanner.cs

@ -42,8 +42,16 @@ namespace ErsatzTV.Core.Metadata
ILibraryRepository libraryRepository, ILibraryRepository libraryRepository,
IMediator mediator, IMediator mediator,
IFFmpegProcessService ffmpegProcessService, IFFmpegProcessService ffmpegProcessService,
ITempFilePool tempFilePool,
ILogger<MovieFolderScanner> logger) ILogger<MovieFolderScanner> logger)
: base(localFileSystem, localStatisticsProvider, metadataRepository, imageCache, ffmpegProcessService, logger) : base(
localFileSystem,
localStatisticsProvider,
metadataRepository,
imageCache,
ffmpegProcessService,
tempFilePool,
logger)
{ {
_localFileSystem = localFileSystem; _localFileSystem = localFileSystem;
_movieRepository = movieRepository; _movieRepository = movieRepository;

2
ErsatzTV.Core/Metadata/MusicVideoFolderScanner.cs

@ -43,12 +43,14 @@ namespace ErsatzTV.Core.Metadata
ILibraryRepository libraryRepository, ILibraryRepository libraryRepository,
IMediator mediator, IMediator mediator,
IFFmpegProcessService ffmpegProcessService, IFFmpegProcessService ffmpegProcessService,
ITempFilePool tempFilePool,
ILogger<MusicVideoFolderScanner> logger) : base( ILogger<MusicVideoFolderScanner> logger) : base(
localFileSystem, localFileSystem,
localStatisticsProvider, localStatisticsProvider,
metadataRepository, metadataRepository,
imageCache, imageCache,
ffmpegProcessService, ffmpegProcessService,
tempFilePool,
logger) logger)
{ {
_localFileSystem = localFileSystem; _localFileSystem = localFileSystem;

2
ErsatzTV.Core/Metadata/OtherVideoFolderScanner.cs

@ -41,12 +41,14 @@ namespace ErsatzTV.Core.Metadata
IOtherVideoRepository otherVideoRepository, IOtherVideoRepository otherVideoRepository,
ILibraryRepository libraryRepository, ILibraryRepository libraryRepository,
IFFmpegProcessService ffmpegProcessService, IFFmpegProcessService ffmpegProcessService,
ITempFilePool tempFilePool,
ILogger<OtherVideoFolderScanner> logger) : base( ILogger<OtherVideoFolderScanner> logger) : base(
localFileSystem, localFileSystem,
localStatisticsProvider, localStatisticsProvider,
metadataRepository, metadataRepository,
imageCache, imageCache,
ffmpegProcessService, ffmpegProcessService,
tempFilePool,
logger) logger)
{ {
_localFileSystem = localFileSystem; _localFileSystem = localFileSystem;

2
ErsatzTV.Core/Metadata/SongFolderScanner.cs

@ -42,12 +42,14 @@ namespace ErsatzTV.Core.Metadata
ISongRepository songRepository, ISongRepository songRepository,
ILibraryRepository libraryRepository, ILibraryRepository libraryRepository,
IFFmpegProcessService ffmpegProcessService, IFFmpegProcessService ffmpegProcessService,
ITempFilePool tempFilePool,
ILogger<SongFolderScanner> logger) : base( ILogger<SongFolderScanner> logger) : base(
localFileSystem, localFileSystem,
localStatisticsProvider, localStatisticsProvider,
metadataRepository, metadataRepository,
imageCache, imageCache,
ffmpegProcessService, ffmpegProcessService,
tempFilePool,
logger) logger)
{ {
_localFileSystem = localFileSystem; _localFileSystem = localFileSystem;

2
ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs

@ -42,12 +42,14 @@ namespace ErsatzTV.Core.Metadata
ILibraryRepository libraryRepository, ILibraryRepository libraryRepository,
IMediator mediator, IMediator mediator,
IFFmpegProcessService ffmpegProcessService, IFFmpegProcessService ffmpegProcessService,
ITempFilePool tempFilePool,
ILogger<TelevisionFolderScanner> logger) : base( ILogger<TelevisionFolderScanner> logger) : base(
localFileSystem, localFileSystem,
localStatisticsProvider, localStatisticsProvider,
metadataRepository, metadataRepository,
imageCache, imageCache,
ffmpegProcessService, ffmpegProcessService,
tempFilePool,
logger) logger)
{ {
_localFileSystem = localFileSystem; _localFileSystem = localFileSystem;

6
ErsatzTV/Startup.cs

@ -141,6 +141,11 @@ namespace ErsatzTV
Directory.CreateDirectory(FileSystemLayout.TranscodeFolder); Directory.CreateDirectory(FileSystemLayout.TranscodeFolder);
} }
if (!Directory.Exists(FileSystemLayout.TempFilePoolFolder))
{
Directory.CreateDirectory(FileSystemLayout.TempFilePoolFolder);
}
Log.Logger.Information("Database is at {DatabasePath}", FileSystemLayout.DatabasePath); Log.Logger.Information("Database is at {DatabasePath}", FileSystemLayout.DatabasePath);
// until we add a setting for a file-specific scheme://host:port to access // until we add a setting for a file-specific scheme://host:port to access
@ -245,6 +250,7 @@ namespace ErsatzTV
services.AddSingleton<IEntityLocker, EntityLocker>(); services.AddSingleton<IEntityLocker, EntityLocker>();
services.AddSingleton<ISearchIndex, SearchIndex>(); services.AddSingleton<ISearchIndex, SearchIndex>();
services.AddSingleton<IFFmpegSegmenterService, FFmpegSegmenterService>(); services.AddSingleton<IFFmpegSegmenterService, FFmpegSegmenterService>();
services.AddSingleton<ITempFilePool, TempFilePool>();
AddChannel<IBackgroundServiceRequest>(services); AddChannel<IBackgroundServiceRequest>(services);
AddChannel<IPlexBackgroundServiceRequest>(services); AddChannel<IPlexBackgroundServiceRequest>(services);
AddChannel<IJellyfinBackgroundServiceRequest>(services); AddChannel<IJellyfinBackgroundServiceRequest>(services);

Loading…
Cancel
Save