Skip to content

Commit

Permalink
Refactor TwitchHelper.GetImage
Browse files Browse the repository at this point in the history
  • Loading branch information
ScrubN committed Jun 17, 2024
1 parent 611756e commit b8f0d79
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 59 deletions.
6 changes: 3 additions & 3 deletions TwitchDownloaderCore/Tools/HighlightIcons.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public sealed class HighlightIcons : IDisposable
private SKImage _watchStreakIcon;
private SKImage _charityDonationIcon;

private readonly string _cachePath;
private readonly DirectoryInfo _cacheDir;
private readonly SKColor _purple;
private readonly bool _offline;
private readonly double _fontSize;
Expand All @@ -66,7 +66,7 @@ public sealed class HighlightIcons : IDisposable

public HighlightIcons(ChatRenderOptions renderOptions, SKColor iconPurple, SKPaint outlinePaint)
{
_cachePath = Path.Combine(renderOptions.TempFolder, "icons");
_cacheDir = new DirectoryInfo(Path.Combine(renderOptions.TempFolder, "icons"));
_purple = iconPurple;
_offline = renderOptions.Offline;
_fontSize = renderOptions.FontSize;
Expand Down Expand Up @@ -184,7 +184,7 @@ private SKImage GenerateGiftedManyIcon()
return SKImage.FromBitmap(offlineBitmap);
}

var taskIconBytes = TwitchHelper.GetImage(_cachePath, GIFTED_MANY_ICON_URL, "gift-illus", "3", "png");
var taskIconBytes = TwitchHelper.GetImage(_cacheDir, GIFTED_MANY_ICON_URL, "gift-illus", 3, "png", StubTaskProgress.Instance);
taskIconBytes.Wait();
using var ms = new MemoryStream(taskIconBytes.Result); // Illustration is 72x72
using var codec = SKCodec.Create(ms);
Expand Down
112 changes: 56 additions & 56 deletions TwitchDownloaderCore/TwitchHelper.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
Expand Down Expand Up @@ -389,9 +389,9 @@ public static async Task<List<TwitchEmote>> GetThirdPartyEmotes(List<Comment> co
return returnList;
}

string bttvFolder = Path.Combine(cacheFolder, "bttv");
string ffzFolder = Path.Combine(cacheFolder, "ffz");
string stvFolder = Path.Combine(cacheFolder, "stv");
DirectoryInfo bttvFolder = new DirectoryInfo(Path.Combine(cacheFolder, "bttv"));
DirectoryInfo ffzFolder = new DirectoryInfo(Path.Combine(cacheFolder, "ffz"));
DirectoryInfo stvFolder = new DirectoryInfo(Path.Combine(cacheFolder, "stv"));

EmoteResponse emoteDataResponse = await GetThirdPartyEmotesMetadata(streamerId, bttv, ffz, stv, allowUnlistedEmotes, logger, cancellationToken);

Expand Down Expand Up @@ -434,10 +434,10 @@ public static async Task<List<TwitchEmote>> GetThirdPartyEmotes(List<Comment> co
return returnList;

static async Task FetchEmoteImages(IReadOnlyCollection<Comment> comments, IEnumerable<EmoteResponseItem> emoteResponse, ICollection<TwitchEmote> returnList,
ICollection<string> alreadyAdded, string cacheFolder, ITaskLogger logger, CancellationToken cancellationToken)
ICollection<string> alreadyAdded, DirectoryInfo cacheFolder, ITaskLogger logger, CancellationToken cancellationToken)
{
if (!Directory.Exists(cacheFolder))
CreateDirectory(cacheFolder);
if (!cacheFolder.Exists)
cacheFolder = CreateDirectory(cacheFolder.FullName);

IEnumerable<EmoteResponseItem> emoteResponseQuery;
if (comments.Count == 0)
Expand All @@ -457,7 +457,7 @@ where comments.Any(comment => Regex.IsMatch(comment.message.body, pattern))
{
try
{
var imageData = await GetImage(cacheFolder, emote.ImageUrl.Replace("[scale]", "2"), emote.Id, "2", emote.ImageType, cancellationToken);
var imageData = await GetImage(cacheFolder, emote.ImageUrl.Replace("[scale]", "2"), emote.Id, 2, emote.ImageType, logger, cancellationToken);
var newEmote = new TwitchEmote(imageData, EmoteProvider.ThirdParty, 2, emote.Id, emote.Code);
newEmote.IsZeroWidth = emote.IsZeroWidth;

Expand All @@ -478,9 +478,9 @@ public static async Task<List<TwitchEmote>> GetEmotes(List<Comment> comments, st
List<string> alreadyAdded = new List<string>();
List<string> failedEmotes = new List<string>();

string emoteFolder = Path.Combine(cacheFolder, "emotes");
if (!Directory.Exists(emoteFolder))
TwitchHelper.CreateDirectory(emoteFolder);
DirectoryInfo emoteFolder = new DirectoryInfo(Path.Combine(cacheFolder, "emotes"));
if (!emoteFolder.Exists)
emoteFolder = CreateDirectory(emoteFolder.FullName);

// Load our embedded emotes
if (embeddedData?.firstParty != null)
Expand Down Expand Up @@ -518,7 +518,7 @@ public static async Task<List<TwitchEmote>> GetEmotes(List<Comment> comments, st
{
try
{
byte[] bytes = await GetImage(emoteFolder, $"https://static-cdn.jtvnw.net/emoticons/v2/{id}/default/dark/2.0", id, "2", "png", cancellationToken);
byte[] bytes = await GetImage(emoteFolder, $"https://static-cdn.jtvnw.net/emoticons/v2/{id}/default/dark/2.0", id, 2, "png", logger, cancellationToken);
TwitchEmote newEmote = new TwitchEmote(bytes, EmoteProvider.FirstParty, 2, id, id);
alreadyAdded.Add(id);
returnList.Add(newEmote);
Expand Down Expand Up @@ -638,9 +638,9 @@ public static async Task<List<ChatBadge>> GetChatBadges(List<Comment> comments,

List<EmbedChatBadge> badgesData = await GetChatBadgesData(comments, streamerId, cancellationToken);

string badgeFolder = Path.Combine(cacheFolder, "badges");
if (!Directory.Exists(badgeFolder))
TwitchHelper.CreateDirectory(badgeFolder);
DirectoryInfo badgeFolder = new DirectoryInfo(Path.Combine(cacheFolder, "badges"));
if (!badgeFolder.Exists)
badgeFolder = CreateDirectory(badgeFolder.FullName);

foreach(var badge in badgesData)
{
Expand All @@ -654,7 +654,7 @@ public static async Task<List<ChatBadge>> GetChatBadges(List<Comment> comments,
foreach (var (version, data) in badge.versions)
{
string id = data.url.Split('/')[^2];
byte[] bytes = await GetImage(badgeFolder, data.url, id, "2", "png", cancellationToken);
byte[] bytes = await GetImage(badgeFolder, data.url, id, 2, "png", logger, cancellationToken);
versions.Add(version, new ChatBadgeData
{
title = data.title,
Expand Down Expand Up @@ -805,9 +805,9 @@ public static async Task<List<CheerEmote>> GetBits(List<Comment> comments, strin
cheerResponseMessage.EnsureSuccessStatusCode();
var cheerResponse = await cheerResponseMessage.Content.ReadFromJsonAsync<GqlCheerResponse>(cancellationToken: cancellationToken);

string bitFolder = Path.Combine(cacheFolder, "bits");
if (!Directory.Exists(bitFolder))
TwitchHelper.CreateDirectory(bitFolder);
DirectoryInfo bitFolder = new DirectoryInfo(Path.Combine(cacheFolder, "bits"));
if (!bitFolder.Exists)
bitFolder = CreateDirectory(bitFolder.FullName);

if (cheerResponse?.data != null)
{
Expand Down Expand Up @@ -849,7 +849,8 @@ where comments
{
int minBits = tier.bits;
string url = templateURL.Replace("PREFIX", node.prefix.ToLower()).Replace("BACKGROUND", "dark").Replace("ANIMATION", "animated").Replace("TIER", tier.bits.ToString()).Replace("SCALE.EXTENSION", "2.gif");
TwitchEmote emote = new TwitchEmote(await GetImage(bitFolder, url, node.id + tier.bits, "2", "gif", cancellationToken), EmoteProvider.FirstParty, 2, prefix + minBits, prefix + minBits);
var bytes = await GetImage(bitFolder, url, node.id + tier.bits, 2, "gif", logger, cancellationToken);
TwitchEmote emote = new TwitchEmote(bytes, EmoteProvider.FirstParty, 2, prefix + minBits, prefix + minBits);
tierList.Add(new KeyValuePair<int, TwitchEmote>(minBits, emote));
}
returnList.Add(newEmote);
Expand Down Expand Up @@ -1011,65 +1012,64 @@ public static async Task<GqlUserInfoResponse> GetUserInfo(IEnumerable<string> id
return await response.Content.ReadFromJsonAsync<GqlUserInfoResponse>();
}

public static async Task<byte[]> GetImage(string cachePath, string imageUrl, string imageId, string imageScale, string imageType, CancellationToken cancellationToken = new())
public static async Task<byte[]> GetImage(DirectoryInfo cacheDir, string imageUrl, string imageId, int imageScale, string imageType, ITaskLogger logger, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();

byte[] imageBytes = null;
cacheDir.Refresh();
if (!cacheDir.Exists)
{
CreateDirectory(cacheDir.FullName);
cacheDir.Refresh();
}

if (!Directory.Exists(cachePath))
CreateDirectory(cachePath);
byte[] imageBytes;

string filePath = Path.Combine(cachePath!, imageId + "_" + imageScale + "." + imageType);
if (File.Exists(filePath))
var filePath = Path.Combine(cacheDir.FullName, $"{imageId}_{imageScale}.{imageType}");
var file = new FileInfo(filePath);

if (file.Exists)
{
try
{
await using FileStream stream = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
byte[] bytes = new byte[stream.Length];
stream.Seek(0, SeekOrigin.Begin);
_ = await stream.ReadAsync(bytes, cancellationToken);
await using var fs = file.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
imageBytes = new byte[fs.Length];
_ = await fs.ReadAsync(imageBytes, cancellationToken);

//Check if image file is not corrupt
if (bytes.Length > 0)
if (imageBytes.Length > 0)
{
using SKImage image = SKImage.FromEncodedData(bytes);
if (image != null)
using var ms = new MemoryStream(imageBytes);
using var codec = SKCodec.Create(ms, out var result);

if (codec is not null)
{
imageBytes = bytes;
}
else
{
//Try to delete the corrupted image
try
{
await stream.DisposeAsync();
File.Delete(filePath);
}
catch { }
return imageBytes;
}

logger.LogVerbose($"Failed to decode {imageId} from cache: {result}");
}

// Delete the corrupted image
file.Delete();
}
catch (IOException)
catch (Exception e) when (e is IOException or SecurityException)
{
//File being written to by parallel process? Maybe. Can just fallback to HTTP request.
// File being written to by parallel process? Maybe. Can just fallback to HTTP request.
logger.LogVerbose($"Failed to read from or delete {file.Name}: {e.Message}");
}
}

// If fetching from cache failed
if (imageBytes != null)
return imageBytes;

// Fallback to HTTP request
imageBytes = await httpClient.GetByteArrayAsync(imageUrl, cancellationToken);

//Let's save this image to the cache
try
{
await using var stream = File.Open(filePath, FileMode.Create, FileAccess.Write, FileShare.Read);
await stream.WriteAsync(imageBytes, cancellationToken);
await using var fs = file.Open(FileMode.Create, FileAccess.Write, FileShare.Read);
await fs.WriteAsync(imageBytes, cancellationToken);
}
catch (Exception e)
{
logger.LogVerbose($"Failed to open or write to {file.Name}: {e.Message}");
}
catch { }

return imageBytes;
}
Expand Down

0 comments on commit b8f0d79

Please sign in to comment.