It works a part of the way: When user uploads an image TinifyAPI optimizes it and saves the optimized image in a temporary location (like C:\Users\marti\AppData\Local\Temp\tmpsxjjxj.tmp).
But the part of the code that should overwrite the uploaded, non-optimized image (which is my goal - I don't want the original non-optimized image since it consumes server disc space) does not work. I.e. this part:
using (var fileStream = System.IO.File.OpenRead(tempFilePath))
{
byte[] fileBytesArray;
using (var memoryStream = new MemoryStream())
{
await fileStream.CopyToAsync(memoryStream);
fileBytesArray = memoryStream.ToArray();
}
var fileBytes = new ByteArrayContent(fileBytesArray);
var uploadResponse = await httpClient.PutAsync(mediaUrl, fileBytes);
if (!uploadResponse.IsSuccessStatusCode)
{
NotifyUser("Upload Error", $"Failed to upload optimized image.");
}
}
No error, but also no file is overwritten.
My full code:
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Services;
using TinifyAPI;
using Newtonsoft.Json.Linq;
namespace MartinRud.Notifications
{
public class MediaNotificationHandler : INotificationHandler<MediaSavedNotification>
{
private readonly IMediaService _mediaService;
private readonly IConfiguration _configuration;
private readonly ILogger<MediaNotificationHandler> _logger;
private readonly IEventMessagesFactory _eventMessagesFactory;
private readonly IHttpClientFactory _httpClientFactory;
private readonly string _baseUrl;
public MediaNotificationHandler(IMediaService mediaService, IConfiguration configuration, ILogger<MediaNotificationHandler> logger, IEventMessagesFactory eventMessagesFactory, IHttpClientFactory httpClientFactory)
{
_mediaService = mediaService;
_configuration = configuration;
_logger = logger;
_eventMessagesFactory = eventMessagesFactory;
_httpClientFactory = httpClientFactory;
_baseUrl = configuration["MediaBaseUrl"] ?? "https://localhost:44378";
Tinify.Key = _configuration["TinifierSettings:ApiKey"];
}
public async void Handle(MediaSavedNotification notification)
{
var httpClient = _httpClientFactory.CreateClient();
foreach (var mediaItem in notification.SavedEntities)
{
if (mediaItem.ContentType.Alias.Equals("Image"))
{
var mediaJson = mediaItem.GetValue<string>("umbracoFile");
var mediaUrl = ExtractUrlFromJson(mediaJson);
if (!string.IsNullOrEmpty(mediaUrl))
{
if (!Uri.IsWellFormedUriString(mediaUrl, UriKind.Absolute))
{
mediaUrl = new Uri(new Uri(_baseUrl), mediaUrl).ToString();
}
_logger.LogInformation($"Attempting to fetch URL: {mediaUrl}");
var tempFilePath = Path.GetTempFileName();
var jsonFilePath = Path.ChangeExtension(tempFilePath, "json");
try
{
var response = await GetWithRetryAsync(httpClient, mediaUrl);
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
{
NotifyUser("URL Not Found", $"The URL '{mediaUrl}' was not found.");
continue;
}
using (var fileStream = System.IO.File.Create(tempFilePath))
{
await response.Content.CopyToAsync(fileStream);
}
if (System.IO.File.Exists(jsonFilePath))
{
NotifyUser("Image already compressed", "The image has already been optimized.");
continue;
}
NotifyUser("Starting image optimization", $"Starting optimization for image '{mediaItem.Name}'");
_logger.LogInformation("Calling CompressImage for: {TempFilePath}", tempFilePath);
await CompressImage(tempFilePath, jsonFilePath);
_logger.LogInformation("Finished calling CompressImage for: {TempFilePath}", tempFilePath);
// Upload the optimized image
using (var fileStream = System.IO.File.OpenRead(tempFilePath))
{
byte[] fileBytesArray;
using (var memoryStream = new MemoryStream())
{
await fileStream.CopyToAsync(memoryStream);
fileBytesArray = memoryStream.ToArray();
}
var fileBytes = new ByteArrayContent(fileBytesArray);
var uploadResponse = await httpClient.PutAsync(mediaUrl, fileBytes);
if (!uploadResponse.IsSuccessStatusCode)
{
NotifyUser("Upload Error", $"Failed to upload optimized image.");
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing media item for: {MediaItemName}", mediaItem.Name);
NotifyUser("Error", $"Error processing media item: {ex.Message}");
}
finally
{
// Clean up temporary files
try
{
if (System.IO.File.Exists(tempFilePath))
{
//System.IO.File.Delete(tempFilePath);
_logger.LogInformation("Deleted temporary file: {TempFilePath}", tempFilePath);
}
if (System.IO.File.Exists(jsonFilePath))
{
//System.IO.File.Delete(jsonFilePath);
_logger.LogInformation("Deleted JSON file: {JsonFilePath}", jsonFilePath);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error deleting temporary files.");
}
}
}
}
}
}
private string ExtractUrlFromJson(string json)
{
try
{
var jsonObject = JObject.Parse(json);
var url = jsonObject["src"]?.ToString();
return url ?? string.Empty;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error parsing JSON for media URL");
return string.Empty;
}
}
private async Task<HttpResponseMessage> GetWithRetryAsync(HttpClient httpClient, string url, int maxRetries = 3)
{
for (int retry = 0; retry < maxRetries; retry++)
{
try
{
var response = await httpClient.GetAsync(url);
response.EnsureSuccessStatusCode(); // Throws if not success code.
return response;
}
catch (HttpRequestException ex)
{
if (retry == maxRetries - 1) // Last retry
{
_logger.LogError(ex, "Error fetching URL: {Url}", url);
throw;
}
await Task.Delay(1000); // Wait before retrying
}
}
throw new Exception("Failed to fetch URL after multiple retries.");
}
private async Task CompressImage(string filePath, string jsonFilePath)
{
try
{
var originalSize = new FileInfo(filePath).Length;
var source = Tinify.FromFile(filePath);
await source.ToFile(filePath);
var compressedSize = new FileInfo(filePath).Length;
var percentageReduction = (double)(originalSize - compressedSize) / originalSize * 100;
var compressionInfo = new
{
OriginalSize = originalSize,
CompressedSize = compressedSize,
PercentageReduction = Math.Round(percentageReduction, 2)
};
var json = JObject.FromObject(compressionInfo);
await System.IO.File.WriteAllTextAsync(jsonFilePath, json.ToString());
NotifyUser("Image optimization complete", $"Optimization for image completed successfully.");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error compressing image");
NotifyUser("Image optimization error", $"Error optimizing image: {ex.Message}");
}
}
private void NotifyUser(string title, string message)
{
var eventMessages = _eventMessagesFactory.Get();
eventMessages.Add(new EventMessage(title, message, EventMessageType.Success)); // or .Error if needed
}
}
}
How to overwrite a media file with a locale, physical file so it works both with blob and non-blob storage
I can't get Cogworks.Tinifier package working on Umbraco v13 (and have got no solutions here: https://www.facebook.com/groups/umbracowebdevs/posts/2091914831210397/). Therefore I have tried to make my own solution (see code below).
It works a part of the way: When user uploads an image TinifyAPI optimizes it and saves the optimized image in a temporary location (like C:\Users\marti\AppData\Local\Temp\tmpsxjjxj.tmp).
But the part of the code that should overwrite the uploaded, non-optimized image (which is my goal - I don't want the original non-optimized image since it consumes server disc space) does not work. I.e. this part:
No error, but also no file is overwritten.
My full code:
is working on a reply...