|
@@ -6,6 +6,7 @@ using System.Linq;
|
|
|
using System.Threading;
|
|
|
using System.Threading.Tasks;
|
|
|
using MediaBrowser.Controller.Configuration;
|
|
|
+using MediaBrowser.Controller.Drawing;
|
|
|
using MediaBrowser.Controller.Entities;
|
|
|
using MediaBrowser.Controller.Library;
|
|
|
using MediaBrowser.Controller.MediaEncoding;
|
|
@@ -15,7 +16,6 @@ using MediaBrowser.Model.Configuration;
|
|
|
using MediaBrowser.Model.Entities;
|
|
|
using MediaBrowser.Model.IO;
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
-using SkiaSharp;
|
|
|
|
|
|
namespace MediaBrowser.Providers.Trickplay;
|
|
|
|
|
@@ -31,6 +31,7 @@ public class TrickplayManager : ITrickplayManager
|
|
|
private readonly EncodingHelper _encodingHelper;
|
|
|
private readonly ILibraryManager _libraryManager;
|
|
|
private readonly IServerConfigurationManager _config;
|
|
|
+ private readonly IImageEncoder _imageEncoder;
|
|
|
|
|
|
private static readonly SemaphoreSlim _resourcePool = new(1, 1);
|
|
|
private static readonly string[] _trickplayImgExtensions = { ".jpg" };
|
|
@@ -45,6 +46,7 @@ public class TrickplayManager : ITrickplayManager
|
|
|
/// <param name="encodingHelper">The encoding helper.</param>
|
|
|
/// <param name="libraryManager">The library manager.</param>
|
|
|
/// <param name="config">The server configuration manager.</param>
|
|
|
+ /// <param name="imageEncoder">The image encoder.</param>
|
|
|
public TrickplayManager(
|
|
|
ILogger<TrickplayManager> logger,
|
|
|
IItemRepository itemRepo,
|
|
@@ -52,7 +54,8 @@ public class TrickplayManager : ITrickplayManager
|
|
|
IFileSystem fileSystem,
|
|
|
EncodingHelper encodingHelper,
|
|
|
ILibraryManager libraryManager,
|
|
|
- IServerConfigurationManager config)
|
|
|
+ IServerConfigurationManager config,
|
|
|
+ IImageEncoder imageEncoder)
|
|
|
{
|
|
|
_logger = logger;
|
|
|
_itemRepo = itemRepo;
|
|
@@ -61,6 +64,7 @@ public class TrickplayManager : ITrickplayManager
|
|
|
_encodingHelper = encodingHelper;
|
|
|
_libraryManager = libraryManager;
|
|
|
_config = config;
|
|
|
+ _imageEncoder = imageEncoder;
|
|
|
}
|
|
|
|
|
|
/// <inheritdoc />
|
|
@@ -141,7 +145,8 @@ public class TrickplayManager : ITrickplayManager
|
|
|
}
|
|
|
|
|
|
var images = _fileSystem.GetFiles(imgTempDir, _trickplayImgExtensions, false, false)
|
|
|
- .OrderBy(i => i.FullName)
|
|
|
+ .Select(i => i.FullName)
|
|
|
+ .OrderBy(i => i)
|
|
|
.ToList();
|
|
|
|
|
|
// Create tiles
|
|
@@ -185,11 +190,11 @@ public class TrickplayManager : ITrickplayManager
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private TrickplayTilesInfo CreateTiles(List<FileSystemMetadata> images, int width, TrickplayOptions options, string workDir, string outputDir)
|
|
|
+ private TrickplayTilesInfo CreateTiles(List<string> images, int width, TrickplayOptions options, string workDir, string outputDir)
|
|
|
{
|
|
|
if (images.Count == 0)
|
|
|
{
|
|
|
- throw new InvalidOperationException("Can't create trickplay from 0 images.");
|
|
|
+ throw new ArgumentException("Can't create trickplay from 0 images.");
|
|
|
}
|
|
|
|
|
|
Directory.CreateDirectory(workDir);
|
|
@@ -200,76 +205,42 @@ public class TrickplayManager : ITrickplayManager
|
|
|
Interval = options.Interval,
|
|
|
TileWidth = options.TileWidth,
|
|
|
TileHeight = options.TileHeight,
|
|
|
- TileCount = 0,
|
|
|
+ TileCount = images.Count,
|
|
|
+ // Set during image generation
|
|
|
+ Height = 0,
|
|
|
Bandwidth = 0
|
|
|
};
|
|
|
|
|
|
- var firstImg = SKBitmap.Decode(images[0].FullName);
|
|
|
- if (firstImg == null)
|
|
|
+ /*
|
|
|
+ * Generate trickplay tile grids from sets of images
|
|
|
+ */
|
|
|
+ var imageOptions = new ImageCollageOptions
|
|
|
{
|
|
|
- throw new InvalidDataException("Could not decode image data.");
|
|
|
- }
|
|
|
+ Width = tilesInfo.TileWidth,
|
|
|
+ Height = tilesInfo.TileHeight
|
|
|
+ };
|
|
|
|
|
|
- tilesInfo.Height = firstImg.Height;
|
|
|
- if (tilesInfo.Width != firstImg.Width)
|
|
|
- {
|
|
|
- throw new InvalidOperationException("Image width does not match config width.");
|
|
|
- }
|
|
|
+ var tilesPerGrid = tilesInfo.TileWidth * tilesInfo.TileHeight;
|
|
|
+ var requiredTileGrids = (int)Math.Ceiling((double)images.Count / tilesPerGrid);
|
|
|
|
|
|
- /*
|
|
|
- * Generate grids of trickplay image tiles
|
|
|
- */
|
|
|
- var imgNo = 0;
|
|
|
- var i = 0;
|
|
|
- while (i < images.Count)
|
|
|
+ for (int i = 0; i < requiredTileGrids; i++)
|
|
|
{
|
|
|
- var tileGrid = new SKBitmap(tilesInfo.Width * tilesInfo.TileWidth, tilesInfo.Height * tilesInfo.TileHeight);
|
|
|
+ // Set output/input paths
|
|
|
+ var tileGridPath = Path.Combine(workDir, $"{i}.jpg");
|
|
|
|
|
|
- using (var canvas = new SKCanvas(tileGrid))
|
|
|
- {
|
|
|
- for (var y = 0; y < tilesInfo.TileHeight; y++)
|
|
|
- {
|
|
|
- for (var x = 0; x < tilesInfo.TileWidth; x++)
|
|
|
- {
|
|
|
- if (i >= images.Count)
|
|
|
- {
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- var img = SKBitmap.Decode(images[i].FullName);
|
|
|
- if (img == null)
|
|
|
- {
|
|
|
- throw new InvalidDataException("Could not decode image data.");
|
|
|
- }
|
|
|
-
|
|
|
- if (tilesInfo.Width != img.Width)
|
|
|
- {
|
|
|
- throw new InvalidOperationException("Image width does not match config width.");
|
|
|
- }
|
|
|
-
|
|
|
- if (tilesInfo.Height != img.Height)
|
|
|
- {
|
|
|
- throw new InvalidOperationException("Image height does not match first image height.");
|
|
|
- }
|
|
|
-
|
|
|
- canvas.DrawBitmap(img, x * tilesInfo.Width, y * tilesInfo.Height);
|
|
|
- tilesInfo.TileCount++;
|
|
|
- i++;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ imageOptions.OutputPath = tileGridPath;
|
|
|
+ imageOptions.InputPaths = images.Skip(i * tilesPerGrid).Take(tilesPerGrid).ToList();
|
|
|
|
|
|
- // Output each tile grid to singular file
|
|
|
- var tileGridPath = Path.Combine(workDir, $"{imgNo}.jpg");
|
|
|
- using (var stream = File.OpenWrite(tileGridPath))
|
|
|
+ // Generate image and use returned height for tiles info
|
|
|
+ var height = _imageEncoder.CreateTrickplayGrid(imageOptions, options.JpegQuality, tilesInfo.Width, tilesInfo.Height != 0 ? tilesInfo.Height : null);
|
|
|
+ if (tilesInfo.Height == 0)
|
|
|
{
|
|
|
- tileGrid.Encode(stream, SKEncodedImageFormat.Jpeg, options.JpegQuality);
|
|
|
+ tilesInfo.Height = height;
|
|
|
}
|
|
|
|
|
|
+ // Update bitrate
|
|
|
var bitrate = (int)Math.Ceiling((decimal)new FileInfo(tileGridPath).Length * 8 / tilesInfo.TileWidth / tilesInfo.TileHeight / (tilesInfo.Interval / 1000));
|
|
|
tilesInfo.Bandwidth = Math.Max(tilesInfo.Bandwidth, bitrate);
|
|
|
-
|
|
|
- imgNo++;
|
|
|
}
|
|
|
|
|
|
/*
|