diff --git a/CRD/App.axaml b/CRD/App.axaml
index c6ae445..00654fb 100644
--- a/CRD/App.axaml
+++ b/CRD/App.axaml
@@ -4,16 +4,14 @@
xmlns:sty="clr-namespace:FluentAvalonia.Styling;assembly=FluentAvalonia"
x:Class="CRD.App"
RequestedThemeVariant="Dark">
-
+
-
+
-
-
-
+
-
+
diff --git a/CRD/Downloader/CrEpisode.cs b/CRD/Downloader/CrEpisode.cs
index b37c315..9306e0b 100644
--- a/CRD/Downloader/CrEpisode.cs
+++ b/CRD/Downloader/CrEpisode.cs
@@ -47,7 +47,7 @@ public class CrEpisode(){
}
- public CrunchySeriesList EpisodeData(CrunchyEpisodeList dlEpisodes){
+ public async Task EpisodeData(CrunchyEpisodeList dlEpisodes){
bool serieshasversions = true;
Dictionary episodes = new Dictionary();
@@ -56,7 +56,7 @@ public class CrEpisode(){
foreach (var episode in dlEpisodes.Data){
if (crunInstance.CrunOptions.History){
- crunInstance.CrHistory.UpdateWithEpisode(episode);
+ await crunInstance.CrHistory.UpdateWithEpisode(episode);
}
// Prepare the episode array
diff --git a/CRD/Downloader/Crunchyroll.cs b/CRD/Downloader/Crunchyroll.cs
index 6a0d6d7..67d5a51 100644
--- a/CRD/Downloader/Crunchyroll.cs
+++ b/CRD/Downloader/Crunchyroll.cs
@@ -164,7 +164,6 @@ public class Crunchyroll{
RefreshSonarr();
}
-
calendarLanguage = new(){
{ "en-us", "https://www.crunchyroll.com/simulcastcalendar" },
{ "es", "https://www.crunchyroll.com/es/simulcastcalendar" },
@@ -181,8 +180,8 @@ public class Crunchyroll{
}
public async void RefreshSonarr(){
- if (CrunOptions.SonarrProperties != null && !string.IsNullOrEmpty(CrunOptions.SonarrProperties.ApiKey)){
- SonarrClient.Instance.SetApiUrl();
+ await SonarrClient.Instance.CheckSonarrSettings();
+ if (CrunOptions.SonarrProperties is{ SonarrEnabled: true }){
SonarrSeries = await SonarrClient.Instance.GetSeries();
CrHistory.MatchHistorySeriesWithSonarr(true);
}
@@ -221,8 +220,6 @@ public class Crunchyroll{
var dayName = day.SelectSingleNode(".//h1[@class='day-name']/time")?.InnerText.Trim();
- // Console.WriteLine($"Day: {dayName}, Date: {date}");
-
CalendarDay calDay = new CalendarDay();
calDay.CalendarEpisodes = new List();
@@ -242,14 +239,10 @@ public class Crunchyroll{
var episodeLink = episode.SelectSingleNode(".//a[contains(@class, 'available-episode-link')]")?.GetAttributeValue("href", "No link");
var thumbnailUrl = episode.SelectSingleNode(".//img[contains(@class, 'thumbnail')]")?.GetAttributeValue("src", "No image");
var isPremiumOnly = episode.SelectSingleNode(".//svg[contains(@class, 'premium-flag')]") != null;
+ var isPremiere = episode.SelectSingleNode(".//div[contains(@class, 'premiere-flag')]") != null;
var seasonName = episode.SelectSingleNode(".//a[contains(@class, 'js-season-name-link')]")?.SelectSingleNode(".//cite[@itemprop='name']")?.InnerText.Trim();
var episodeNumber = episode.SelectSingleNode(".//meta[contains(@itemprop, 'episodeNumber')]")?.GetAttributeValue("content", "?");
- // Console.WriteLine($" Time: {episodeTime} (Has Passed: {hasPassed}), Episode: {episodeName}");
- // Console.WriteLine($" Season Link: {seasonLink}");
- // Console.WriteLine($" Episode Link: {episodeLink}");
- // Console.WriteLine($" Thumbnail URL: {thumbnailUrl}");
-
CalendarEpisode calEpisode = new CalendarEpisode();
calEpisode.DateTime = episodeTime;
@@ -259,6 +252,7 @@ public class Crunchyroll{
calEpisode.EpisodeUrl = episodeLink;
calEpisode.ThumbnailUrl = thumbnailUrl;
calEpisode.IsPremiumOnly = isPremiumOnly;
+ calEpisode.IsPremiere = isPremiere;
calEpisode.SeasonName = seasonName;
calEpisode.EpisodeNumber = episodeNumber;
@@ -267,7 +261,6 @@ public class Crunchyroll{
}
week.CalendarDays.Add(calDay);
- // Console.WriteLine();
}
} else{
Console.WriteLine("No days found in the HTML document.");
@@ -291,10 +284,19 @@ public class Crunchyroll{
return;
}
- var sList = CrEpisode.EpisodeData((CrunchyEpisodeList)episodeL);
+ var sList = await CrEpisode.EpisodeData((CrunchyEpisodeList)episodeL);
var selected = CrEpisode.EpisodeMeta(sList.Data, dubLang);
var metas = selected.Values.ToList();
+
foreach (var crunchyEpMeta in metas){
+ if (CrunOptions.SonarrProperties is{ SonarrEnabled: true, UseSonarrNumbering: true }){
+ var historyEpisode = CrHistory.GetHistoryEpisode(crunchyEpMeta.ShowId, crunchyEpMeta.SeasonId, crunchyEpMeta.Data.First().MediaId);
+ if (historyEpisode != null){
+ crunchyEpMeta.EpisodeNumber = historyEpisode.SonarrEpisodeNumber;
+ crunchyEpMeta.Season = historyEpisode.SonarrSeasonNumber;
+ }
+ }
+
Queue.Add(crunchyEpMeta);
}
@@ -310,6 +312,14 @@ public class Crunchyroll{
foreach (var crunchyEpMeta in selected.Values.ToList()){
if (crunchyEpMeta.Data?.First().Playback != null){
+ if (CrunOptions.SonarrProperties is{ SonarrEnabled: true, UseSonarrNumbering: true }){
+ var historyEpisode = CrHistory.GetHistoryEpisode(crunchyEpMeta.ShowId, crunchyEpMeta.SeasonId, crunchyEpMeta.Data.First().MediaId);
+ if (historyEpisode != null){
+ crunchyEpMeta.EpisodeNumber = historyEpisode.SonarrEpisodeNumber;
+ crunchyEpMeta.Season = historyEpisode.SonarrSeasonNumber;
+ }
+ }
+
Queue.Add(crunchyEpMeta);
} else{
failed = true;
@@ -362,7 +372,7 @@ public class Crunchyroll{
};
Queue.Refresh();
-
+
await MuxStreams(res.Data,
new CrunchyMuxOptions{
FfmpegOptions = options.FfmpegOptions,
@@ -434,7 +444,7 @@ public class Crunchyroll{
subt.Fonts = downloadedMedia.Fonts;
subsList.Add(subt);
}
-
+
if (File.Exists($"{filename}.{(muxToMp3 ? "mp3" : options.Mp4 ? "mp4" : "mkv")}") && !string.IsNullOrEmpty(filename)){
string newFilePath = filename;
int counter = 1;
@@ -446,7 +456,7 @@ public class Crunchyroll{
filename = newFilePath;
}
-
+
var merger = new Merger(new MergerOptions{
OnlyVid = hasAudioStreams ? data.Where(a => a.Type == DownloadMediaType.Video).Select(a => new MergerInput{ Language = a.Lang, Path = a.Path ?? string.Empty }).ToList() : new List(),
@@ -559,10 +569,11 @@ public class Crunchyroll{
};
}
+
bool dlFailed = false;
bool dlVideoOnce = false;
- if (data.Data != null)
+ if (data.Data != null){
foreach (CrunchyEpMetaData epMeta in data.Data){
Console.WriteLine($"Requesting: [{epMeta.MediaId}] {mediaName}");
@@ -1065,6 +1076,7 @@ public class Crunchyroll{
if (File.Exists($"{tsFile}.video.m4s")){
File.Delete($"{tsFile}.video.m4s");
}
+
File.Move($"{tempTsFile}.video.m4s", $"{tsFile}.video.m4s");
} catch (IOException ex){
Console.WriteLine($"An error occurred: {ex.Message}");
@@ -1111,6 +1123,7 @@ public class Crunchyroll{
if (File.Exists($"{tsFile}.audio.m4s")){
File.Delete($"{tsFile}.audio.m4s");
}
+
File.Move($"{tempTsFile}.audio.m4s", $"{tsFile}.audio.m4s");
} catch (IOException ex){
Console.WriteLine($"An error occurred: {ex.Message}");
@@ -1226,7 +1239,7 @@ public class Crunchyroll{
await Task.Delay(options.Waittime);
}
-
+ }
// variables.Add(new Variable("height", quality == 0 ? plQuality.Last().RESOLUTION.Height : plQuality[quality - 1].RESOLUTION.Height, false));
// variables.Add(new Variable("width", quality == 0 ? plQuality.Last().RESOLUTION.Width : plQuality[quality - 1].RESOLUTION.Width, false));
@@ -1476,7 +1489,7 @@ public class Crunchyroll{
var stream = hardsub.Value;
derivedPlayCrunchyStreams[hardsub.Key] = new StreamDetails{
Url = stream.Url,
- HardsubLocale = Helpers.ConvertStringToLocale(stream.Hlang)
+ HardsubLocale = stream.Hlang
};
}
@@ -1552,7 +1565,7 @@ public class Crunchyroll{
playbackRequestResponse = await HttpClientReq.Instance.SendHttpRequest(playbackRequest);
if (playbackRequestResponse.IsOk){
- // temppbData = Helpers.Deserialize(playbackRequestResponse22.ResponseContent, SettingsJsonSerializerSettings) ??
+ // var temppbData2 = Helpers.Deserialize(playbackRequestResponse22.ResponseContent, SettingsJsonSerializerSettings) ??
// new PlaybackData{ Total = 0, Data = new List>>() };
temppbData = new PlaybackData{ Total = 0, Data = new List>>() };
@@ -1569,7 +1582,7 @@ public class Crunchyroll{
var stream = hardsub.Value;
derivedPlayCrunchyStreams[hardsub.Key] = new StreamDetails{
Url = stream.Url,
- HardsubLocale = Helpers.ConvertStringToLocale(stream.Hlang)
+ HardsubLocale = stream.Hlang
};
}
@@ -1578,7 +1591,7 @@ public class Crunchyroll{
HardsubLocale = Locale.DefaulT
};
- if (temppbData.Data != null) temppbData.Data[0]["drm_adaptive_dash"] = derivedPlayCrunchyStreams;
+ if (temppbData.Data != null) temppbData.Data[0]["drm_adaptive_switch_dash"] = derivedPlayCrunchyStreams;
temppbData.Meta = new PlaybackMeta(){ AudioLocale = playStream.AudioLocale, Versions = playStream.Versions, Bifs = new List{ playStream.Bifs }, MediaId = mediaId };
temppbData.Meta.Subtitles = new Subtitles();
diff --git a/CRD/Downloader/History.cs b/CRD/Downloader/History.cs
index 8bad1f6..886f44a 100644
--- a/CRD/Downloader/History.cs
+++ b/CRD/Downloader/History.cs
@@ -22,7 +22,7 @@ public class History(){
public async Task UpdateSeries(string seriesId, string? seasonId){
await crunInstance.CrAuth.RefreshToken(true);
- CrSeriesSearch? parsedSeries = await crunInstance.CrSeries.ParseSeriesById(seriesId, "ja");
+ CrSeriesSearch? parsedSeries = await crunInstance.CrSeries.ParseSeriesById(seriesId, "ja");
if (parsedSeries == null){
Console.WriteLine("Parse Data Invalid");
@@ -79,11 +79,30 @@ public class History(){
MessageBus.Current.SendMessage(new ToastMessage($"Couldn't update download History", ToastType.Warning, 1));
}
+
+ public HistoryEpisode? GetHistoryEpisode(string? seriesId, string? seasonId, string episodeId){
+ var historySeries = crunInstance.HistoryList.FirstOrDefault(series => series.SeriesId == seriesId);
+
+ if (historySeries != null){
+ var historySeason = historySeries.Seasons.Find(s => s.SeasonId == seasonId);
+
+ if (historySeason != null){
+ var historyEpisode = historySeason.EpisodesList.Find(e => e.EpisodeId == episodeId);
+
+ if (historyEpisode != null){
+
+ return historyEpisode;
+ }
+ }
+ }
+
+ return null;
+ }
- public async void UpdateWithEpisode(CrunchyEpisode episodeParam){
+ public async Task UpdateWithEpisode(CrunchyEpisode episodeParam){
var episode = episodeParam;
-
+
if (episode.Versions != null){
var version = episode.Versions.Find(a => a.Original);
if (version.AudioLocale != episode.AudioLocale){
@@ -137,23 +156,23 @@ public class History(){
historySeries.UpdateNewEpisodes();
} else{
- var newHistorySeries = new HistorySeries{
+ historySeries = new HistorySeries{
SeriesTitle = episode.SeriesTitle,
SeriesId = episode.SeriesId,
Seasons =[],
};
- crunInstance.HistoryList.Add(newHistorySeries);
+ crunInstance.HistoryList.Add(historySeries);
var newSeason = NewHistorySeason(episode);
var series = await crunInstance.CrSeries.SeriesById(seriesId);
if (series?.Data != null){
- newHistorySeries.SeriesDescription = series.Data.First().Description;
- newHistorySeries.ThumbnailImageUrl = GetSeriesThumbnail(series);
- newHistorySeries.SeriesTitle = series.Data.First().Title;
+ historySeries.SeriesDescription = series.Data.First().Description;
+ historySeries.ThumbnailImageUrl = GetSeriesThumbnail(series);
+ historySeries.SeriesTitle = series.Data.First().Title;
}
- newHistorySeries.Seasons.Add(newSeason);
- newHistorySeries.UpdateNewEpisodes();
+ historySeries.Seasons.Add(newSeason);
+ historySeries.UpdateNewEpisodes();
}
var sortedList = crunInstance.HistoryList.OrderBy(item => item.SeriesTitle).ToList();
@@ -163,10 +182,11 @@ public class History(){
}
MatchHistorySeriesWithSonarr(false);
+ await MatchHistoryEpisodesWithSonarr(false,historySeries);
UpdateHistoryFile();
}
- public async void UpdateWithSeasonData(CrunchyEpisodeList seasonData){
+ public async Task UpdateWithSeasonData(CrunchyEpisodeList seasonData){
if (seasonData.Data != null){
var firstEpisode = seasonData.Data.First();
var seriesId = firstEpisode.SeriesId;
@@ -203,7 +223,6 @@ public class History(){
historyEpisode.EpisodeId = crunchyEpisode.Id;
historyEpisode.Episode = crunchyEpisode.Episode;
historyEpisode.EpisodeSeasonNum = Helpers.ExtractNumberAfterS(crunchyEpisode.Identifier) ?? crunchyEpisode.SeasonNumber + "";
-
}
}
@@ -220,12 +239,12 @@ public class History(){
historySeries.UpdateNewEpisodes();
} else{
- var newHistorySeries = new HistorySeries{
+ historySeries = new HistorySeries{
SeriesTitle = firstEpisode.SeriesTitle,
SeriesId = firstEpisode.SeriesId,
Seasons =[],
};
- crunInstance.HistoryList.Add(newHistorySeries);
+ crunInstance.HistoryList.Add(historySeries);
var newSeason = NewHistorySeason(seasonData, firstEpisode);
@@ -233,26 +252,26 @@ public class History(){
var series = await crunInstance.CrSeries.SeriesById(seriesId);
if (series?.Data != null){
- newHistorySeries.SeriesDescription = series.Data.First().Description;
- newHistorySeries.ThumbnailImageUrl = GetSeriesThumbnail(series);
- newHistorySeries.SeriesTitle = series.Data.First().Title;
+ historySeries.SeriesDescription = series.Data.First().Description;
+ historySeries.ThumbnailImageUrl = GetSeriesThumbnail(series);
+ historySeries.SeriesTitle = series.Data.First().Title;
}
- newHistorySeries.Seasons.Add(newSeason);
+ historySeries.Seasons.Add(newSeason);
- newHistorySeries.UpdateNewEpisodes();
+ historySeries.UpdateNewEpisodes();
+ }
+ var sortedList = crunInstance.HistoryList.OrderBy(item => item.SeriesTitle).ToList();
+ crunInstance.HistoryList.Clear();
+ foreach (var item in sortedList){
+ crunInstance.HistoryList.Add(item);
}
- }
- var sortedList = crunInstance.HistoryList.OrderBy(item => item.SeriesTitle).ToList();
- crunInstance.HistoryList.Clear();
- foreach (var item in sortedList){
- crunInstance.HistoryList.Add(item);
+ MatchHistorySeriesWithSonarr(false);
+ await MatchHistoryEpisodesWithSonarr(false,historySeries);
+ UpdateHistoryFile();
}
-
- MatchHistorySeriesWithSonarr(false);
- UpdateHistoryFile();
}
private string GetSeriesThumbnail(CrSeriesBase series){
@@ -322,6 +341,11 @@ public class History(){
}
public void MatchHistorySeriesWithSonarr(bool updateAll){
+
+ if (crunInstance.CrunOptions.SonarrProperties is{ SonarrEnabled: false }){
+ return;
+ }
+
foreach (var historySeries in crunInstance.HistoryList){
if (updateAll || string.IsNullOrEmpty(historySeries.SonarrSeriesId)){
var sonarrSeries = FindClosestMatch(historySeries.SeriesTitle);
@@ -334,7 +358,11 @@ public class History(){
}
}
- public async void MatchHistoryEpisodesWithSonarr(bool updateAll, HistorySeries historySeries){
+ public async Task MatchHistoryEpisodesWithSonarr(bool updateAll, HistorySeries historySeries){
+ if (crunInstance.CrunOptions.SonarrProperties is{ SonarrEnabled: false }){
+ return;
+ }
+
if (!string.IsNullOrEmpty(historySeries.SonarrSeriesId)){
var episodes = await SonarrClient.Instance.GetEpisodes(int.Parse(historySeries.SonarrSeriesId));
@@ -345,7 +373,7 @@ public class History(){
}
List failedEpisodes =[];
-
+
foreach (var historyEpisode in allHistoryEpisodes){
if (updateAll || string.IsNullOrEmpty(historyEpisode.SonarrEpisodeId)){
var episode = FindClosestMatchEpisodes(episodes, historyEpisode.EpisodeTitle);
@@ -372,8 +400,9 @@ public class History(){
historyEpisode.SonarrSeasonNumber = episode.SeasonNumber + "";
episodes.Remove(episode);
} else{
- var episode1 = episodes.Find(ele => !string.IsNullOrEmpty(historyEpisode.EpisodeDescription) && !string.IsNullOrEmpty(ele.Overview) && Helpers.CalculateCosineSimilarity(ele.Overview, historyEpisode.EpisodeDescription) > 0.8);
-
+ var episode1 = episodes.Find(ele =>
+ !string.IsNullOrEmpty(historyEpisode.EpisodeDescription) && !string.IsNullOrEmpty(ele.Overview) && Helpers.CalculateCosineSimilarity(ele.Overview, historyEpisode.EpisodeDescription) > 0.8);
+
if (episode1 != null){
historyEpisode.SonarrEpisodeId = episode1.Id + "";
historyEpisode.SonarrEpisodeNumber = episode1.EpisodeNumber + "";
@@ -395,11 +424,7 @@ public class History(){
}
}
}
-
}
-
-
-
}
}
@@ -442,7 +467,8 @@ public class History(){
return 1.0 - (double)distance / Math.Max(source.Length, target.Length);
}
- private int LevenshteinDistance(string source, string target){
+
+ public int LevenshteinDistance(string source, string target){
if (string.IsNullOrEmpty(source)){
return string.IsNullOrEmpty(target) ? 0 : target.Length;
}
@@ -454,35 +480,32 @@ public class History(){
int n = source.Length;
int m = target.Length;
- // Create two work arrays of integer distances.
- int[] previousDistances = new int[m + 1];
- int[] currentDistances = new int[m + 1];
+ // Use a single array for distances.
+ int[] distances = new int[m + 1];
- // Initialize the previous distance array.
+ // Initialize the distance array.
for (int j = 0; j <= m; j++){
- previousDistances[j] = j;
+ distances[j] = j;
}
for (int i = 1; i <= n; i++){
- // Initialize the current distance array.
- currentDistances[0] = i;
+ int previousDiagonal = distances[0];
+ distances[0] = i;
for (int j = 1; j <= m; j++){
+ int previousDistance = distances[j];
int cost = (target[j - 1] == source[i - 1]) ? 0 : 1;
- currentDistances[j] = Math.Min(
- Math.Min(currentDistances[j - 1] + 1, previousDistances[j] + 1),
- previousDistances[j - 1] + cost);
- }
+ distances[j] = Math.Min(
+ Math.Min(distances[j - 1] + 1, distances[j] + 1),
+ previousDiagonal + cost);
- // Swap the arrays for the next iteration.
- var temp = previousDistances;
- previousDistances = currentDistances;
- currentDistances = temp;
+ previousDiagonal = previousDistance;
+ }
}
- // The final distance is in the previous distance array.
- return previousDistances[m];
+ // The final distance is in the last cell.
+ return distances[m];
}
}
@@ -530,6 +553,9 @@ public class HistorySeries : INotifyPropertyChanged{
public event PropertyChangedEventHandler? PropertyChanged;
+ [JsonIgnore]
+ public bool FetchingData{ get; set; }
+
public async Task LoadImage(){
try{
using (var client = new HttpClient()){
@@ -574,6 +600,11 @@ public class HistorySeries : INotifyPropertyChanged{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NewEpisodes)));
}
+ public void SetFetchingData(){
+ FetchingData = true;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FetchingData)));
+ }
+
public async Task AddNewMissingToDownloads(){
bool foundWatched = false;
@@ -600,7 +631,12 @@ public class HistorySeries : INotifyPropertyChanged{
}
public async Task FetchData(string? seasonId){
+ FetchingData = true;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FetchingData)));
await Crunchyroll.Instance.CrHistory.UpdateSeries(SeriesId, seasonId);
+ FetchingData = false;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FetchingData)));
+ Crunchyroll.Instance.CrHistory.MatchHistoryEpisodesWithSonarr(false,this);
}
}
@@ -654,10 +690,10 @@ public partial class HistoryEpisode : INotifyPropertyChanged{
[JsonProperty("episode_cr_episode_number")]
public string? Episode{ get; set; }
-
+
[JsonProperty("episode_cr_episode_description")]
public string? EpisodeDescription{ get; set; }
-
+
[JsonProperty("episode_cr_season_number")]
public string? EpisodeSeasonNum{ get; set; }
diff --git a/CRD/Utils/Files/CfgManager.cs b/CRD/Utils/Files/CfgManager.cs
index 1c25349..5bc0fde 100644
--- a/CRD/Utils/Files/CfgManager.cs
+++ b/CRD/Utils/Files/CfgManager.cs
@@ -146,12 +146,9 @@ public class CfgManager{
}
private static object fileLock = new object();
-
+
public static void WriteJsonToFile(string pathToFile, object obj){
try{
- // Serialize the object to a JSON string.
- var jsonString = JsonConvert.SerializeObject(obj, Formatting.Indented);
-
// Check if the directory exists; if not, create it.
string directoryPath = Path.GetDirectoryName(pathToFile);
if (!Directory.Exists(directoryPath)){
@@ -159,14 +156,19 @@ public class CfgManager{
}
lock (fileLock){
- // Write the JSON string to file. Creates the file if it does not exist.
- File.WriteAllText(pathToFile, jsonString);
+ // Write the JSON string to file using a streaming approach.
+ using (var fileStream = new FileStream(pathToFile, FileMode.Create, FileAccess.Write))
+ using (var streamWriter = new StreamWriter(fileStream))
+ using (var jsonWriter = new JsonTextWriter(streamWriter){ Formatting = Formatting.Indented }){
+ var serializer = new JsonSerializer();
+ serializer.Serialize(jsonWriter, obj);
+ }
}
} catch (Exception ex){
Console.WriteLine($"An error occurred: {ex.Message}");
}
}
-
+
public static bool CheckIfFileExists(string filePath){
string dirPath = Path.GetDirectoryName(filePath) ?? string.Empty;
diff --git a/CRD/Utils/HLS/HLSDownloader.cs b/CRD/Utils/HLS/HLSDownloader.cs
index 07b65da..7f0d66e 100644
--- a/CRD/Utils/HLS/HLSDownloader.cs
+++ b/CRD/Utils/HLS/HLSDownloader.cs
@@ -421,7 +421,7 @@ public class HlsDownloader{
try{
response = await HttpClientReq.Instance.GetHttpClient().SendAsync(request, HttpCompletionOption.ResponseContentRead);
response.EnsureSuccessStatusCode();
- return await response.Content.ReadAsByteArrayAsync();
+ return await ReadContentAsByteArrayAsync(response.Content);
} catch (HttpRequestException ex){
// Log retry attempts
string partType = isKey ? "Key" : "Part";
@@ -437,6 +437,14 @@ public class HlsDownloader{
return null; // Should not reach here
}
+ private async Task ReadContentAsByteArrayAsync(HttpContent content){
+ using (var memoryStream = new MemoryStream())
+ using (var contentStream = await content.ReadAsStreamAsync()){
+ await contentStream.CopyToAsync(memoryStream, 81920);
+ return memoryStream.ToArray();
+ }
+ }
+
private HttpRequestMessage CloneHttpRequestMessage(HttpRequestMessage originalRequest){
var clone = new HttpRequestMessage(originalRequest.Method, originalRequest.RequestUri){
Content = originalRequest.Content?.Clone(),
diff --git a/CRD/Utils/Helpers.cs b/CRD/Utils/Helpers.cs
index b8f8a52..312c226 100644
--- a/CRD/Utils/Helpers.cs
+++ b/CRD/Utils/Helpers.cs
@@ -38,7 +38,11 @@ public class Helpers{
}
}
- return Locale.DefaulT; // Return default if not found
+ if (string.IsNullOrEmpty(value)){
+ return Locale.DefaulT;
+ }
+
+ return Locale.Unknown; // Return default if not found
}
public static string GenerateSessionId(){
@@ -88,22 +92,44 @@ public class Helpers{
return CosineSimilarity(vector1, vector2);
}
- private static Dictionary ComputeWordFrequency(string text){
- var wordFrequency = new Dictionary();
- var words = text.Split(new[]{ ' ', ',', '.', ';', ':', '-', '_', '\'' }, StringSplitOptions.RemoveEmptyEntries);
+ private static readonly char[] Delimiters ={ ' ', ',', '.', ';', ':', '-', '_', '\'' };
+
+ public static Dictionary ComputeWordFrequency(string text){
+ var wordFrequency = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ var words = SplitText(text);
foreach (var word in words){
- var lowerWord = word.ToLower();
- if (!wordFrequency.ContainsKey(lowerWord)){
- wordFrequency[lowerWord] = 0;
+ if (wordFrequency.TryGetValue(word, out double count)){
+ wordFrequency[word] = count + 1;
+ } else{
+ wordFrequency[word] = 1;
}
-
- wordFrequency[lowerWord]++;
}
return wordFrequency;
}
+ private static List SplitText(string text){
+ var words = new List();
+ int start = 0;
+ for (int i = 0; i < text.Length; i++){
+ if (Array.IndexOf(Delimiters, text[i]) >= 0){
+ if (i > start){
+ words.Add(text.Substring(start, i - start));
+ }
+
+ start = i + 1;
+ }
+ }
+
+ if (start < text.Length){
+ words.Add(text.Substring(start));
+ }
+
+ return words;
+ }
+
+
private static double CosineSimilarity(Dictionary vector1, Dictionary vector2){
var intersection = vector1.Keys.Intersect(vector2.Keys);
diff --git a/CRD/Utils/Sonarr/SonarrClient.cs b/CRD/Utils/Sonarr/SonarrClient.cs
index 3e8496b..8a578cb 100644
--- a/CRD/Utils/Sonarr/SonarrClient.cs
+++ b/CRD/Utils/Sonarr/SonarrClient.cs
@@ -16,7 +16,7 @@ using Newtonsoft.Json;
namespace CRD.Utils.Sonarr;
public class SonarrClient{
- private string apiUrl;
+ private string? apiUrl;
private HttpClient httpClient;
@@ -50,11 +50,39 @@ public class SonarrClient{
public void SetApiUrl(){
if (Crunchyroll.Instance.CrunOptions.SonarrProperties != null) properties = Crunchyroll.Instance.CrunOptions.SonarrProperties;
- if (properties != null){
- apiUrl = $"http{(properties.UseSsl ? "s" : "")}://{properties.Host}:{properties.Port}{(properties.UrlBase ?? "")}/api";
+ if (properties != null ){
+ apiUrl = $"http{(properties.UseSsl ? "s" : "")}://{(!string.IsNullOrEmpty(properties.Host) ? properties.Host : "localhost")}:{properties.Port}{(properties.UrlBase ?? "")}/api";
}
}
+ public async Task CheckSonarrSettings(){
+
+ SetApiUrl();
+
+ if (Crunchyroll.Instance.CrunOptions.SonarrProperties != null){
+ Crunchyroll.Instance.CrunOptions.SonarrProperties.SonarrEnabled = false;
+ } else{
+ Crunchyroll.Instance.CrunOptions.SonarrProperties = new SonarrProperties(){SonarrEnabled = false};
+ return;
+ }
+
+ Debug.WriteLine($"[DEBUG] [SonarrClient.CheckSonarrSettings] Endpoint URL: '{apiUrl}'");
+
+ var request = CreateRequestMessage($"{apiUrl}", HttpMethod.Get);
+ HttpResponseMessage response;
+
+ try{
+ response = await httpClient.SendAsync(request);
+ response.EnsureSuccessStatusCode();
+ if (Crunchyroll.Instance.CrunOptions.SonarrProperties != null) Crunchyroll.Instance.CrunOptions.SonarrProperties.SonarrEnabled = true;
+ } catch (Exception ex){
+ Debug.WriteLine($"[ERROR] [SonarrClient.GetJson] Endpoint URL: '{apiUrl}', {ex}");
+ if (Crunchyroll.Instance.CrunOptions.SonarrProperties != null) Crunchyroll.Instance.CrunOptions.SonarrProperties.SonarrEnabled = false;
+ }
+
+
+ }
+
public async Task> GetSeries(){
var json = await GetJson($"/v3/series{(true ? $"?includeSeasonImages={true}" : "")}");
@@ -151,4 +179,7 @@ public class SonarrProperties(){
public bool UseSsl{ get; set; }
public string? UrlBase{ get; set; }
+
+ public bool UseSonarrNumbering{ get; set; }
+ public bool SonarrEnabled{ get; set; }
}
\ No newline at end of file
diff --git a/CRD/Utils/Structs/CalendarStructs.cs b/CRD/Utils/Structs/CalendarStructs.cs
index 6b520ed..a6178bc 100644
--- a/CRD/Utils/Structs/CalendarStructs.cs
+++ b/CRD/Utils/Structs/CalendarStructs.cs
@@ -7,8 +7,6 @@ using System.Threading.Tasks;
using Avalonia.Media.Imaging;
using CommunityToolkit.Mvvm.Input;
using CRD.Downloader;
-using CRD.Views;
-using ReactiveUI;
namespace CRD.Utils.Structs;
@@ -36,6 +34,7 @@ public partial class CalendarEpisode : INotifyPropertyChanged{
public string? EpisodeNumber{ get; set; }
public bool IsPremiumOnly{ get; set; }
+ public bool IsPremiere{ get; set; }
public string? SeasonName{ get; set; }
@@ -51,7 +50,7 @@ public partial class CalendarEpisode : INotifyPropertyChanged{
Crunchyroll.Instance.AddEpisodeToQue(id, locale, Crunchyroll.Instance.CrunOptions.DubLang);
}
}
-
+
public async Task LoadImage(){
try{
using (var client = new HttpClient()){
diff --git a/CRD/Utils/Structs/CrunchyStreamData.cs b/CRD/Utils/Structs/CrunchyStreamData.cs
index 335faa6..74cd577 100644
--- a/CRD/Utils/Structs/CrunchyStreamData.cs
+++ b/CRD/Utils/Structs/CrunchyStreamData.cs
@@ -25,7 +25,7 @@ public class Caption{
}
public class HardSub{
- public string? Hlang{ get; set; }
+ public Locale? Hlang{ get; set; }
public string? Url{ get; set; }
public string? Quality{ get; set; }
}
diff --git a/CRD/Utils/Structs/EpisodeStructs.cs b/CRD/Utils/Structs/EpisodeStructs.cs
index 49c170d..c1cacb3 100644
--- a/CRD/Utils/Structs/EpisodeStructs.cs
+++ b/CRD/Utils/Structs/EpisodeStructs.cs
@@ -11,24 +11,55 @@ public struct CrunchyEpisodeList{
}
public struct CrunchyEpisode{
- [JsonProperty("next_episode_id")] public string NextEpisodeId{ get; set; }
- [JsonProperty("series_id")] public string SeriesId{ get; set; }
- [JsonProperty("season_number")] public int SeasonNumber{ get; set; }
- [JsonProperty("next_episode_title")] public string NextEpisodeTitle{ get; set; }
- [JsonProperty("availability_notes")] public string AvailabilityNotes{ get; set; }
- [JsonProperty("duration_ms")] public int DurationMs{ get; set; }
- [JsonProperty("series_slug_title")] public string SeriesSlugTitle{ get; set; }
- [JsonProperty("series_title")] public string SeriesTitle{ get; set; }
- [JsonProperty("is_dubbed")] public bool IsDubbed{ get; set; }
- public List? Versions{ get; set; } // Assume Version is defined elsewhere.
+ [JsonProperty("next_episode_id")]
+ public string NextEpisodeId{ get; set; }
+
+ [JsonProperty("series_id")]
+ public string SeriesId{ get; set; }
+
+ [JsonProperty("season_number")]
+ public int SeasonNumber{ get; set; }
+
+ [JsonProperty("next_episode_title")]
+ public string NextEpisodeTitle{ get; set; }
+
+ [JsonProperty("availability_notes")]
+ public string AvailabilityNotes{ get; set; }
+
+ [JsonProperty("duration_ms")]
+ public int DurationMs{ get; set; }
+
+ [JsonProperty("series_slug_title")]
+ public string SeriesSlugTitle{ get; set; }
+
+ [JsonProperty("series_title")]
+ public string SeriesTitle{ get; set; }
+
+ [JsonProperty("is_dubbed")]
+ public bool IsDubbed{ get; set; }
+
+ public List? Versions{ get; set; }
public string Identifier{ get; set; }
- [JsonProperty("sequence_number")] public float SequenceNumber{ get; set; }
- [JsonProperty("eligible_region")] public string EligibleRegion{ get; set; }
- [JsonProperty("availability_starts")] public DateTime? AvailabilityStarts{ get; set; }
- public Images? Images{ get; set; } // Assume Images is a struct or class you've defined elsewhere.
- [JsonProperty("season_id")] public string SeasonId{ get; set; }
- [JsonProperty("seo_title")] public string SeoTitle{ get; set; }
- [JsonProperty("is_premium_only")] public bool IsPremiumOnly{ get; set; }
+
+ [JsonProperty("sequence_number")]
+ public float SequenceNumber{ get; set; }
+
+ [JsonProperty("eligible_region")]
+ public string EligibleRegion{ get; set; }
+
+ [JsonProperty("availability_starts")]
+ public DateTime? AvailabilityStarts{ get; set; }
+
+ public Images? Images{ get; set; }
+
+ [JsonProperty("season_id")]
+ public string SeasonId{ get; set; }
+
+ [JsonProperty("seo_title")]
+ public string SeoTitle{ get; set; }
+
+ [JsonProperty("is_premium_only")]
+ public bool IsPremiumOnly{ get; set; }
[JsonProperty("extended_maturity_rating")]
public Dictionary ExtendedMaturityRating{ get; set; }
@@ -41,104 +72,119 @@ public struct CrunchyEpisode{
[JsonProperty("premium_available_date")]
public DateTime? PremiumAvailableDate{ get; set; }
- [JsonProperty("season_title")] public string SeasonTitle{ get; set; }
- [JsonProperty("seo_description")] public string SeoDescription{ get; set; }
+ [JsonProperty("season_title")]
+ public string SeasonTitle{ get; set; }
+
+ [JsonProperty("seo_description")]
+ public string SeoDescription{ get; set; }
+
+ [JsonProperty("audio_locale")]
+ public string AudioLocale{ get; set; }
- [JsonProperty("audio_locale")] public string AudioLocale{ get; set; }
public string Id{ get; set; }
- [JsonProperty("media_type")] public MediaType? MediaType{ get; set; } // MediaType should be an enum you define based on possible values.
- [JsonProperty("availability_ends")] public DateTime? AvailabilityEnds{ get; set; }
- [JsonProperty("free_available_date")] public DateTime? FreeAvailableDate{ get; set; }
+
+ [JsonProperty("media_type")]
+ public MediaType? MediaType{ get; set; }
+
+ [JsonProperty("availability_ends")]
+ public DateTime? AvailabilityEnds{ get; set; }
+
+ [JsonProperty("free_available_date")]
+ public DateTime? FreeAvailableDate{ get; set; }
+
public string Playback{ get; set; }
- [JsonProperty("channel_id")] public ChannelId? ChannelId{ get; set; } // ChannelID should be an enum or struct.
+
+ [JsonProperty("channel_id")]
+ public ChannelId? ChannelId{ get; set; }
+
public string? Episode{ get; set; }
- [JsonProperty("is_mature")] public bool IsMature{ get; set; }
- [JsonProperty("listing_id")] public string ListingId{ get; set; }
- [JsonProperty("episode_air_date")] public DateTime? EpisodeAirDate{ get; set; }
+
+ [JsonProperty("is_mature")]
+ public bool IsMature{ get; set; }
+
+ [JsonProperty("listing_id")]
+ public string ListingId{ get; set; }
+
+ [JsonProperty("episode_air_date")]
+ public DateTime? EpisodeAirDate{ get; set; }
+
public string Slug{ get; set; }
- [JsonProperty("available_date")] public DateTime? AvailableDate{ get; set; }
- [JsonProperty("subtitle_locales")] public List SubtitleLocales{ get; set; }
- [JsonProperty("slug_title")] public string SlugTitle{ get; set; }
- [JsonProperty("available_offline")] public bool AvailableOffline{ get; set; }
+
+ [JsonProperty("available_date")]
+ public DateTime? AvailableDate{ get; set; }
+
+ [JsonProperty("subtitle_locales")]
+ public List SubtitleLocales{ get; set; }
+
+ [JsonProperty("slug_title")]
+ public string SlugTitle{ get; set; }
+
+ [JsonProperty("available_offline")]
+ public bool AvailableOffline{ get; set; }
+
public string Description{ get; set; }
- [JsonProperty("is_subbed")] public bool IsSubbed{ get; set; }
- [JsonProperty("premium_date")] public DateTime? PremiumDate{ get; set; }
- [JsonProperty("upload_date")] public DateTime? UploadDate{ get; set; }
- [JsonProperty("season_slug_title")] public string SeasonSlugTitle{ get; set; }
+
+ [JsonProperty("is_subbed")]
+ public bool IsSubbed{ get; set; }
+
+ [JsonProperty("premium_date")]
+ public DateTime? PremiumDate{ get; set; }
+
+ [JsonProperty("upload_date")]
+ public DateTime? UploadDate{ get; set; }
+
+ [JsonProperty("season_slug_title")]
+ public string SeasonSlugTitle{ get; set; }
[JsonProperty("closed_captions_available")]
public bool ClosedCaptionsAvailable{ get; set; }
- [JsonProperty("episode_number")] public int? EpisodeNumber{ get; set; }
- [JsonProperty("season_tags")] public List