201 lines
6.8 KiB
C#
201 lines
6.8 KiB
C#
using Butter.Settings;
|
|
using Lactose.Models;
|
|
using Lactose.Repositories;
|
|
using SixLabors.ImageSharp;
|
|
using SixLabors.ImageSharp.Formats.Jpeg;
|
|
using SixLabors.ImageSharp.Processing;
|
|
|
|
namespace Lactose.Jobs;
|
|
|
|
public class ThumbnailJob : Job {
|
|
ThumbnailJob? ParentJob;
|
|
List<ThumbnailJob>? subJobs;
|
|
Asset? Asset;
|
|
string? ThumbnailPath;
|
|
int processedAssets = 0;
|
|
int totalAssets = 1; // Avoid division by zero
|
|
int ThumbnailSize;
|
|
ILogger<ThumbnailJob> Logger;
|
|
ISettingsRepository SettingsRepository;
|
|
IAssetRepository AssetRepository;
|
|
JobManager? JobManager;
|
|
|
|
public override string Name { get; }
|
|
|
|
public ThumbnailJob(
|
|
ILogger<ThumbnailJob> logger,
|
|
ISettingsRepository settingsRepository,
|
|
IAssetRepository assetRepository,
|
|
JobManager jobManager
|
|
) {
|
|
Logger = logger;
|
|
SettingsRepository = settingsRepository;
|
|
AssetRepository = assetRepository;
|
|
JobManager = jobManager;
|
|
Name = "Thumbnail Generation Job";
|
|
}
|
|
|
|
public ThumbnailJob(
|
|
ThumbnailJob parentJob,
|
|
Asset asset,
|
|
int thumbnailSize,
|
|
string thumbnailPath,
|
|
ILogger<ThumbnailJob> logger,
|
|
ISettingsRepository settingsRepository,
|
|
IAssetRepository assetRepository
|
|
) {
|
|
Logger = logger;
|
|
SettingsRepository = settingsRepository;
|
|
AssetRepository = assetRepository;
|
|
Asset = asset;
|
|
Name = $"Thumbnail Gen for {Asset?.OriginalPath}";
|
|
ParentJob = parentJob;
|
|
ThumbnailSize = thumbnailSize;
|
|
ThumbnailPath = thumbnailPath;
|
|
}
|
|
|
|
protected override async Task TaskJob(CancellationToken token) {
|
|
if (ParentJob == null) MasterJob(token);
|
|
else SlaveJob(token);
|
|
}
|
|
|
|
void SlaveJob(CancellationToken token) {
|
|
if (token.IsCancellationRequested) {
|
|
JobStatus.Cancel("Cancellation requested from user.");
|
|
return;
|
|
}
|
|
|
|
if (Asset == null) {
|
|
Logger.LogError("Sub-job started without an asset. Canceling job.");
|
|
JobStatus.Fail("Sub-job started without an asset. Canceling job.");
|
|
return;
|
|
}
|
|
|
|
JobStatus.Start();
|
|
|
|
try {
|
|
if (string.IsNullOrEmpty(Asset.OriginalPath) || !File.Exists(Asset.OriginalPath)) {
|
|
var msg = $"Original file for asset ID {Asset.Id} not found at path {Asset.OriginalPath}";
|
|
Logger.LogWarning(msg);
|
|
JobStatus.Fail(msg);
|
|
return;
|
|
}
|
|
|
|
using var image = Image.Load(Asset.OriginalPath);
|
|
|
|
image.Mutate(x => x.Resize(
|
|
new ResizeOptions {
|
|
Size = new Size(ThumbnailSize),
|
|
Mode = ResizeMode.Max,
|
|
Sampler = KnownResamplers.CatmullRom
|
|
}
|
|
)
|
|
);
|
|
|
|
if (ThumbnailPath == null) {
|
|
var msg = "Thumbnail path is not set. Cannot save thumbnail.";
|
|
Logger.LogError(msg);
|
|
JobStatus.Fail(msg);
|
|
return;
|
|
}
|
|
|
|
var path = PathFromGuid(Asset.Id, ThumbnailPath);
|
|
|
|
if (!Directory.Exists(path)) Directory.CreateDirectory(Path.GetDirectoryName(path)!);
|
|
|
|
image.SaveAsJpeg(
|
|
path,
|
|
new JpegEncoder() {
|
|
ColorType = JpegEncodingColor.Rgb,
|
|
Quality = 75,
|
|
SkipMetadata = true
|
|
}
|
|
);
|
|
|
|
Asset.ThumbnailPath = path;
|
|
AssetRepository.Update(Asset);
|
|
AssetRepository.Save();
|
|
} catch (Exception ex) {
|
|
Logger.LogError(ex, $"Failed to generate thumbnail for asset ID {Asset.Id}");
|
|
JobStatus.Fail($"Error: {ex.Message}");
|
|
return;
|
|
}
|
|
|
|
Logger.LogInformation($"Thumbnail generated for asset ID {Asset.Id} at {Asset.ThumbnailPath}");
|
|
JobStatus.Complete($"Thumbnail generated for asset ID {Asset.Id} at {Asset.ThumbnailPath}");
|
|
}
|
|
|
|
void MasterJob(CancellationToken token) {
|
|
if (JobManager == null) {
|
|
Logger.LogError("JobManager is not available. Cannot create sub-jobs.");
|
|
JobStatus.Fail("JobManager is not available. Cannot create sub-jobs.");
|
|
return;
|
|
}
|
|
|
|
JobStatus.Start();
|
|
Logger.LogInformation("Starting master Thumbnail job for all assets.");
|
|
var thumbPath = SettingsRepository.Get(Settings.ThumbnailPath.AsString());
|
|
//TODO: Add thumbnail size setting to the system
|
|
|
|
if (thumbPath == null) {
|
|
Logger.LogError("Thumbnail path not found. Cannot proceed with thumbnail generation.");
|
|
JobStatus.Fail("Thumbnail path not found. Cannot proceed with thumbnail generation.");
|
|
return;
|
|
}
|
|
|
|
var missingThumbnail = AssetRepository.GetAssetsMissingThumbnail(out totalAssets);
|
|
Logger.LogInformation($"Found {totalAssets} assets missing thumbnails.");
|
|
|
|
subJobs = new List<ThumbnailJob>();
|
|
|
|
foreach (var asset in missingThumbnail) {
|
|
if (token.IsCancellationRequested) {
|
|
lock (subJobs) subJobs.ForEach(j => j.Cancel());
|
|
JobStatus.Cancel("Cancellation requested from user.");
|
|
return;
|
|
}
|
|
|
|
// Create a sub-job for each asset
|
|
var job = JobManager.CreateJob<ThumbnailJob>(this, asset, 256, thumbPath.Value!);
|
|
|
|
job.Done += (o, _) => {
|
|
IncrementProcessedAssets();
|
|
lock (subJobs) subJobs.Remove((o as ThumbnailJob)!);
|
|
};
|
|
|
|
lock (subJobs) subJobs.Add(job);
|
|
JobManager.EnqueueJob(job);
|
|
}
|
|
|
|
JobStatus.Wait();
|
|
|
|
// Wait for all sub-jobs to complete
|
|
while (subJobs.Count > 0) {
|
|
if (token.IsCancellationRequested) {
|
|
lock (subJobs) subJobs.ForEach(j => j.Cancel());
|
|
JobStatus.Cancel("User requested cancellation.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
JobStatus.Complete("All thumbnails generated successfully.");
|
|
}
|
|
|
|
void IncrementProcessedAssets() {
|
|
processedAssets++;
|
|
JobStatus.UpdateProgress((float)processedAssets / totalAssets, $"Waiting for {subJobs.Count} sub-jobs to complete.");
|
|
}
|
|
|
|
static string PathFromGuid(Guid id, string root) {
|
|
var s = id.ToString("N"); // 32 chars, no dashes
|
|
|
|
return Path.Combine(
|
|
root,
|
|
s[..2], // 3F
|
|
s.Substring(2, 2), // 25
|
|
s.Substring(4, 2), // 04
|
|
$"{s}.jpg"
|
|
);
|
|
}
|
|
}
|