mirror of
https://github.com/Crunchy-DL/Crunchy-Downloader.git
synced 2026-03-11 17:45:39 +00:00
Add - Added **Skip Unmonitored Sonarr Episodes** option to settings
Add - Added **Include Crunchyroll Artists (Music)** in history to settings for expanded tracking Add - Added **filters to history tab** to hide series or artists for a cleaner view Add - Added a **toggle to include featured music** in series search results Chg - Made small changes to **sync timing** to more accurately detect delays Chg - Migrated settings to json file Fix - Fixed a **sync timing issue** with longer comparison videos to ensure proper synchronization Fix - Fixed issues with artist urls
This commit is contained in:
parent
6886217dd7
commit
93244a749f
57 changed files with 2311 additions and 1010 deletions
|
|
@ -11,7 +11,6 @@ using CRD.Downloader.Crunchyroll;
|
||||||
using CRD.Utils;
|
using CRD.Utils;
|
||||||
using CRD.Utils.Structs;
|
using CRD.Utils.Structs;
|
||||||
using CRD.Utils.Structs.History;
|
using CRD.Utils.Structs.History;
|
||||||
using DynamicData;
|
|
||||||
using HtmlAgilityPack;
|
using HtmlAgilityPack;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
|
@ -90,56 +89,60 @@ public class CalendarManager{
|
||||||
foreach (var day in dayNodes){
|
foreach (var day in dayNodes){
|
||||||
// Extract the date and day name
|
// Extract the date and day name
|
||||||
var date = day.SelectSingleNode(".//time[@datetime]")?.GetAttributeValue("datetime", "No date");
|
var date = day.SelectSingleNode(".//time[@datetime]")?.GetAttributeValue("datetime", "No date");
|
||||||
DateTime dayDateTime = DateTime.Parse(date, null, DateTimeStyles.RoundtripKind);
|
if (date != null){
|
||||||
|
DateTime dayDateTime = DateTime.Parse(date, null, DateTimeStyles.RoundtripKind);
|
||||||
|
|
||||||
if (week.FirstDayOfWeek == DateTime.MinValue){
|
if (week.FirstDayOfWeek == DateTime.MinValue){
|
||||||
week.FirstDayOfWeek = dayDateTime;
|
week.FirstDayOfWeek = dayDateTime;
|
||||||
week.FirstDayOfWeekString = dayDateTime.ToString("yyyy-MM-dd");
|
week.FirstDayOfWeekString = dayDateTime.ToString("yyyy-MM-dd");
|
||||||
}
|
|
||||||
|
|
||||||
var dayName = day.SelectSingleNode(".//h1[@class='day-name']/time")?.InnerText.Trim();
|
|
||||||
|
|
||||||
CalendarDay calDay = new CalendarDay();
|
|
||||||
|
|
||||||
calDay.CalendarEpisodes = new List<CalendarEpisode>();
|
|
||||||
calDay.DayName = dayName;
|
|
||||||
calDay.DateTime = dayDateTime;
|
|
||||||
|
|
||||||
// Iterate through each episode listed under this day
|
|
||||||
var episodes = day.SelectNodes(".//article[contains(@class, 'release')]");
|
|
||||||
if (episodes != null){
|
|
||||||
foreach (var episode in episodes){
|
|
||||||
var episodeTimeStr = episode.SelectSingleNode(".//time[contains(@class, 'available-time')]")?.GetAttributeValue("datetime", null);
|
|
||||||
DateTime episodeTime = DateTime.Parse(episodeTimeStr, null, DateTimeStyles.RoundtripKind);
|
|
||||||
var hasPassed = DateTime.Now > episodeTime;
|
|
||||||
|
|
||||||
var episodeName = episode.SelectSingleNode(".//h1[contains(@class, 'episode-name')]")?.SelectSingleNode(".//cite[@itemprop='name']")?.InnerText.Trim();
|
|
||||||
var seasonLink = episode.SelectSingleNode(".//a[contains(@class, 'js-season-name-link')]")?.GetAttributeValue("href", "No link");
|
|
||||||
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", "?");
|
|
||||||
|
|
||||||
CalendarEpisode calEpisode = new CalendarEpisode();
|
|
||||||
|
|
||||||
calEpisode.DateTime = episodeTime;
|
|
||||||
calEpisode.HasPassed = hasPassed;
|
|
||||||
calEpisode.EpisodeName = episodeName;
|
|
||||||
calEpisode.SeriesUrl = seasonLink;
|
|
||||||
calEpisode.EpisodeUrl = episodeLink;
|
|
||||||
calEpisode.ThumbnailUrl = thumbnailUrl;
|
|
||||||
calEpisode.IsPremiumOnly = isPremiumOnly;
|
|
||||||
calEpisode.IsPremiere = isPremiere;
|
|
||||||
calEpisode.SeasonName = seasonName;
|
|
||||||
calEpisode.EpisodeNumber = episodeNumber;
|
|
||||||
|
|
||||||
calDay.CalendarEpisodes.Add(calEpisode);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
week.CalendarDays.Add(calDay);
|
var dayName = day.SelectSingleNode(".//h1[@class='day-name']/time")?.InnerText.Trim();
|
||||||
|
|
||||||
|
CalendarDay calDay = new CalendarDay();
|
||||||
|
|
||||||
|
calDay.CalendarEpisodes = new List<CalendarEpisode>();
|
||||||
|
calDay.DayName = dayName;
|
||||||
|
calDay.DateTime = dayDateTime;
|
||||||
|
|
||||||
|
// Iterate through each episode listed under this day
|
||||||
|
var episodes = day.SelectNodes(".//article[contains(@class, 'release')]");
|
||||||
|
if (episodes != null){
|
||||||
|
foreach (var episode in episodes){
|
||||||
|
var episodeTimeStr = episode.SelectSingleNode(".//time[contains(@class, 'available-time')]")?.GetAttributeValue("datetime", null);
|
||||||
|
if (episodeTimeStr != null){
|
||||||
|
DateTime episodeTime = DateTime.Parse(episodeTimeStr, null, DateTimeStyles.RoundtripKind);
|
||||||
|
var hasPassed = DateTime.Now > episodeTime;
|
||||||
|
|
||||||
|
var episodeName = episode.SelectSingleNode(".//h1[contains(@class, 'episode-name')]")?.SelectSingleNode(".//cite[@itemprop='name']")?.InnerText.Trim();
|
||||||
|
var seasonLink = episode.SelectSingleNode(".//a[contains(@class, 'js-season-name-link')]")?.GetAttributeValue("href", "No link");
|
||||||
|
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", "?");
|
||||||
|
|
||||||
|
CalendarEpisode calEpisode = new CalendarEpisode();
|
||||||
|
|
||||||
|
calEpisode.DateTime = episodeTime;
|
||||||
|
calEpisode.HasPassed = hasPassed;
|
||||||
|
calEpisode.EpisodeName = episodeName;
|
||||||
|
calEpisode.SeriesUrl = seasonLink;
|
||||||
|
calEpisode.EpisodeUrl = episodeLink;
|
||||||
|
calEpisode.ThumbnailUrl = thumbnailUrl;
|
||||||
|
calEpisode.IsPremiumOnly = isPremiumOnly;
|
||||||
|
calEpisode.IsPremiere = isPremiere;
|
||||||
|
calEpisode.SeasonName = seasonName;
|
||||||
|
calEpisode.EpisodeNumber = episodeNumber;
|
||||||
|
|
||||||
|
calDay.CalendarEpisodes.Add(calEpisode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
week.CalendarDays.Add(calDay);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else{
|
} else{
|
||||||
Console.Error.WriteLine("No days found in the HTML document.");
|
Console.Error.WriteLine("No days found in the HTML document.");
|
||||||
|
|
@ -260,7 +263,7 @@ public class CalendarManager{
|
||||||
calEpisode.EpisodeName = crBrowseEpisode.Title;
|
calEpisode.EpisodeName = crBrowseEpisode.Title;
|
||||||
calEpisode.SeriesUrl = $"https://www.crunchyroll.com/{CrunchyrollManager.Instance.CrunOptions.HistoryLang}/series/" + crBrowseEpisode.EpisodeMetadata.SeriesId;
|
calEpisode.SeriesUrl = $"https://www.crunchyroll.com/{CrunchyrollManager.Instance.CrunOptions.HistoryLang}/series/" + crBrowseEpisode.EpisodeMetadata.SeriesId;
|
||||||
calEpisode.EpisodeUrl = $"https://www.crunchyroll.com/{CrunchyrollManager.Instance.CrunOptions.HistoryLang}/watch/{crBrowseEpisode.Id}/";
|
calEpisode.EpisodeUrl = $"https://www.crunchyroll.com/{CrunchyrollManager.Instance.CrunOptions.HistoryLang}/watch/{crBrowseEpisode.Id}/";
|
||||||
calEpisode.ThumbnailUrl = crBrowseEpisode.Images.Thumbnail?.FirstOrDefault()?.FirstOrDefault().Source ?? ""; //https://www.crunchyroll.com/i/coming_soon_beta_thumb.jpg
|
calEpisode.ThumbnailUrl = crBrowseEpisode.Images.Thumbnail?.FirstOrDefault()?.FirstOrDefault()?.Source ?? ""; //https://www.crunchyroll.com/i/coming_soon_beta_thumb.jpg
|
||||||
calEpisode.IsPremiumOnly = crBrowseEpisode.EpisodeMetadata.IsPremiumOnly;
|
calEpisode.IsPremiumOnly = crBrowseEpisode.EpisodeMetadata.IsPremiumOnly;
|
||||||
calEpisode.IsPremiere = crBrowseEpisode.EpisodeMetadata.Episode == "1";
|
calEpisode.IsPremiere = crBrowseEpisode.EpisodeMetadata.Episode == "1";
|
||||||
calEpisode.SeasonName = crBrowseEpisode.EpisodeMetadata.SeasonTitle;
|
calEpisode.SeasonName = crBrowseEpisode.EpisodeMetadata.SeasonTitle;
|
||||||
|
|
@ -268,10 +271,10 @@ public class CalendarManager{
|
||||||
calEpisode.CrSeriesID = crBrowseEpisode.EpisodeMetadata.SeriesId;
|
calEpisode.CrSeriesID = crBrowseEpisode.EpisodeMetadata.SeriesId;
|
||||||
|
|
||||||
var existingEpisode = calendarDay.CalendarEpisodes
|
var existingEpisode = calendarDay.CalendarEpisodes
|
||||||
?.FirstOrDefault(e => e.SeasonName == calEpisode.SeasonName);
|
.FirstOrDefault(e => e.SeasonName == calEpisode.SeasonName);
|
||||||
|
|
||||||
if (existingEpisode != null){
|
if (existingEpisode != null){
|
||||||
if (!int.TryParse(existingEpisode.EpisodeNumber, out var num)){
|
if (!int.TryParse(existingEpisode.EpisodeNumber, out _)){
|
||||||
existingEpisode.EpisodeNumber = "...";
|
existingEpisode.EpisodeNumber = "...";
|
||||||
} else{
|
} else{
|
||||||
var existingNumbers = existingEpisode.EpisodeNumber
|
var existingNumbers = existingEpisode.EpisodeNumber
|
||||||
|
|
@ -300,7 +303,7 @@ public class CalendarManager{
|
||||||
|
|
||||||
existingEpisode.CalendarEpisodes.Add(calEpisode);
|
existingEpisode.CalendarEpisodes.Add(calEpisode);
|
||||||
} else{
|
} else{
|
||||||
calendarDay.CalendarEpisodes?.Add(calEpisode);
|
calendarDay.CalendarEpisodes.Add(calEpisode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -429,8 +432,6 @@ public class CalendarManager{
|
||||||
calEp.EpisodeNumber = anilistEle.Episode.ToString();
|
calEp.EpisodeNumber = anilistEle.Episode.ToString();
|
||||||
calEp.AnilistEpisode = true;
|
calEp.AnilistEpisode = true;
|
||||||
|
|
||||||
var crunchyrollID = "";
|
|
||||||
|
|
||||||
if (anilistEle.Media?.ExternalLinks != null){
|
if (anilistEle.Media?.ExternalLinks != null){
|
||||||
var url = anilistEle.Media.ExternalLinks.First(external =>
|
var url = anilistEle.Media.ExternalLinks.First(external =>
|
||||||
string.Equals(external.Site, "Crunchyroll", StringComparison.OrdinalIgnoreCase)).Url;
|
string.Equals(external.Site, "Crunchyroll", StringComparison.OrdinalIgnoreCase)).Url;
|
||||||
|
|
@ -438,10 +439,11 @@ public class CalendarManager{
|
||||||
string pattern = @"series\/([^\/]+)";
|
string pattern = @"series\/([^\/]+)";
|
||||||
|
|
||||||
Match match = Regex.Match(url, pattern);
|
Match match = Regex.Match(url, pattern);
|
||||||
|
string crunchyrollId;
|
||||||
if (match.Success){
|
if (match.Success){
|
||||||
crunchyrollID = match.Groups[1].Value;
|
crunchyrollId = match.Groups[1].Value;
|
||||||
|
|
||||||
AdjustReleaseTimeToHistory(calEp, crunchyrollID);
|
AdjustReleaseTimeToHistory(calEp, crunchyrollId);
|
||||||
} else{
|
} else{
|
||||||
Uri uri = new Uri(url);
|
Uri uri = new Uri(url);
|
||||||
|
|
||||||
|
|
@ -462,9 +464,9 @@ public class CalendarManager{
|
||||||
|
|
||||||
Match match2 = Regex.Match(finalUrl ?? string.Empty, pattern);
|
Match match2 = Regex.Match(finalUrl ?? string.Empty, pattern);
|
||||||
if (match2.Success){
|
if (match2.Success){
|
||||||
crunchyrollID = match2.Groups[1].Value;
|
crunchyrollId = match2.Groups[1].Value;
|
||||||
|
|
||||||
AdjustReleaseTimeToHistory(calEp, crunchyrollID);
|
AdjustReleaseTimeToHistory(calEp, crunchyrollId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CRD.Utils;
|
using CRD.Utils;
|
||||||
|
using CRD.Utils.Files;
|
||||||
using CRD.Utils.Structs;
|
using CRD.Utils.Structs;
|
||||||
using CRD.Utils.Structs.Crunchyroll;
|
using CRD.Utils.Structs.Crunchyroll;
|
||||||
using CRD.Views;
|
using CRD.Views;
|
||||||
|
|
@ -15,6 +16,11 @@ namespace CRD.Downloader.Crunchyroll;
|
||||||
public class CrAuth{
|
public class CrAuth{
|
||||||
private readonly CrunchyrollManager crunInstance = CrunchyrollManager.Instance;
|
private readonly CrunchyrollManager crunInstance = CrunchyrollManager.Instance;
|
||||||
|
|
||||||
|
private readonly string authorization = ApiUrls.authBasicMob;
|
||||||
|
private readonly string userAgent = ApiUrls.MobileUserAgent;
|
||||||
|
private const string DeviceType = "OnePlus CPH2449";
|
||||||
|
private const string DeviceName = "CPH2449";
|
||||||
|
|
||||||
public async Task AuthAnonymous(){
|
public async Task AuthAnonymous(){
|
||||||
string uuid = Guid.NewGuid().ToString();
|
string uuid = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
|
@ -22,17 +28,18 @@ public class CrAuth{
|
||||||
{ "grant_type", "client_id" },
|
{ "grant_type", "client_id" },
|
||||||
{ "scope", "offline_access" },
|
{ "scope", "offline_access" },
|
||||||
{ "device_id", uuid },
|
{ "device_id", uuid },
|
||||||
{ "device_type", "Chrome on Windows" }
|
{ "device_name", DeviceName },
|
||||||
|
{ "device_type", DeviceType },
|
||||||
};
|
};
|
||||||
|
|
||||||
var requestContent = new FormUrlEncodedContent(formData);
|
var requestContent = new FormUrlEncodedContent(formData);
|
||||||
|
|
||||||
var crunchyAuthHeaders = new Dictionary<string, string>{
|
var crunchyAuthHeaders = new Dictionary<string, string>{
|
||||||
{ "Authorization", ApiUrls.authBasicSwitch },
|
{ "Authorization", authorization },
|
||||||
{ "User-Agent", ApiUrls.ChromeUserAgent }
|
{ "User-Agent", userAgent }
|
||||||
};
|
};
|
||||||
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Post, ApiUrls.BetaAuth){
|
var request = new HttpRequestMessage(HttpMethod.Post, ApiUrls.Auth){
|
||||||
Content = requestContent
|
Content = requestContent
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -63,7 +70,7 @@ public class CrAuth{
|
||||||
crunInstance.Token.device_id = deviceId;
|
crunInstance.Token.device_id = deviceId;
|
||||||
crunInstance.Token.expires = DateTime.Now.AddSeconds((double)crunInstance.Token.expires_in);
|
crunInstance.Token.expires = DateTime.Now.AddSeconds((double)crunInstance.Token.expires_in);
|
||||||
|
|
||||||
CfgManager.WriteTokenToYamlFile(crunInstance.Token, CfgManager.PathCrToken);
|
CfgManager.WriteJsonToFile(CfgManager.PathCrToken, crunInstance.Token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -76,17 +83,18 @@ public class CrAuth{
|
||||||
{ "grant_type", "password" },
|
{ "grant_type", "password" },
|
||||||
{ "scope", "offline_access" },
|
{ "scope", "offline_access" },
|
||||||
{ "device_id", uuid },
|
{ "device_id", uuid },
|
||||||
{ "device_type", "Chrome on Windows" }
|
{ "device_name", DeviceName },
|
||||||
|
{ "device_type", DeviceType },
|
||||||
};
|
};
|
||||||
|
|
||||||
var requestContent = new FormUrlEncodedContent(formData);
|
var requestContent = new FormUrlEncodedContent(formData);
|
||||||
|
|
||||||
var crunchyAuthHeaders = new Dictionary<string, string>{
|
var crunchyAuthHeaders = new Dictionary<string, string>{
|
||||||
{ "Authorization", ApiUrls.authBasicSwitch },
|
{ "Authorization", authorization },
|
||||||
{ "User-Agent", ApiUrls.ChromeUserAgent }
|
{ "User-Agent", userAgent }
|
||||||
};
|
};
|
||||||
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Post, ApiUrls.BetaAuth){
|
var request = new HttpRequestMessage(HttpMethod.Post, ApiUrls.Auth){
|
||||||
Content = requestContent
|
Content = requestContent
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -120,7 +128,7 @@ public class CrAuth{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var request = HttpClientReq.CreateRequestMessage(ApiUrls.BetaProfile, HttpMethod.Get, true, true, null);
|
var request = HttpClientReq.CreateRequestMessage(ApiUrls.Profile, HttpMethod.Get, true, true, null);
|
||||||
|
|
||||||
var response = await HttpClientReq.Instance.SendHttpRequest(request);
|
var response = await HttpClientReq.Instance.SendHttpRequest(request);
|
||||||
|
|
||||||
|
|
@ -183,18 +191,19 @@ public class CrAuth{
|
||||||
{ "refresh_token", crunInstance.Token.refresh_token },
|
{ "refresh_token", crunInstance.Token.refresh_token },
|
||||||
{ "scope", "offline_access" },
|
{ "scope", "offline_access" },
|
||||||
{ "device_id", uuid },
|
{ "device_id", uuid },
|
||||||
{ "device_type", "Chrome on Windows" },
|
{ "grant_type", "refresh_token" },
|
||||||
{ "grant_type", "refresh_token" }
|
{ "device_name", DeviceName },
|
||||||
|
{ "device_type", DeviceType },
|
||||||
};
|
};
|
||||||
|
|
||||||
var requestContent = new FormUrlEncodedContent(formData);
|
var requestContent = new FormUrlEncodedContent(formData);
|
||||||
|
|
||||||
var crunchyAuthHeaders = new Dictionary<string, string>{
|
var crunchyAuthHeaders = new Dictionary<string, string>{
|
||||||
{ "Authorization", ApiUrls.authBasicSwitch },
|
{ "Authorization", authorization },
|
||||||
{ "User-Agent", ApiUrls.ChromeUserAgent }
|
{ "User-Agent", userAgent }
|
||||||
};
|
};
|
||||||
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Post, ApiUrls.BetaAuth){
|
var request = new HttpRequestMessage(HttpMethod.Post, ApiUrls.Auth){
|
||||||
Content = requestContent
|
Content = requestContent
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -237,21 +246,22 @@ public class CrAuth{
|
||||||
string uuid = Guid.NewGuid().ToString();
|
string uuid = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
var formData = new Dictionary<string, string>{
|
var formData = new Dictionary<string, string>{
|
||||||
{ "refresh_token", crunInstance.Token.refresh_token },
|
{ "refresh_token", crunInstance.Token?.refresh_token ?? "" },
|
||||||
{ "grant_type", "refresh_token" },
|
{ "grant_type", "refresh_token" },
|
||||||
{ "scope", "offline_access" },
|
{ "scope", "offline_access" },
|
||||||
{ "device_id", uuid },
|
{ "device_id", uuid },
|
||||||
{ "device_type", "Chrome on Windows" }
|
{ "device_name", DeviceName },
|
||||||
|
{ "device_type", DeviceType },
|
||||||
};
|
};
|
||||||
|
|
||||||
var requestContent = new FormUrlEncodedContent(formData);
|
var requestContent = new FormUrlEncodedContent(formData);
|
||||||
|
|
||||||
var crunchyAuthHeaders = new Dictionary<string, string>{
|
var crunchyAuthHeaders = new Dictionary<string, string>{
|
||||||
{ "Authorization", ApiUrls.authBasicSwitch },
|
{ "Authorization", authorization },
|
||||||
{ "User-Agent", ApiUrls.ChromeUserAgent }
|
{ "User-Agent", userAgent }
|
||||||
};
|
};
|
||||||
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Post, ApiUrls.BetaAuth){
|
var request = new HttpRequestMessage(HttpMethod.Post, ApiUrls.Auth){
|
||||||
Content = requestContent
|
Content = requestContent
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -259,7 +269,7 @@ public class CrAuth{
|
||||||
request.Headers.Add(header.Key, header.Value);
|
request.Headers.Add(header.Key, header.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpClientReq.Instance.SetETPCookie(crunInstance.Token.refresh_token);
|
HttpClientReq.Instance.SetETPCookie(crunInstance.Token?.refresh_token ?? string.Empty);
|
||||||
|
|
||||||
var response = await HttpClientReq.Instance.SendHttpRequest(request);
|
var response = await HttpClientReq.Instance.SendHttpRequest(request);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
using CRD.Utils;
|
using CRD.Utils;
|
||||||
|
using CRD.Utils.Files;
|
||||||
using CRD.Utils.Structs;
|
using CRD.Utils.Structs;
|
||||||
|
|
||||||
namespace CRD.Downloader.Crunchyroll;
|
namespace CRD.Downloader.Crunchyroll;
|
||||||
|
|
@ -35,9 +36,9 @@ public class CrEpisode(){
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
CrunchyEpisodeList epsidoe = Helpers.Deserialize<CrunchyEpisodeList>(response.ResponseContent, crunInstance.SettingsJsonSerializerSettings);
|
CrunchyEpisodeList epsidoe = Helpers.Deserialize<CrunchyEpisodeList>(response.ResponseContent, crunInstance.SettingsJsonSerializerSettings) ?? new CrunchyEpisodeList();
|
||||||
|
|
||||||
if (epsidoe.Total < 1){
|
if (epsidoe is{ Total: < 1 }){
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,7 +60,7 @@ public class CrEpisode(){
|
||||||
CrunchyRollEpisodeData episode = new CrunchyRollEpisodeData();
|
CrunchyRollEpisodeData episode = new CrunchyRollEpisodeData();
|
||||||
|
|
||||||
if (crunInstance.CrunOptions.History && updateHistory){
|
if (crunInstance.CrunOptions.History && updateHistory){
|
||||||
await crunInstance.History.UpdateWithSeasonData(new List<CrunchyEpisode>(){dlEpisode},false);
|
await crunInstance.History.UpdateWithEpisodeList([dlEpisode]);
|
||||||
var historySeries = crunInstance.HistoryList.FirstOrDefault(series => series.SeriesId == dlEpisode.SeriesId);
|
var historySeries = crunInstance.HistoryList.FirstOrDefault(series => series.SeriesId == dlEpisode.SeriesId);
|
||||||
if (historySeries != null){
|
if (historySeries != null){
|
||||||
CrunchyrollManager.Instance.History.MatchHistorySeriesWithSonarr(false);
|
CrunchyrollManager.Instance.History.MatchHistorySeriesWithSonarr(false);
|
||||||
|
|
@ -81,7 +82,13 @@ public class CrEpisode(){
|
||||||
if (episode.EpisodeAndLanguages.Langs.All(a => a.CrLocale != version.AudioLocale)){
|
if (episode.EpisodeAndLanguages.Langs.All(a => a.CrLocale != version.AudioLocale)){
|
||||||
// Push to arrays if there are no duplicates of the same language
|
// Push to arrays if there are no duplicates of the same language
|
||||||
episode.EpisodeAndLanguages.Items.Add(dlEpisode);
|
episode.EpisodeAndLanguages.Items.Add(dlEpisode);
|
||||||
episode.EpisodeAndLanguages.Langs.Add(Array.Find(Languages.languages, a => a.CrLocale == version.AudioLocale));
|
episode.EpisodeAndLanguages.Langs.Add(Array.Find(Languages.languages, a => a.CrLocale == version.AudioLocale) ?? new LanguageItem{
|
||||||
|
CrLocale = "und",
|
||||||
|
Locale = "un",
|
||||||
|
Code = "und",
|
||||||
|
Name = string.Empty,
|
||||||
|
Language = string.Empty
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else{
|
} else{
|
||||||
|
|
@ -91,7 +98,13 @@ public class CrEpisode(){
|
||||||
if (episode.EpisodeAndLanguages.Langs.All(a => a.CrLocale != dlEpisode.AudioLocale)){
|
if (episode.EpisodeAndLanguages.Langs.All(a => a.CrLocale != dlEpisode.AudioLocale)){
|
||||||
// Push to arrays if there are no duplicates of the same language
|
// Push to arrays if there are no duplicates of the same language
|
||||||
episode.EpisodeAndLanguages.Items.Add(dlEpisode);
|
episode.EpisodeAndLanguages.Items.Add(dlEpisode);
|
||||||
episode.EpisodeAndLanguages.Langs.Add(Array.Find(Languages.languages, a => a.CrLocale == dlEpisode.AudioLocale));
|
episode.EpisodeAndLanguages.Langs.Add(Array.Find(Languages.languages, a => a.CrLocale == dlEpisode.AudioLocale) ?? new LanguageItem{
|
||||||
|
CrLocale = "und",
|
||||||
|
Locale = "un",
|
||||||
|
Code = "und",
|
||||||
|
Name = string.Empty,
|
||||||
|
Language = string.Empty
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,7 +113,7 @@ public class CrEpisode(){
|
||||||
int epIndex = 1;
|
int epIndex = 1;
|
||||||
|
|
||||||
|
|
||||||
var isSpecial = !Regex.IsMatch(episode.EpisodeAndLanguages.Items[0].Episode ?? string.Empty, @"^\d+(\.\d+)?$"); // Checking if the episode is not a number (i.e., special).
|
var isSpecial = !Regex.IsMatch(episode.EpisodeAndLanguages.Items[0].Episode ?? string.Empty, @"^\d+(\.\d+)?$"); // Checking if the episode is not a number (i.e., special).
|
||||||
string newKey;
|
string newKey;
|
||||||
if (isSpecial && !string.IsNullOrEmpty(episode.EpisodeAndLanguages.Items[0].Episode)){
|
if (isSpecial && !string.IsNullOrEmpty(episode.EpisodeAndLanguages.Items[0].Episode)){
|
||||||
newKey = episode.EpisodeAndLanguages.Items[0].Episode ?? "SP" + (specialIndex + " " + episode.EpisodeAndLanguages.Items[0].Id);
|
newKey = episode.EpisodeAndLanguages.Items[0].Episode ?? "SP" + (specialIndex + " " + episode.EpisodeAndLanguages.Items[0].Id);
|
||||||
|
|
@ -110,14 +123,14 @@ public class CrEpisode(){
|
||||||
|
|
||||||
episode.Key = newKey;
|
episode.Key = newKey;
|
||||||
|
|
||||||
var seasonTitle = episode.EpisodeAndLanguages.Items.FirstOrDefault(a => !Regex.IsMatch(a.SeasonTitle, @"\(\w+ Dub\)")).SeasonTitle
|
var seasonTitle = episode.EpisodeAndLanguages.Items.FirstOrDefault(a => !Regex.IsMatch(a.SeasonTitle, @"\(\w+ Dub\)"))?.SeasonTitle
|
||||||
?? Regex.Replace(episode.EpisodeAndLanguages.Items[0].SeasonTitle, @"\(\w+ Dub\)", "").TrimEnd();
|
?? Regex.Replace(episode.EpisodeAndLanguages.Items[0].SeasonTitle, @"\(\w+ Dub\)", "").TrimEnd();
|
||||||
|
|
||||||
var title = episode.EpisodeAndLanguages.Items[0].Title;
|
var title = episode.EpisodeAndLanguages.Items[0].Title;
|
||||||
var seasonNumber = Helpers.ExtractNumberAfterS(episode.EpisodeAndLanguages.Items[0].Identifier) ?? episode.EpisodeAndLanguages.Items[0].SeasonNumber.ToString();
|
var seasonNumber = Helpers.ExtractNumberAfterS(episode.EpisodeAndLanguages.Items[0].Identifier) ?? episode.EpisodeAndLanguages.Items[0].SeasonNumber.ToString();
|
||||||
|
|
||||||
var languages = episode.EpisodeAndLanguages.Items.Select((a, index) =>
|
var languages = episode.EpisodeAndLanguages.Items.Select((a, index) =>
|
||||||
$"{(a.IsPremiumOnly ? "+ " : "")}{episode.EpisodeAndLanguages.Langs.ElementAtOrDefault(index).Name ?? "Unknown"}").ToArray(); //☆
|
$"{(a.IsPremiumOnly ? "+ " : "")}{episode.EpisodeAndLanguages.Langs.ElementAtOrDefault(index)?.Name ?? "Unknown"}").ToArray(); //☆
|
||||||
|
|
||||||
Console.WriteLine($"[{episode.Key}] {seasonTitle} - Season {seasonNumber} - {title} [{string.Join(", ", languages)}]");
|
Console.WriteLine($"[{episode.Key}] {seasonTitle} - Season {seasonNumber} - {title} [{string.Join(", ", languages)}]");
|
||||||
|
|
||||||
|
|
@ -125,7 +138,7 @@ public class CrEpisode(){
|
||||||
if (!serieshasversions){
|
if (!serieshasversions){
|
||||||
Console.WriteLine("Couldn\'t find versions on episode, added languages with language array.");
|
Console.WriteLine("Couldn\'t find versions on episode, added languages with language array.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return episode;
|
return episode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -160,17 +173,17 @@ public class CrEpisode(){
|
||||||
|
|
||||||
var epMeta = new CrunchyEpMeta();
|
var epMeta = new CrunchyEpMeta();
|
||||||
epMeta.Data = new List<CrunchyEpMetaData>{ new(){ MediaId = item.Id, Versions = item.Versions, IsSubbed = item.IsSubbed, IsDubbed = item.IsDubbed } };
|
epMeta.Data = new List<CrunchyEpMetaData>{ new(){ MediaId = item.Id, Versions = item.Versions, IsSubbed = item.IsSubbed, IsDubbed = item.IsDubbed } };
|
||||||
epMeta.SeriesTitle = episodeP.EpisodeAndLanguages.Items.FirstOrDefault(a => !dubPattern.IsMatch(a.SeriesTitle)).SeriesTitle ??
|
epMeta.SeriesTitle = episodeP.EpisodeAndLanguages.Items.FirstOrDefault(a => !dubPattern.IsMatch(a.SeriesTitle))?.SeriesTitle ??
|
||||||
Regex.Replace(episodeP.EpisodeAndLanguages.Items[0].SeriesTitle, @"\(\w+ Dub\)", "").TrimEnd();
|
Regex.Replace(episodeP.EpisodeAndLanguages.Items[0].SeriesTitle, @"\(\w+ Dub\)", "").TrimEnd();
|
||||||
epMeta.SeasonTitle = episodeP.EpisodeAndLanguages.Items.FirstOrDefault(a => !dubPattern.IsMatch(a.SeasonTitle)).SeasonTitle ??
|
epMeta.SeasonTitle = episodeP.EpisodeAndLanguages.Items.FirstOrDefault(a => !dubPattern.IsMatch(a.SeasonTitle))?.SeasonTitle ??
|
||||||
Regex.Replace(episodeP.EpisodeAndLanguages.Items[0].SeasonTitle, @"\(\w+ Dub\)", "").TrimEnd();
|
Regex.Replace(episodeP.EpisodeAndLanguages.Items[0].SeasonTitle, @"\(\w+ Dub\)", "").TrimEnd();
|
||||||
epMeta.EpisodeNumber = item.Episode;
|
epMeta.EpisodeNumber = item.Episode;
|
||||||
epMeta.EpisodeTitle = item.Title;
|
epMeta.EpisodeTitle = item.Title;
|
||||||
epMeta.SeasonId = item.SeasonId;
|
epMeta.SeasonId = item.SeasonId;
|
||||||
epMeta.Season = Helpers.ExtractNumberAfterS(item.Identifier) ?? item.SeasonNumber + "";
|
epMeta.Season = Helpers.ExtractNumberAfterS(item.Identifier) ?? item.SeasonNumber + "";
|
||||||
epMeta.ShowId = item.SeriesId;
|
epMeta.SeriesId = item.SeriesId;
|
||||||
epMeta.AbsolutEpisodeNumberE = epNum;
|
epMeta.AbsolutEpisodeNumberE = epNum;
|
||||||
epMeta.Image = images[images.Count / 2].FirstOrDefault().Source;
|
epMeta.Image = images[images.Count / 2].FirstOrDefault()?.Source;
|
||||||
epMeta.DownloadProgress = new DownloadProgress(){
|
epMeta.DownloadProgress = new DownloadProgress(){
|
||||||
IsDownloading = false,
|
IsDownloading = false,
|
||||||
Done = false,
|
Done = false,
|
||||||
|
|
@ -197,7 +210,7 @@ public class CrEpisode(){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (retMeta.Data != null){
|
if (retMeta.Data is{ Count: > 0 }){
|
||||||
epMetaData.Lang = episodeP.EpisodeAndLanguages.Langs[index];
|
epMetaData.Lang = episodeP.EpisodeAndLanguages.Langs[index];
|
||||||
retMeta.Data.Add(epMetaData);
|
retMeta.Data.Add(epMetaData);
|
||||||
} else{
|
} else{
|
||||||
|
|
@ -215,7 +228,7 @@ public class CrEpisode(){
|
||||||
return retMeta;
|
return retMeta;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<CrBrowseEpisodeBase?> GetNewEpisodes(string? crLocale, int requestAmount,DateTime? firstWeekDay = null , bool forcedLang = false){
|
public async Task<CrBrowseEpisodeBase?> GetNewEpisodes(string? crLocale, int requestAmount, DateTime? firstWeekDay = null, bool forcedLang = false){
|
||||||
await crunInstance.CrAuth.RefreshToken(true);
|
await crunInstance.CrAuth.RefreshToken(true);
|
||||||
CrBrowseEpisodeBase? complete = new CrBrowseEpisodeBase();
|
CrBrowseEpisodeBase? complete = new CrBrowseEpisodeBase();
|
||||||
complete.Data =[];
|
complete.Data =[];
|
||||||
|
|
@ -257,7 +270,6 @@ public class CrEpisode(){
|
||||||
requestAmount += 50;
|
requestAmount += 50;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
} else{
|
} else{
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ public class CrMovies{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
CrunchyMovieList movie = Helpers.Deserialize<CrunchyMovieList>(response.ResponseContent, crunInstance.SettingsJsonSerializerSettings);
|
CrunchyMovieList movie = Helpers.Deserialize<CrunchyMovieList>(response.ResponseContent, crunInstance.SettingsJsonSerializerSettings) ?? new CrunchyMovieList();
|
||||||
|
|
||||||
if (movie.Total < 1){
|
if (movie.Total < 1){
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -67,9 +67,9 @@ public class CrMovies{
|
||||||
epMeta.EpisodeTitle = episodeP.Title;
|
epMeta.EpisodeTitle = episodeP.Title;
|
||||||
epMeta.SeasonId = "";
|
epMeta.SeasonId = "";
|
||||||
epMeta.Season = "";
|
epMeta.Season = "";
|
||||||
epMeta.ShowId = "";
|
epMeta.SeriesId = "";
|
||||||
epMeta.AbsolutEpisodeNumberE = "";
|
epMeta.AbsolutEpisodeNumberE = "";
|
||||||
epMeta.Image = images[images.Count / 2].FirstOrDefault().Source;
|
epMeta.Image = images[images.Count / 2].FirstOrDefault()?.Source;
|
||||||
epMeta.DownloadProgress = new DownloadProgress(){
|
epMeta.DownloadProgress = new DownloadProgress(){
|
||||||
IsDownloading = false,
|
IsDownloading = false,
|
||||||
Done = false,
|
Done = false,
|
||||||
|
|
|
||||||
|
|
@ -14,44 +14,119 @@ namespace CRD.Downloader.Crunchyroll;
|
||||||
public class CrMusic{
|
public class CrMusic{
|
||||||
private readonly CrunchyrollManager crunInstance = CrunchyrollManager.Instance;
|
private readonly CrunchyrollManager crunInstance = CrunchyrollManager.Instance;
|
||||||
|
|
||||||
public async Task<CrunchyMusicVideo?> ParseMusicVideoByIdAsync(string id, string crLocale, bool forcedLang = false){
|
public async Task<CrunchyMusicVideoList?> ParseFeaturedMusicVideoByIdAsync(string seriesId, string crLocale, bool forcedLang = false, bool updateHistory = false){
|
||||||
return await ParseMediaByIdAsync(id, crLocale, forcedLang, "music/music_videos");
|
var musicVideos = await FetchMediaListAsync($"{ApiUrls.Content}/music/featured/{seriesId}", crLocale, forcedLang);
|
||||||
|
|
||||||
|
if (musicVideos.Data is{ Count: > 0 } && updateHistory){
|
||||||
|
await crunInstance.History.UpdateWithMusicEpisodeList(musicVideos.Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return musicVideos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CrunchyMusicVideo?> ParseMusicVideoByIdAsync(string id, string crLocale, bool forcedLang = false, bool updateHistory = false){
|
||||||
|
var musicVideo = await ParseMediaByIdAsync(id, crLocale, forcedLang, "music/music_videos");
|
||||||
|
|
||||||
|
if (musicVideo != null && updateHistory){
|
||||||
|
await crunInstance.History.UpdateWithMusicEpisodeList([musicVideo]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return musicVideo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<CrunchyMusicVideo?> ParseConcertByIdAsync(string id, string crLocale, bool forcedLang = false){
|
public async Task<CrunchyMusicVideo?> ParseConcertByIdAsync(string id, string crLocale, bool forcedLang = false, bool updateHistory = false){
|
||||||
return await ParseMediaByIdAsync(id, crLocale, forcedLang, "music/concerts");
|
var concert = await ParseMediaByIdAsync(id, crLocale, forcedLang, "music/concerts");
|
||||||
|
|
||||||
|
if (concert != null){
|
||||||
|
concert.EpisodeType = EpisodeType.Concert;
|
||||||
|
if (updateHistory){
|
||||||
|
await crunInstance.History.UpdateWithMusicEpisodeList([concert]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return concert;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<CrunchyMusicVideoList?> ParseArtistMusicVideosByIdAsync(string id, string crLocale, bool forcedLang = false){
|
public async Task<CrunchyMusicVideoList?> ParseArtistMusicVideosByIdAsync(string artistId, string crLocale, bool forcedLang = false, bool updateHistory = false){
|
||||||
var musicVideosTask = FetchMediaListAsync($"{ApiUrls.Content}/music/artists/{id}/music_videos", crLocale, forcedLang);
|
var musicVideos = await FetchMediaListAsync($"{ApiUrls.Content}/music/artists/{artistId}/music_videos", crLocale, forcedLang);
|
||||||
var concertsTask = FetchMediaListAsync($"{ApiUrls.Content}/music/artists/{id}/concerts", crLocale, forcedLang);
|
|
||||||
|
if (updateHistory){
|
||||||
|
await crunInstance.History.UpdateWithMusicEpisodeList(musicVideos.Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return musicVideos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CrunchyMusicVideoList?> ParseArtistConcertVideosByIdAsync(string artistId, string crLocale, bool forcedLang = false, bool updateHistory = false){
|
||||||
|
var concerts = await FetchMediaListAsync($"{ApiUrls.Content}/music/artists/{artistId}/concerts", crLocale, forcedLang);
|
||||||
|
|
||||||
|
if (concerts.Data.Count > 0){
|
||||||
|
foreach (var crunchyConcertVideo in concerts.Data){
|
||||||
|
crunchyConcertVideo.EpisodeType = EpisodeType.Concert;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateHistory){
|
||||||
|
await crunInstance.History.UpdateWithMusicEpisodeList(concerts.Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return concerts;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<CrunchyMusicVideoList?> ParseArtistVideosByIdAsync(string artistId, string crLocale, bool forcedLang = false, bool updateHistory = false){
|
||||||
|
var musicVideosTask = FetchMediaListAsync($"{ApiUrls.Content}/music/artists/{artistId}/music_videos", crLocale, forcedLang);
|
||||||
|
var concertsTask = FetchMediaListAsync($"{ApiUrls.Content}/music/artists/{artistId}/concerts", crLocale, forcedLang);
|
||||||
|
|
||||||
await Task.WhenAll(musicVideosTask, concertsTask);
|
await Task.WhenAll(musicVideosTask, concertsTask);
|
||||||
|
|
||||||
var musicVideos = await musicVideosTask;
|
var musicVideos = await musicVideosTask;
|
||||||
var concerts = await concertsTask;
|
var concerts = await concertsTask;
|
||||||
|
|
||||||
musicVideos.Total += concerts.Total;
|
|
||||||
musicVideos.Data ??= new List<CrunchyMusicVideo>();
|
|
||||||
|
|
||||||
if (concerts.Data != null){
|
musicVideos.Total += concerts.Total;
|
||||||
|
|
||||||
|
if (concerts.Data.Count > 0){
|
||||||
|
foreach (var crunchyConcertVideo in concerts.Data){
|
||||||
|
crunchyConcertVideo.EpisodeType = EpisodeType.Concert;
|
||||||
|
}
|
||||||
|
|
||||||
musicVideos.Data.AddRange(concerts.Data);
|
musicVideos.Data.AddRange(concerts.Data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (updateHistory){
|
||||||
|
await crunInstance.History.UpdateWithMusicEpisodeList(musicVideos.Data);
|
||||||
|
}
|
||||||
|
|
||||||
return musicVideos;
|
return musicVideos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<CrArtist> ParseArtistByIdAsync(string id, string crLocale, bool forcedLang = false){
|
||||||
|
var query = CreateQueryParameters(crLocale, forcedLang);
|
||||||
|
var request = HttpClientReq.CreateRequestMessage($"{ApiUrls.Content}/music/artists/{id}", HttpMethod.Get, true, true, query);
|
||||||
|
|
||||||
|
var response = await HttpClientReq.Instance.SendHttpRequest(request);
|
||||||
|
|
||||||
|
if (!response.IsOk){
|
||||||
|
Console.Error.WriteLine($"Request to {ApiUrls.Content}/music/artists/{id} failed");
|
||||||
|
return new CrArtist();
|
||||||
|
}
|
||||||
|
|
||||||
|
var artistList = Helpers.Deserialize<CrunchyArtistList>(response.ResponseContent, crunInstance.SettingsJsonSerializerSettings) ?? new CrunchyArtistList();
|
||||||
|
|
||||||
|
return artistList.Data.FirstOrDefault() ?? new CrArtist();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<CrunchyMusicVideo?> ParseMediaByIdAsync(string id, string crLocale, bool forcedLang, string endpoint){
|
private async Task<CrunchyMusicVideo?> ParseMediaByIdAsync(string id, string crLocale, bool forcedLang, string endpoint){
|
||||||
var mediaList = await FetchMediaListAsync($"{ApiUrls.Content}/{endpoint}/{id}", crLocale, forcedLang);
|
var mediaList = await FetchMediaListAsync($"{ApiUrls.Content}/{endpoint}/{id}", crLocale, forcedLang);
|
||||||
|
|
||||||
switch (mediaList.Total){
|
switch (mediaList.Total){
|
||||||
case < 1:
|
case < 1:
|
||||||
return null;
|
return null;
|
||||||
case 1 when mediaList.Data != null:
|
case 1 when mediaList.Data.Count > 0:
|
||||||
return mediaList.Data.First();
|
return mediaList.Data.First();
|
||||||
default:
|
default:
|
||||||
Console.Error.WriteLine($"Multiple items returned for endpoint {endpoint} with ID {id}");
|
Console.Error.WriteLine($"Multiple items returned for endpoint {endpoint} with ID {id}");
|
||||||
return mediaList.Data?.First();
|
return mediaList.Data.First();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -66,7 +141,7 @@ public class CrMusic{
|
||||||
return new CrunchyMusicVideoList();
|
return new CrunchyMusicVideoList();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Helpers.Deserialize<CrunchyMusicVideoList>(response.ResponseContent, crunInstance.SettingsJsonSerializerSettings);
|
return Helpers.Deserialize<CrunchyMusicVideoList>(response.ResponseContent, crunInstance.SettingsJsonSerializerSettings) ?? new CrunchyMusicVideoList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private NameValueCollection CreateQueryParameters(string crLocale, bool forcedLang){
|
private NameValueCollection CreateQueryParameters(string crLocale, bool forcedLang){
|
||||||
|
|
@ -88,15 +163,16 @@ public class CrMusic{
|
||||||
public CrunchyEpMeta EpisodeMeta(CrunchyMusicVideo episodeP){
|
public CrunchyEpMeta EpisodeMeta(CrunchyMusicVideo episodeP){
|
||||||
var images = (episodeP.Images?.Thumbnail ?? new List<Image>{ new Image{ Source = "/notFound.png" } });
|
var images = (episodeP.Images?.Thumbnail ?? new List<Image>{ new Image{ Source = "/notFound.png" } });
|
||||||
|
|
||||||
|
|
||||||
var epMeta = new CrunchyEpMeta();
|
var epMeta = new CrunchyEpMeta();
|
||||||
epMeta.Data = new List<CrunchyEpMetaData>{ new(){ MediaId = episodeP.Id, Versions = null } };
|
epMeta.Data = new List<CrunchyEpMetaData>{ new(){ MediaId = episodeP.Id, Versions = null } };
|
||||||
epMeta.SeriesTitle = "Music";
|
epMeta.SeriesTitle = episodeP.GetSeriesTitle();
|
||||||
epMeta.SeasonTitle = episodeP.DisplayArtistName;
|
epMeta.SeasonTitle = episodeP.GetSeasonTitle();
|
||||||
epMeta.EpisodeNumber = episodeP.SequenceNumber + "";
|
epMeta.EpisodeNumber = episodeP.SequenceNumber + "";
|
||||||
epMeta.EpisodeTitle = episodeP.Title;
|
epMeta.EpisodeTitle = episodeP.GetEpisodeTitle();
|
||||||
epMeta.SeasonId = "";
|
epMeta.SeasonId = episodeP.GetSeasonId();
|
||||||
epMeta.Season = "";
|
epMeta.Season = "";
|
||||||
epMeta.ShowId = "";
|
epMeta.SeriesId = episodeP.GetSeriesId();
|
||||||
epMeta.AbsolutEpisodeNumberE = "";
|
epMeta.AbsolutEpisodeNumberE = "";
|
||||||
epMeta.Image = images[images.Count / 2].Source;
|
epMeta.Image = images[images.Count / 2].Source;
|
||||||
epMeta.DownloadProgress = new DownloadProgress(){
|
epMeta.DownloadProgress = new DownloadProgress(){
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,14 @@ using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
using CRD.Utils;
|
using CRD.Utils;
|
||||||
|
using CRD.Utils.Files;
|
||||||
using CRD.Utils.Structs;
|
using CRD.Utils.Structs;
|
||||||
using CRD.Views;
|
using CRD.Views;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace CRD.Downloader.Crunchyroll;
|
namespace CRD.Downloader.Crunchyroll;
|
||||||
|
|
||||||
public class CrSeries(){
|
public class CrSeries{
|
||||||
private readonly CrunchyrollManager crunInstance = CrunchyrollManager.Instance;
|
private readonly CrunchyrollManager crunInstance = CrunchyrollManager.Instance;
|
||||||
|
|
||||||
public Dictionary<string, CrunchyEpMeta> ItemSelectMultiDub(Dictionary<string, EpisodeAndLanguage> eps, List<string> dubLang, bool? but, bool? all, List<string>? e){
|
public Dictionary<string, CrunchyEpMeta> ItemSelectMultiDub(Dictionary<string, EpisodeAndLanguage> eps, List<string> dubLang, bool? but, bool? all, List<string>? e){
|
||||||
|
|
@ -61,15 +62,15 @@ public class CrSeries(){
|
||||||
|
|
||||||
var epMeta = new CrunchyEpMeta();
|
var epMeta = new CrunchyEpMeta();
|
||||||
epMeta.Data = new List<CrunchyEpMetaData>{ new(){ MediaId = item.Id, Versions = item.Versions, IsSubbed = item.IsSubbed, IsDubbed = item.IsDubbed } };
|
epMeta.Data = new List<CrunchyEpMetaData>{ new(){ MediaId = item.Id, Versions = item.Versions, IsSubbed = item.IsSubbed, IsDubbed = item.IsDubbed } };
|
||||||
epMeta.SeriesTitle = episode.Items.FirstOrDefault(a => !dubPattern.IsMatch(a.SeriesTitle)).SeriesTitle ?? Regex.Replace(episode.Items[0].SeriesTitle, @"\(\w+ Dub\)", "").TrimEnd();
|
epMeta.SeriesTitle = episode.Items.FirstOrDefault(a => !dubPattern.IsMatch(a.SeriesTitle))?.SeriesTitle ?? Regex.Replace(episode.Items[0].SeriesTitle, @"\(\w+ Dub\)", "").TrimEnd();
|
||||||
epMeta.SeasonTitle = episode.Items.FirstOrDefault(a => !dubPattern.IsMatch(a.SeasonTitle)).SeasonTitle ?? Regex.Replace(episode.Items[0].SeasonTitle, @"\(\w+ Dub\)", "").TrimEnd();
|
epMeta.SeasonTitle = episode.Items.FirstOrDefault(a => !dubPattern.IsMatch(a.SeasonTitle))?.SeasonTitle ?? Regex.Replace(episode.Items[0].SeasonTitle, @"\(\w+ Dub\)", "").TrimEnd();
|
||||||
epMeta.EpisodeNumber = item.Episode;
|
epMeta.EpisodeNumber = item.Episode;
|
||||||
epMeta.EpisodeTitle = item.Title;
|
epMeta.EpisodeTitle = item.Title;
|
||||||
epMeta.SeasonId = item.SeasonId;
|
epMeta.SeasonId = item.SeasonId;
|
||||||
epMeta.Season = Helpers.ExtractNumberAfterS(item.Identifier) ?? item.SeasonNumber + "";
|
epMeta.Season = Helpers.ExtractNumberAfterS(item.Identifier) ?? item.SeasonNumber + "";
|
||||||
epMeta.ShowId = item.SeriesId;
|
epMeta.SeriesId = item.SeriesId;
|
||||||
epMeta.AbsolutEpisodeNumberE = epNum;
|
epMeta.AbsolutEpisodeNumberE = epNum;
|
||||||
epMeta.Image = images[images.Count / 2].FirstOrDefault().Source;
|
epMeta.Image = images[images.Count / 2].FirstOrDefault()?.Source ?? "";
|
||||||
epMeta.DownloadProgress = new DownloadProgress(){
|
epMeta.DownloadProgress = new DownloadProgress(){
|
||||||
IsDownloading = false,
|
IsDownloading = false,
|
||||||
Done = false,
|
Done = false,
|
||||||
|
|
@ -98,7 +99,7 @@ public class CrSeries(){
|
||||||
if (all is true || e != null && e.Contains(epNum)){
|
if (all is true || e != null && e.Contains(epNum)){
|
||||||
if (ret.TryGetValue(key, out var epMe)){
|
if (ret.TryGetValue(key, out var epMe)){
|
||||||
epMetaData.Lang = episode.Langs[index];
|
epMetaData.Lang = episode.Langs[index];
|
||||||
epMe.Data?.Add(epMetaData);
|
epMe.Data.Add(epMetaData);
|
||||||
} else{
|
} else{
|
||||||
epMetaData.Lang = episode.Langs[index];
|
epMetaData.Lang = episode.Langs[index];
|
||||||
epMeta.Data[0] = epMetaData;
|
epMeta.Data[0] = epMetaData;
|
||||||
|
|
@ -122,7 +123,7 @@ public class CrSeries(){
|
||||||
|
|
||||||
bool serieshasversions = true;
|
bool serieshasversions = true;
|
||||||
|
|
||||||
CrSeriesSearch? parsedSeries = await ParseSeriesById(id, crLocale,forcedLocale);
|
CrSeriesSearch? parsedSeries = await ParseSeriesById(id, crLocale, forcedLocale);
|
||||||
|
|
||||||
if (parsedSeries == null || parsedSeries.Data == null){
|
if (parsedSeries == null || parsedSeries.Data == null){
|
||||||
Console.Error.WriteLine("Parse Data Invalid");
|
Console.Error.WriteLine("Parse Data Invalid");
|
||||||
|
|
@ -131,66 +132,59 @@ public class CrSeries(){
|
||||||
|
|
||||||
// var result = ParseSeriesResult(parsedSeries);
|
// var result = ParseSeriesResult(parsedSeries);
|
||||||
Dictionary<string, EpisodeAndLanguage> episodes = new Dictionary<string, EpisodeAndLanguage>();
|
Dictionary<string, EpisodeAndLanguage> episodes = new Dictionary<string, EpisodeAndLanguage>();
|
||||||
|
|
||||||
if (crunInstance.CrunOptions.History){
|
if (crunInstance.CrunOptions.History){
|
||||||
crunInstance.History.CRUpdateSeries(id,"");
|
_ = crunInstance.History.CrUpdateSeries(id, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
var cachedSeasonID = "";
|
var cachedSeasonId = "";
|
||||||
var seasonData = new CrunchyEpisodeList();
|
var seasonData = new CrunchyEpisodeList();
|
||||||
|
|
||||||
foreach (var s in parsedSeries.Data){
|
foreach (var s in parsedSeries.Data){
|
||||||
if (data?.S != null && s.Id != data.Value.S) continue;
|
if (data?.S != null && s.Id != data.S) continue;
|
||||||
int fallbackIndex = 0;
|
int fallbackIndex = 0;
|
||||||
if (cachedSeasonID != s.Id){
|
if (cachedSeasonId != s.Id){
|
||||||
seasonData = await GetSeasonDataById(s.Id, forcedLocale ? crLocale : "");
|
seasonData = await GetSeasonDataById(s.Id, forcedLocale ? crLocale : "");
|
||||||
cachedSeasonID = s.Id;
|
cachedSeasonId = s.Id;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (seasonData.Data != null){
|
|
||||||
|
|
||||||
|
|
||||||
foreach (var episode in seasonData.Data){
|
if (seasonData.Data != null){
|
||||||
// Prepare the episode array
|
foreach (var episode in seasonData.Data){
|
||||||
EpisodeAndLanguage item;
|
// Prepare the episode array
|
||||||
|
EpisodeAndLanguage item;
|
||||||
|
|
||||||
|
|
||||||
string episodeNum = (episode.Episode != String.Empty ? episode.Episode : (episode.EpisodeNumber != null ? episode.EpisodeNumber + "" : $"F{fallbackIndex++}")) ?? string.Empty;
|
string episodeNum = (episode.Episode != String.Empty ? episode.Episode : (episode.EpisodeNumber != null ? episode.EpisodeNumber + "" : $"F{fallbackIndex++}")) ?? string.Empty;
|
||||||
|
|
||||||
var seasonIdentifier = !string.IsNullOrEmpty(s.Identifier) ? s.Identifier.Split('|')[1] : $"S{episode.SeasonNumber}";
|
var seasonIdentifier = !string.IsNullOrEmpty(s.Identifier) ? s.Identifier.Split('|')[1] : $"S{episode.SeasonNumber}";
|
||||||
var episodeKey = $"{seasonIdentifier}E{episodeNum}";
|
var episodeKey = $"{seasonIdentifier}E{episodeNum}";
|
||||||
|
|
||||||
if (!episodes.ContainsKey(episodeKey)){
|
if (!episodes.ContainsKey(episodeKey)){
|
||||||
item = new EpisodeAndLanguage{
|
item = new EpisodeAndLanguage{
|
||||||
Items = new List<CrunchyEpisode>(),
|
Items = new List<CrunchyEpisode>(),
|
||||||
Langs = new List<LanguageItem>()
|
Langs = new List<LanguageItem>()
|
||||||
};
|
};
|
||||||
episodes[episodeKey] = item;
|
episodes[episodeKey] = item;
|
||||||
} else{
|
} else{
|
||||||
item = episodes[episodeKey];
|
item = episodes[episodeKey];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (episode.Versions != null){
|
if (episode.Versions != null){
|
||||||
foreach (var version in episode.Versions){
|
foreach (var version in episode.Versions){
|
||||||
// Ensure there is only one of the same language
|
if (item.Langs.All(a => a.CrLocale != version.AudioLocale)){
|
||||||
if (item.Langs.All(a => a.CrLocale != version.AudioLocale)){
|
|
||||||
// Push to arrays if there are no duplicates of the same language
|
|
||||||
item.Items.Add(episode);
|
|
||||||
item.Langs.Add(Array.Find(Languages.languages, a => a.CrLocale == version.AudioLocale));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else{
|
|
||||||
// Episode didn't have versions, mark it as such to be logged.
|
|
||||||
serieshasversions = false;
|
|
||||||
// Ensure there is only one of the same language
|
|
||||||
if (item.Langs.All(a => a.CrLocale != episode.AudioLocale)){
|
|
||||||
// Push to arrays if there are no duplicates of the same language
|
|
||||||
item.Items.Add(episode);
|
item.Items.Add(episode);
|
||||||
item.Langs.Add(Array.Find(Languages.languages, a => a.CrLocale == episode.AudioLocale));
|
item.Langs.Add(Array.Find(Languages.languages, a => a.CrLocale == version.AudioLocale) ?? new LanguageItem());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else{
|
||||||
|
serieshasversions = false;
|
||||||
|
if (item.Langs.All(a => a.CrLocale != episode.AudioLocale)){
|
||||||
|
item.Items.Add(episode);
|
||||||
|
item.Langs.Add(Array.Find(Languages.languages, a => a.CrLocale == episode.AudioLocale) ?? new LanguageItem());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (crunInstance.CrunOptions.History){
|
if (crunInstance.CrunOptions.History){
|
||||||
|
|
@ -215,21 +209,21 @@ public class CrSeries(){
|
||||||
|
|
||||||
string newKey;
|
string newKey;
|
||||||
if (isSpecial && !string.IsNullOrEmpty(item.Items[0].Episode)){
|
if (isSpecial && !string.IsNullOrEmpty(item.Items[0].Episode)){
|
||||||
newKey = $"SP{specialIndex}_" + item.Items[0].Episode ?? "SP" + (specialIndex + " " + item.Items[0].Id);
|
newKey = $"SP{specialIndex}_" + item.Items[0].Episode;// ?? "SP" + (specialIndex + " " + item.Items[0].Id);
|
||||||
} else{
|
} else{
|
||||||
newKey = $"{(isSpecial ? "SP" : 'E')}{(isSpecial ? (specialIndex + " " + item.Items[0].Id) : epIndex + "")}";
|
newKey = $"{(isSpecial ? "SP" : 'E')}{(isSpecial ? (specialIndex + " " + item.Items[0].Id) : epIndex + "")}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
episodes.Remove(key);
|
episodes.Remove(key);
|
||||||
|
|
||||||
int counter = 1;
|
int counter = 1;
|
||||||
string originalKey = newKey;
|
string originalKey = newKey;
|
||||||
while (episodes.ContainsKey(newKey)){
|
while (episodes.ContainsKey(newKey)){
|
||||||
newKey = originalKey + "_" + counter;
|
newKey = originalKey + "_" + counter;
|
||||||
counter++;
|
counter++;
|
||||||
}
|
}
|
||||||
|
|
||||||
episodes.Add(newKey, item);
|
episodes.Add(newKey, item);
|
||||||
|
|
||||||
if (isSpecial){
|
if (isSpecial){
|
||||||
|
|
@ -249,14 +243,14 @@ public class CrSeries(){
|
||||||
var key = kvp.Key;
|
var key = kvp.Key;
|
||||||
var item = kvp.Value;
|
var item = kvp.Value;
|
||||||
|
|
||||||
var seasonTitle = item.Items.FirstOrDefault(a => !Regex.IsMatch(a.SeasonTitle, @"\(\w+ Dub\)")).SeasonTitle
|
var seasonTitle = item.Items.FirstOrDefault(a => !Regex.IsMatch(a.SeasonTitle, @"\(\w+ Dub\)"))?.SeasonTitle
|
||||||
?? Regex.Replace(item.Items[0].SeasonTitle, @"\(\w+ Dub\)", "").TrimEnd();
|
?? Regex.Replace(item.Items[0].SeasonTitle, @"\(\w+ Dub\)", "").TrimEnd();
|
||||||
|
|
||||||
var title = item.Items[0].Title;
|
var title = item.Items[0].Title;
|
||||||
var seasonNumber = Helpers.ExtractNumberAfterS(item.Items[0].Identifier) ?? item.Items[0].SeasonNumber.ToString();
|
var seasonNumber = Helpers.ExtractNumberAfterS(item.Items[0].Identifier) ?? item.Items[0].SeasonNumber.ToString();
|
||||||
|
|
||||||
var languages = item.Items.Select((a, index) =>
|
var languages = item.Items.Select((a, index) =>
|
||||||
$"{(a.IsPremiumOnly ? "+ " : "")}{item.Langs.ElementAtOrDefault(index).Name ?? "Unknown"}").ToArray(); //☆
|
$"{(a.IsPremiumOnly ? "+ " : "")}{item.Langs.ElementAtOrDefault(index)?.Name ?? "Unknown"}").ToArray(); //☆
|
||||||
|
|
||||||
Console.WriteLine($"[{key}] {seasonTitle} - Season {seasonNumber} - {title} [{string.Join(", ", languages)}]");
|
Console.WriteLine($"[{key}] {seasonTitle} - Season {seasonNumber} - {title} [{string.Join(", ", languages)}]");
|
||||||
}
|
}
|
||||||
|
|
@ -275,7 +269,7 @@ public class CrSeries(){
|
||||||
var seconds = (int)Math.Floor(value.Items[0].DurationMs / 1000.0);
|
var seconds = (int)Math.Floor(value.Items[0].DurationMs / 1000.0);
|
||||||
var langList = value.Langs.Select(a => a.CrLocale).ToList();
|
var langList = value.Langs.Select(a => a.CrLocale).ToList();
|
||||||
Languages.SortListByLangList(langList);
|
Languages.SortListByLangList(langList);
|
||||||
|
|
||||||
return new Episode{
|
return new Episode{
|
||||||
E = key.StartsWith("E") ? key.Substring(1) : key,
|
E = key.StartsWith("E") ? key.Substring(1) : key,
|
||||||
Lang = langList,
|
Lang = langList,
|
||||||
|
|
@ -285,8 +279,9 @@ public class CrSeries(){
|
||||||
SeasonTitle = Regex.Replace(value.Items[0].SeasonTitle, @"\(\w+ Dub\)", "").TrimEnd(),
|
SeasonTitle = Regex.Replace(value.Items[0].SeasonTitle, @"\(\w+ Dub\)", "").TrimEnd(),
|
||||||
EpisodeNum = key.StartsWith("SP") ? key : value.Items[0].EpisodeNumber?.ToString() ?? value.Items[0].Episode ?? "?",
|
EpisodeNum = key.StartsWith("SP") ? key : value.Items[0].EpisodeNumber?.ToString() ?? value.Items[0].Episode ?? "?",
|
||||||
Id = value.Items[0].SeasonId,
|
Id = value.Items[0].SeasonId,
|
||||||
Img = images[images.Count / 2].FirstOrDefault().Source,
|
Img = images[images.Count / 2].FirstOrDefault()?.Source ?? "",
|
||||||
Description = value.Items[0].Description,
|
Description = value.Items[0].Description,
|
||||||
|
EpisodeType = EpisodeType.Episode,
|
||||||
Time = $"{seconds / 60}:{seconds % 60:D2}" // Ensures two digits for seconds.
|
Time = $"{seconds / 60}:{seconds % 60:D2}" // Ensures two digits for seconds.
|
||||||
};
|
};
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
@ -294,7 +289,7 @@ public class CrSeries(){
|
||||||
return crunchySeriesList;
|
return crunchySeriesList;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<CrunchyEpisodeList> GetSeasonDataById(string seasonID, string? crLocale, bool forcedLang = false, bool log = false){
|
public async Task<CrunchyEpisodeList> GetSeasonDataById(string seasonId, string? crLocale, bool forcedLang = false, bool log = false){
|
||||||
CrunchyEpisodeList episodeList = new CrunchyEpisodeList(){ Data = new List<CrunchyEpisode>(), Total = 0, Meta = new Meta() };
|
CrunchyEpisodeList episodeList = new CrunchyEpisodeList(){ Data = new List<CrunchyEpisode>(), Total = 0, Meta = new Meta() };
|
||||||
|
|
||||||
NameValueCollection query;
|
NameValueCollection query;
|
||||||
|
|
@ -309,7 +304,7 @@ public class CrSeries(){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var showRequest = HttpClientReq.CreateRequestMessage($"{ApiUrls.Cms}/seasons/{seasonID}", HttpMethod.Get, true, true, query);
|
var showRequest = HttpClientReq.CreateRequestMessage($"{ApiUrls.Cms}/seasons/{seasonId}", HttpMethod.Get, true, true, query);
|
||||||
|
|
||||||
var response = await HttpClientReq.Instance.SendHttpRequest(showRequest);
|
var response = await HttpClientReq.Instance.SendHttpRequest(showRequest);
|
||||||
|
|
||||||
|
|
@ -330,14 +325,15 @@ public class CrSeries(){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var episodeRequest = HttpClientReq.CreateRequestMessage($"{ApiUrls.Cms}/seasons/{seasonID}/episodes", HttpMethod.Get, true, true, query);
|
var episodeRequest = HttpClientReq.CreateRequestMessage($"{ApiUrls.Cms}/seasons/{seasonId}/episodes", HttpMethod.Get, true, true, query);
|
||||||
|
|
||||||
var episodeRequestResponse = await HttpClientReq.Instance.SendHttpRequest(episodeRequest);
|
var episodeRequestResponse = await HttpClientReq.Instance.SendHttpRequest(episodeRequest);
|
||||||
|
|
||||||
if (!episodeRequestResponse.IsOk){
|
if (!episodeRequestResponse.IsOk){
|
||||||
Console.Error.WriteLine($"Episode List Request FAILED! uri: {episodeRequest.RequestUri}");
|
Console.Error.WriteLine($"Episode List Request FAILED! uri: {episodeRequest.RequestUri}");
|
||||||
} else{
|
} else{
|
||||||
episodeList = Helpers.Deserialize<CrunchyEpisodeList>(episodeRequestResponse.ResponseContent, crunInstance.SettingsJsonSerializerSettings);
|
episodeList = Helpers.Deserialize<CrunchyEpisodeList>(episodeRequestResponse.ResponseContent, crunInstance.SettingsJsonSerializerSettings) ??
|
||||||
|
new CrunchyEpisodeList(){ Data =[], Total = 0, Meta = new Meta() };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (episodeList.Total < 1){
|
if (episodeList.Total < 1){
|
||||||
|
|
@ -351,6 +347,8 @@ public class CrSeries(){
|
||||||
var ret = new Dictionary<int, Dictionary<string, SeriesSearchItem>>();
|
var ret = new Dictionary<int, Dictionary<string, SeriesSearchItem>>();
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
||||||
|
if (seasonsList.Data == null) return ret;
|
||||||
|
|
||||||
foreach (var item in seasonsList.Data){
|
foreach (var item in seasonsList.Data){
|
||||||
i++;
|
i++;
|
||||||
foreach (var lang in Languages.languages){
|
foreach (var lang in Languages.languages){
|
||||||
|
|
@ -482,12 +480,12 @@ public class CrSeries(){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return series;
|
return series;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<CrBrowseSeriesBase?> GetAllSeries(string? crLocale){
|
public async Task<CrBrowseSeriesBase?> GetAllSeries(string? crLocale){
|
||||||
CrBrowseSeriesBase? complete = new CrBrowseSeriesBase();
|
CrBrowseSeriesBase complete = new CrBrowseSeriesBase();
|
||||||
complete.Data =[];
|
complete.Data =[];
|
||||||
|
|
||||||
var i = 0;
|
var i = 0;
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ using CRD.Utils.Sonarr;
|
||||||
using CRD.Utils.Structs;
|
using CRD.Utils.Structs;
|
||||||
using CRD.Utils.Structs.Crunchyroll;
|
using CRD.Utils.Structs.Crunchyroll;
|
||||||
using CRD.Utils.Structs.History;
|
using CRD.Utils.Structs.History;
|
||||||
|
using CRD.ViewModels;
|
||||||
using CRD.ViewModels.Utils;
|
using CRD.ViewModels.Utils;
|
||||||
using CRD.Views;
|
using CRD.Views;
|
||||||
using CRD.Views.Utils;
|
using CRD.Views.Utils;
|
||||||
|
|
@ -130,9 +131,75 @@ public class CrunchyrollManager{
|
||||||
options.BackgroundImageOpacity = 0.5;
|
options.BackgroundImageOpacity = 0.5;
|
||||||
options.BackgroundImageBlurRadius = 10;
|
options.BackgroundImageBlurRadius = 10;
|
||||||
|
|
||||||
|
options.HistoryPageProperties = new HistoryPageProperties{
|
||||||
|
SelectedView = HistoryViewType.Posters,
|
||||||
|
SelectedSorting = SortingType.SeriesTitle,
|
||||||
|
SelectedFilter = FilterType.All,
|
||||||
|
ScaleValue = 0.73,
|
||||||
|
Ascending = false,
|
||||||
|
ShowSeries = true,
|
||||||
|
ShowArtists = true
|
||||||
|
};
|
||||||
|
|
||||||
options.History = true;
|
options.History = true;
|
||||||
|
|
||||||
CfgManager.UpdateSettingsFromFile(options);
|
if (Path.Exists(CfgManager.PathCrDownloadOptionsOld)){
|
||||||
|
var optionsYaml = new CrDownloadOptionsYaml();
|
||||||
|
|
||||||
|
optionsYaml.AutoDownload = false;
|
||||||
|
optionsYaml.RemoveFinishedDownload = false;
|
||||||
|
optionsYaml.Chapters = true;
|
||||||
|
optionsYaml.Hslang = "none";
|
||||||
|
optionsYaml.Force = "Y";
|
||||||
|
optionsYaml.FileName = "${seriesTitle} - S${season}E${episode} [${height}p]";
|
||||||
|
optionsYaml.Partsize = 10;
|
||||||
|
optionsYaml.DlSubs = new List<string>{ "en-US" };
|
||||||
|
optionsYaml.SkipMuxing = false;
|
||||||
|
optionsYaml.MkvmergeOptions = new List<string>{ "--no-date", "--disable-track-statistics-tags", "--engage no_variable_data" };
|
||||||
|
optionsYaml.FfmpegOptions = new();
|
||||||
|
optionsYaml.DefaultAudio = "ja-JP";
|
||||||
|
optionsYaml.DefaultSub = "en-US";
|
||||||
|
optionsYaml.QualityAudio = "best";
|
||||||
|
optionsYaml.QualityVideo = "best";
|
||||||
|
optionsYaml.CcTag = "CC";
|
||||||
|
optionsYaml.CcSubsFont = "Trebuchet MS";
|
||||||
|
optionsYaml.FsRetryTime = 5;
|
||||||
|
optionsYaml.Numbers = 2;
|
||||||
|
optionsYaml.Timeout = 15000;
|
||||||
|
optionsYaml.DubLang = new List<string>(){ "ja-JP" };
|
||||||
|
optionsYaml.SimultaneousDownloads = 2;
|
||||||
|
// options.AccentColor = Colors.SlateBlue.ToString();
|
||||||
|
optionsYaml.Theme = "System";
|
||||||
|
optionsYaml.SelectedCalendarLanguage = "en-us";
|
||||||
|
optionsYaml.CalendarDubFilter = "none";
|
||||||
|
optionsYaml.CustomCalendar = true;
|
||||||
|
optionsYaml.DlVideoOnce = true;
|
||||||
|
optionsYaml.StreamEndpoint = "web/firefox";
|
||||||
|
optionsYaml.SubsAddScaledBorder = ScaledBorderAndShadowSelection.ScaledBorderAndShadowYes;
|
||||||
|
optionsYaml.HistoryLang = DefaultLocale;
|
||||||
|
|
||||||
|
optionsYaml.BackgroundImageOpacity = 0.5;
|
||||||
|
optionsYaml.BackgroundImageBlurRadius = 10;
|
||||||
|
|
||||||
|
optionsYaml.HistoryPageProperties = new HistoryPageProperties{
|
||||||
|
SelectedView = HistoryViewType.Posters,
|
||||||
|
SelectedSorting = SortingType.SeriesTitle,
|
||||||
|
SelectedFilter = FilterType.All,
|
||||||
|
ScaleValue = 0.73,
|
||||||
|
Ascending = false,
|
||||||
|
ShowSeries = true,
|
||||||
|
ShowArtists = true
|
||||||
|
};
|
||||||
|
|
||||||
|
optionsYaml.History = true;
|
||||||
|
|
||||||
|
CfgManager.UpdateSettingsFromFileYAML(optionsYaml);
|
||||||
|
|
||||||
|
options = Helpers.MigrateSettings(optionsYaml);
|
||||||
|
} else{
|
||||||
|
CfgManager.UpdateSettingsFromFile(options, CfgManager.PathCrDownloadOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
@ -147,7 +214,6 @@ public class CrunchyrollManager{
|
||||||
CrMusic = new CrMusic();
|
CrMusic = new CrMusic();
|
||||||
History = new History();
|
History = new History();
|
||||||
|
|
||||||
|
|
||||||
Profile = new CrProfile{
|
Profile = new CrProfile{
|
||||||
Username = "???",
|
Username = "???",
|
||||||
Avatar = "crbrand_avatars_logo_marks_mangagirl_taupe.png",
|
Avatar = "crbrand_avatars_logo_marks_mangagirl_taupe.png",
|
||||||
|
|
@ -155,6 +221,11 @@ public class CrunchyrollManager{
|
||||||
PreferredContentSubtitleLanguage = DefaultLocale,
|
PreferredContentSubtitleLanguage = DefaultLocale,
|
||||||
HasPremium = false,
|
HasPremium = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (Path.Exists(CfgManager.PathCrDownloadOptionsOld)){
|
||||||
|
CfgManager.WriteCrSettings();
|
||||||
|
Helpers.DeleteFile(CfgManager.PathCrDownloadOptionsOld);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Init(){
|
public async Task Init(){
|
||||||
|
|
@ -183,8 +254,11 @@ public class CrunchyrollManager{
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CfgManager.CheckIfFileExists(CfgManager.PathCrToken)){
|
if (CfgManager.CheckIfFileExists(CfgManager.PathCrToken)){
|
||||||
Token = CfgManager.DeserializeFromFile<CrToken>(CfgManager.PathCrToken);
|
Token = CfgManager.ReadJsonFromFile<CrToken>(CfgManager.PathCrToken);
|
||||||
await CrAuth.LoginWithToken();
|
await CrAuth.LoginWithToken();
|
||||||
|
if (Path.Exists(CfgManager.PathCrTokenOld)){
|
||||||
|
Helpers.DeleteFile(CfgManager.PathCrTokenOld);
|
||||||
|
}
|
||||||
} else{
|
} else{
|
||||||
await CrAuth.AuthAnonymous();
|
await CrAuth.AuthAnonymous();
|
||||||
}
|
}
|
||||||
|
|
@ -200,12 +274,11 @@ public class CrunchyrollManager{
|
||||||
);
|
);
|
||||||
|
|
||||||
if (historyList != null){
|
if (historyList != null){
|
||||||
|
|
||||||
HistoryList = historyList;
|
HistoryList = historyList;
|
||||||
|
|
||||||
Parallel.ForEach(historyList, historySeries => {
|
Parallel.ForEach(historyList, historySeries => {
|
||||||
historySeries.Init();
|
historySeries.Init();
|
||||||
|
|
||||||
foreach (var historySeriesSeason in historySeries.Seasons){
|
foreach (var historySeriesSeason in historySeries.Seasons){
|
||||||
historySeriesSeason.Init();
|
historySeriesSeason.Init();
|
||||||
}
|
}
|
||||||
|
|
@ -318,11 +391,13 @@ public class CrunchyrollManager{
|
||||||
|
|
||||||
QueueManager.Instance.Queue.Refresh();
|
QueueManager.Instance.Queue.Refresh();
|
||||||
|
|
||||||
await Helpers.RunFFmpegWithPresetAsync(merger?.options.Output, FfmpegEncoding.GetPreset(options.EncodingPresetName), data);
|
var preset = FfmpegEncoding.GetPreset(options.EncodingPresetName ?? string.Empty);
|
||||||
|
|
||||||
|
if (preset != null) await Helpers.RunFFmpegWithPresetAsync(merger.options.Output, preset, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.DownloadToTempFolder){
|
if (options.DownloadToTempFolder){
|
||||||
await MoveFromTempFolder(merger, data, options, res.TempFolderPath, res.Data.Where(e => e.Type == DownloadMediaType.Subtitle));
|
await MoveFromTempFolder(merger, data, options, res.TempFolderPath ?? CfgManager.PathTEMP_DIR, res.Data.Where(e => e.Type == DownloadMediaType.Subtitle));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else{
|
} else{
|
||||||
|
|
@ -364,11 +439,12 @@ public class CrunchyrollManager{
|
||||||
|
|
||||||
QueueManager.Instance.Queue.Refresh();
|
QueueManager.Instance.Queue.Refresh();
|
||||||
|
|
||||||
await Helpers.RunFFmpegWithPresetAsync(result.merger?.options.Output, FfmpegEncoding.GetPreset(options.EncodingPresetName), data);
|
var preset = FfmpegEncoding.GetPreset(options.EncodingPresetName ?? string.Empty);
|
||||||
|
if (preset != null && result.merger != null) await Helpers.RunFFmpegWithPresetAsync(result.merger.options.Output, preset, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.DownloadToTempFolder){
|
if (options.DownloadToTempFolder){
|
||||||
await MoveFromTempFolder(result.merger, data, options, res.TempFolderPath, res.Data.Where(e => e.Type == DownloadMediaType.Subtitle));
|
await MoveFromTempFolder(result.merger, data, options, res.TempFolderPath ?? CfgManager.PathTEMP_DIR, res.Data.Where(e => e.Type == DownloadMediaType.Subtitle));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -417,8 +493,8 @@ public class CrunchyrollManager{
|
||||||
QueueManager.Instance.ActiveDownloads--;
|
QueueManager.Instance.ActiveDownloads--;
|
||||||
QueueManager.Instance.Queue.Refresh();
|
QueueManager.Instance.Queue.Refresh();
|
||||||
|
|
||||||
if (options.History && data.Data != null && data.Data.Count > 0){
|
if (options.History && data.Data is{ Count: > 0 } && (options.HistoryIncludeCrArtists && data.Music || !data.Music)){
|
||||||
History.SetAsDownloaded(data.ShowId, data.SeasonId, data.Data.First().MediaId);
|
History.SetAsDownloaded(data.SeriesId, data.SeasonId, data.Data.First().MediaId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -473,9 +549,9 @@ public class CrunchyrollManager{
|
||||||
? options.DownloadDirPath
|
? options.DownloadDirPath
|
||||||
: CfgManager.PathVIDEOS_DIR;
|
: CfgManager.PathVIDEOS_DIR;
|
||||||
|
|
||||||
var destinationPath = Path.Combine(destinationFolder ?? string.Empty, fileName ?? string.Empty);
|
var destinationPath = Path.Combine(destinationFolder ?? string.Empty, fileName);
|
||||||
|
|
||||||
string destinationDirectory = Path.GetDirectoryName(destinationPath);
|
string? destinationDirectory = Path.GetDirectoryName(destinationPath);
|
||||||
if (string.IsNullOrEmpty(destinationDirectory)){
|
if (string.IsNullOrEmpty(destinationDirectory)){
|
||||||
Console.WriteLine("Invalid destination directory path.");
|
Console.WriteLine("Invalid destination directory path.");
|
||||||
return;
|
return;
|
||||||
|
|
@ -519,7 +595,7 @@ public class CrunchyrollManager{
|
||||||
foreach (var downloadedMedia in subs){
|
foreach (var downloadedMedia in subs){
|
||||||
var subt = new SubtitleFonts();
|
var subt = new SubtitleFonts();
|
||||||
subt.Language = downloadedMedia.Language;
|
subt.Language = downloadedMedia.Language;
|
||||||
subt.Fonts = downloadedMedia.Fonts;
|
subt.Fonts = downloadedMedia.Fonts ??[];
|
||||||
subsList.Add(subt);
|
subsList.Add(subt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -609,7 +685,9 @@ public class CrunchyrollManager{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
syncVideosList.ForEach(syncVideo => Helpers.DeleteFile(syncVideo.Path));
|
syncVideosList.ForEach(syncVideo => {
|
||||||
|
if (syncVideo.Path != null) Helpers.DeleteFile(syncVideo.Path);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.Mp4 && !muxToMp3){
|
if (!options.Mp4 && !muxToMp3){
|
||||||
|
|
@ -725,7 +803,15 @@ public class CrunchyrollManager{
|
||||||
bool dlVideoOnce = false;
|
bool dlVideoOnce = false;
|
||||||
string fileDir = CfgManager.PathVIDEOS_DIR;
|
string fileDir = CfgManager.PathVIDEOS_DIR;
|
||||||
|
|
||||||
if (data.Data != null){
|
if (data.Data is{ Count: > 0 }){
|
||||||
|
options.Partsize = options.Partsize > 0 ? options.Partsize : 1;
|
||||||
|
|
||||||
|
var sortedMetaData = data.Data
|
||||||
|
.OrderBy(metaData => options.DubLang.IndexOf(metaData.Lang?.CrLocale ?? string.Empty) != -1 ? options.DubLang.IndexOf(metaData.Lang?.CrLocale ?? string.Empty) : int.MaxValue)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
data.Data = sortedMetaData;
|
||||||
|
|
||||||
foreach (CrunchyEpMetaData epMeta in data.Data){
|
foreach (CrunchyEpMetaData epMeta in data.Data){
|
||||||
Console.WriteLine($"Requesting: [{epMeta.MediaId}] {mediaName}");
|
Console.WriteLine($"Requesting: [{epMeta.MediaId}] {mediaName}");
|
||||||
|
|
||||||
|
|
@ -753,10 +839,10 @@ public class CrunchyrollManager{
|
||||||
string mediaGuid = currentMediaId;
|
string mediaGuid = currentMediaId;
|
||||||
if (epMeta.Versions != null){
|
if (epMeta.Versions != null){
|
||||||
if (epMeta.Lang != null){
|
if (epMeta.Lang != null){
|
||||||
currentVersion = epMeta.Versions.Find(a => a.AudioLocale == epMeta.Lang?.CrLocale);
|
currentVersion = epMeta.Versions.Find(a => a.AudioLocale == epMeta.Lang?.CrLocale) ?? currentVersion;
|
||||||
} else if (data.SelectedDubs is{ Count: 1 }){
|
} else if (data.SelectedDubs is{ Count: 1 }){
|
||||||
LanguageItem lang = Array.Find(Languages.languages, a => a.CrLocale == data.SelectedDubs[0]);
|
LanguageItem? lang = Array.Find(Languages.languages, a => a.CrLocale == data.SelectedDubs[0]);
|
||||||
currentVersion = epMeta.Versions.Find(a => a.AudioLocale == lang.CrLocale);
|
currentVersion = epMeta.Versions.Find(a => a.AudioLocale == lang?.CrLocale) ?? currentVersion;
|
||||||
} else if (epMeta.Versions.Count == 1){
|
} else if (epMeta.Versions.Count == 1){
|
||||||
currentVersion = epMeta.Versions[0];
|
currentVersion = epMeta.Versions[0];
|
||||||
}
|
}
|
||||||
|
|
@ -772,7 +858,7 @@ public class CrunchyrollManager{
|
||||||
mediaGuid = currentVersion.Guid;
|
mediaGuid = currentVersion.Guid;
|
||||||
|
|
||||||
if (!isPrimary){
|
if (!isPrimary){
|
||||||
primaryVersion = epMeta.Versions.Find(a => a.Original);
|
primaryVersion = epMeta.Versions.Find(a => a.Original) ?? currentVersion;
|
||||||
} else{
|
} else{
|
||||||
primaryVersion = currentVersion;
|
primaryVersion = currentVersion;
|
||||||
}
|
}
|
||||||
|
|
@ -816,12 +902,12 @@ public class CrunchyrollManager{
|
||||||
Data = new List<DownloadedMedia>(),
|
Data = new List<DownloadedMedia>(),
|
||||||
Error = true,
|
Error = true,
|
||||||
FileName = "./unknown",
|
FileName = "./unknown",
|
||||||
ErrorText = "Too many active streams that couldn't be stopped"
|
ErrorText = "Too many active streams that couldn't be stopped\nClose open cruchyroll tabs in your browser"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MainWindow.Instance.ShowError("Couldn't get Playback Data");
|
MainWindow.Instance.ShowError("Couldn't get Playback Data\nTry again later or else check logs and crunchyroll");
|
||||||
return new DownloadResponse{
|
return new DownloadResponse{
|
||||||
Data = new List<DownloadedMedia>(),
|
Data = new List<DownloadedMedia>(),
|
||||||
Error = true,
|
Error = true,
|
||||||
|
|
@ -1193,9 +1279,10 @@ public class CrunchyrollManager{
|
||||||
}
|
}
|
||||||
|
|
||||||
//string outFile = Path.Combine(FileNameManager.ParseFileName(options.FileName + "." + (epMeta.Lang?.CrLocale ?? lang.Value.Name), variables, options.Numbers, options.Override).ToArray());
|
//string outFile = Path.Combine(FileNameManager.ParseFileName(options.FileName + "." + (epMeta.Lang?.CrLocale ?? lang.Value.Name), variables, options.Numbers, options.Override).ToArray());
|
||||||
string outFile = fileName + "." + (epMeta.Lang?.CrLocale ?? lang.Value.CrLocale);
|
string outFile = fileName + "." + (epMeta.Lang?.CrLocale ?? lang.CrLocale);
|
||||||
|
|
||||||
string tempFile = Path.Combine(FileNameManager.ParseFileName($"temp-{(currentVersion.Guid != null ? currentVersion.Guid : currentMediaId)}", variables, options.Numbers, options.Override)
|
string tempFile = Path.Combine(FileNameManager
|
||||||
|
.ParseFileName($"temp-{(!string.IsNullOrEmpty(currentVersion.Guid) ? currentVersion.Guid : currentMediaId)}", variables, options.Numbers, options.Override)
|
||||||
.ToArray());
|
.ToArray());
|
||||||
string tempTsFile = Path.IsPathRooted(tempFile) ? tempFile : Path.Combine(fileDir, tempFile);
|
string tempTsFile = Path.IsPathRooted(tempFile) ? tempFile : Path.Combine(fileDir, tempFile);
|
||||||
|
|
||||||
|
|
@ -1207,7 +1294,7 @@ public class CrunchyrollManager{
|
||||||
} else if (options.Novids){
|
} else if (options.Novids){
|
||||||
Console.WriteLine("Skipping video download...");
|
Console.WriteLine("Skipping video download...");
|
||||||
} else{
|
} else{
|
||||||
var videoDownloadResult = await DownloadVideo(chosenVideoSegments, options, outFile, tsFile, tempTsFile, data, fileDir);
|
var videoDownloadResult = await DownloadVideo(chosenVideoSegments, options, outFile, tempTsFile, data, fileDir);
|
||||||
|
|
||||||
tsFile = videoDownloadResult.tsFile;
|
tsFile = videoDownloadResult.tsFile;
|
||||||
|
|
||||||
|
|
@ -1226,7 +1313,7 @@ public class CrunchyrollManager{
|
||||||
|
|
||||||
|
|
||||||
if (chosenAudioSegments.segments.Count > 0 && !options.Noaudio && !dlFailed){
|
if (chosenAudioSegments.segments.Count > 0 && !options.Noaudio && !dlFailed){
|
||||||
var audioDownloadResult = await DownloadAudio(chosenAudioSegments, options, outFile, tsFile, tempTsFile, data, fileDir);
|
var audioDownloadResult = await DownloadAudio(chosenAudioSegments, options, outFile, tempTsFile, data, fileDir);
|
||||||
|
|
||||||
tsFile = audioDownloadResult.tsFile;
|
tsFile = audioDownloadResult.tsFile;
|
||||||
|
|
||||||
|
|
@ -1403,7 +1490,7 @@ public class CrunchyrollManager{
|
||||||
videoDownloadMedia = new DownloadedMedia{
|
videoDownloadMedia = new DownloadedMedia{
|
||||||
Type = syncTimingDownload ? DownloadMediaType.SyncVideo : DownloadMediaType.Video,
|
Type = syncTimingDownload ? DownloadMediaType.SyncVideo : DownloadMediaType.Video,
|
||||||
Path = $"{tsFile}.video.m4s",
|
Path = $"{tsFile}.video.m4s",
|
||||||
Lang = lang.Value,
|
Lang = lang,
|
||||||
IsPrimary = isPrimary
|
IsPrimary = isPrimary
|
||||||
};
|
};
|
||||||
files.Add(videoDownloadMedia);
|
files.Add(videoDownloadMedia);
|
||||||
|
|
@ -1471,7 +1558,7 @@ public class CrunchyrollManager{
|
||||||
files.Add(new DownloadedMedia{
|
files.Add(new DownloadedMedia{
|
||||||
Type = DownloadMediaType.Audio,
|
Type = DownloadMediaType.Audio,
|
||||||
Path = $"{tsFile}.audio.m4s",
|
Path = $"{tsFile}.audio.m4s",
|
||||||
Lang = lang.Value,
|
Lang = lang,
|
||||||
IsPrimary = isPrimary
|
IsPrimary = isPrimary
|
||||||
});
|
});
|
||||||
data.downloadedFiles.Add($"{tsFile}.audio.m4s");
|
data.downloadedFiles.Add($"{tsFile}.audio.m4s");
|
||||||
|
|
@ -1487,7 +1574,7 @@ public class CrunchyrollManager{
|
||||||
videoDownloadMedia = new DownloadedMedia{
|
videoDownloadMedia = new DownloadedMedia{
|
||||||
Type = syncTimingDownload ? DownloadMediaType.SyncVideo : DownloadMediaType.Video,
|
Type = syncTimingDownload ? DownloadMediaType.SyncVideo : DownloadMediaType.Video,
|
||||||
Path = $"{tsFile}.video.m4s",
|
Path = $"{tsFile}.video.m4s",
|
||||||
Lang = lang.Value,
|
Lang = lang,
|
||||||
IsPrimary = isPrimary
|
IsPrimary = isPrimary
|
||||||
};
|
};
|
||||||
files.Add(videoDownloadMedia);
|
files.Add(videoDownloadMedia);
|
||||||
|
|
@ -1498,7 +1585,7 @@ public class CrunchyrollManager{
|
||||||
files.Add(new DownloadedMedia{
|
files.Add(new DownloadedMedia{
|
||||||
Type = DownloadMediaType.Audio,
|
Type = DownloadMediaType.Audio,
|
||||||
Path = $"{tsFile}.audio.m4s",
|
Path = $"{tsFile}.audio.m4s",
|
||||||
Lang = lang.Value,
|
Lang = lang,
|
||||||
IsPrimary = isPrimary
|
IsPrimary = isPrimary
|
||||||
});
|
});
|
||||||
data.downloadedFiles.Add($"{tsFile}.audio.m4s");
|
data.downloadedFiles.Add($"{tsFile}.audio.m4s");
|
||||||
|
|
@ -1547,7 +1634,13 @@ public class CrunchyrollManager{
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finding language by code
|
// Finding language by code
|
||||||
var lang = Languages.languages.FirstOrDefault(l => l.Code == curStream?.AudioLang);
|
var lang = Languages.languages.FirstOrDefault(l => l.Code == curStream?.AudioLang) ?? new LanguageItem{
|
||||||
|
CrLocale = "und",
|
||||||
|
Locale = "un",
|
||||||
|
Code = "und",
|
||||||
|
Name = string.Empty,
|
||||||
|
Language = string.Empty
|
||||||
|
};
|
||||||
if (lang.Code == "und"){
|
if (lang.Code == "und"){
|
||||||
Console.Error.WriteLine($"Unable to find language for code {curStream?.AudioLang}");
|
Console.Error.WriteLine($"Unable to find language for code {curStream?.AudioLang}");
|
||||||
}
|
}
|
||||||
|
|
@ -1571,7 +1664,7 @@ public class CrunchyrollManager{
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.SkipSubs && data.DownloadSubs.IndexOf("none") == -1){
|
if (!options.SkipSubs && data.DownloadSubs.IndexOf("none") == -1){
|
||||||
await DownloadSubtitles(options, pbData, audDub, fileName, files, fileDir, data, (options.DlVideoOnce && dlVideoOnce && options.SyncTiming), videoDownloadMedia);
|
await DownloadSubtitles(options, pbData, audDub, fileName, files, fileDir, data, videoDownloadMedia);
|
||||||
} else{
|
} else{
|
||||||
Console.WriteLine("Subtitles downloading skipped!");
|
Console.WriteLine("Subtitles downloading skipped!");
|
||||||
}
|
}
|
||||||
|
|
@ -1646,7 +1739,7 @@ public class CrunchyrollManager{
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task DownloadSubtitles(CrDownloadOptions options, PlaybackData pbData, string audDub, string fileName, List<DownloadedMedia> files, string fileDir, CrunchyEpMeta data, bool needsDelay,
|
private static async Task DownloadSubtitles(CrDownloadOptions options, PlaybackData pbData, string audDub, string fileName, List<DownloadedMedia> files, string fileDir, CrunchyEpMeta data,
|
||||||
DownloadedMedia videoDownloadMedia){
|
DownloadedMedia videoDownloadMedia){
|
||||||
if (pbData.Meta != null && (pbData.Meta.Subtitles is{ Count: > 0 } || pbData.Meta.Captions is{ Count: > 0 })){
|
if (pbData.Meta != null && (pbData.Meta.Subtitles is{ Count: > 0 } || pbData.Meta.Captions is{ Count: > 0 })){
|
||||||
List<SubtitleInfo> subsData = pbData.Meta.Subtitles?.Values.ToList() ??[];
|
List<SubtitleInfo> subsData = pbData.Meta.Subtitles?.Values.ToList() ??[];
|
||||||
|
|
@ -1804,12 +1897,13 @@ public class CrunchyrollManager{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<(bool Ok, PartsData Parts, string tsFile)> DownloadVideo(VideoItem chosenVideoSegments, CrDownloadOptions options, string outFile, string tsFile, string tempTsFile, CrunchyEpMeta data,
|
private async Task<(bool Ok, PartsData Parts, string tsFile)> DownloadVideo(VideoItem chosenVideoSegments, CrDownloadOptions options, string outFile, string tempTsFile, CrunchyEpMeta data,
|
||||||
string fileDir){
|
string fileDir){
|
||||||
// Prepare for video download
|
// Prepare for video download
|
||||||
int totalParts = chosenVideoSegments.segments.Count;
|
int totalParts = chosenVideoSegments.segments.Count;
|
||||||
int mathParts = (int)Math.Ceiling((double)totalParts / options.Partsize);
|
int mathParts = (int)Math.Ceiling((double)totalParts / options.Partsize);
|
||||||
string mathMsg = $"({mathParts}*{options.Partsize})";
|
string mathMsg = $"({mathParts}*{options.Partsize})";
|
||||||
|
string tsFile;
|
||||||
Console.WriteLine($"Total parts in video stream: {totalParts} {mathMsg}");
|
Console.WriteLine($"Total parts in video stream: {totalParts} {mathMsg}");
|
||||||
|
|
||||||
if (Path.IsPathRooted(outFile)){
|
if (Path.IsPathRooted(outFile)){
|
||||||
|
|
@ -1843,9 +1937,10 @@ public class CrunchyrollManager{
|
||||||
return (videoDownloadResult.Ok, videoDownloadResult.Parts, tsFile);
|
return (videoDownloadResult.Ok, videoDownloadResult.Parts, tsFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<(bool Ok, PartsData Parts, string tsFile)> DownloadAudio(AudioItem chosenAudioSegments, CrDownloadOptions options, string outFile, string tsFile, string tempTsFile, CrunchyEpMeta data,
|
private async Task<(bool Ok, PartsData Parts, string tsFile)> DownloadAudio(AudioItem chosenAudioSegments, CrDownloadOptions options, string outFile, string tempTsFile, CrunchyEpMeta data,
|
||||||
string fileDir){
|
string fileDir){
|
||||||
// Prepare for audio download
|
// Prepare for audio download
|
||||||
|
string tsFile;
|
||||||
int totalParts = chosenAudioSegments.segments.Count;
|
int totalParts = chosenAudioSegments.segments.Count;
|
||||||
int mathParts = (int)Math.Ceiling((double)totalParts / options.Partsize);
|
int mathParts = (int)Math.Ceiling((double)totalParts / options.Partsize);
|
||||||
string mathMsg = $"({mathParts}*{options.Partsize})";
|
string mathMsg = $"({mathParts}*{options.Partsize})";
|
||||||
|
|
@ -1911,7 +2006,7 @@ public class CrunchyrollManager{
|
||||||
var playbackRequestResponse = await SendPlaybackRequestAsync(playbackEndpoint);
|
var playbackRequestResponse = await SendPlaybackRequestAsync(playbackEndpoint);
|
||||||
|
|
||||||
if (!playbackRequestResponse.IsOk){
|
if (!playbackRequestResponse.IsOk){
|
||||||
playbackRequestResponse = await HandleStreamErrorsAsync(playbackRequestResponse, mediaGuidId, playbackEndpoint);
|
playbackRequestResponse = await HandleStreamErrorsAsync(playbackRequestResponse, playbackEndpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playbackRequestResponse.IsOk){
|
if (playbackRequestResponse.IsOk){
|
||||||
|
|
@ -1922,7 +2017,7 @@ public class CrunchyrollManager{
|
||||||
playbackRequestResponse = await SendPlaybackRequestAsync(playbackEndpoint);
|
playbackRequestResponse = await SendPlaybackRequestAsync(playbackEndpoint);
|
||||||
|
|
||||||
if (!playbackRequestResponse.IsOk){
|
if (!playbackRequestResponse.IsOk){
|
||||||
playbackRequestResponse = await HandleStreamErrorsAsync(playbackRequestResponse, mediaGuidId, playbackEndpoint);
|
playbackRequestResponse = await HandleStreamErrorsAsync(playbackRequestResponse, playbackEndpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playbackRequestResponse.IsOk){
|
if (playbackRequestResponse.IsOk){
|
||||||
|
|
@ -1932,7 +2027,7 @@ public class CrunchyrollManager{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (IsOk: playbackRequestResponse.IsOk, pbData: temppbData, error: playbackRequestResponse.IsOk ? "" : playbackRequestResponse.ResponseContent);
|
return (playbackRequestResponse.IsOk, pbData: temppbData, error: playbackRequestResponse.IsOk ? "" : playbackRequestResponse.ResponseContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<(bool IsOk, string ResponseContent)> SendPlaybackRequestAsync(string endpoint){
|
private async Task<(bool IsOk, string ResponseContent)> SendPlaybackRequestAsync(string endpoint){
|
||||||
|
|
@ -1940,7 +2035,7 @@ public class CrunchyrollManager{
|
||||||
return await HttpClientReq.Instance.SendHttpRequest(request);
|
return await HttpClientReq.Instance.SendHttpRequest(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<(bool IsOk, string ResponseContent)> HandleStreamErrorsAsync((bool IsOk, string ResponseContent) response, string mediaGuidId, string endpoint){
|
private async Task<(bool IsOk, string ResponseContent)> HandleStreamErrorsAsync((bool IsOk, string ResponseContent) response, string endpoint){
|
||||||
if (response.IsOk || string.IsNullOrEmpty(response.ResponseContent)) return response;
|
if (response.IsOk || string.IsNullOrEmpty(response.ResponseContent)) return response;
|
||||||
|
|
||||||
var error = StreamError.FromJson(response.ResponseContent);
|
var error = StreamError.FromJson(response.ResponseContent);
|
||||||
|
|
@ -1990,7 +2085,7 @@ public class CrunchyrollManager{
|
||||||
temppbData.Meta = new PlaybackMeta{
|
temppbData.Meta = new PlaybackMeta{
|
||||||
AudioLocale = playStream.AudioLocale,
|
AudioLocale = playStream.AudioLocale,
|
||||||
Versions = playStream.Versions,
|
Versions = playStream.Versions,
|
||||||
Bifs = new List<string>{ playStream.Bifs },
|
Bifs = new List<string>{ playStream.Bifs ?? "" },
|
||||||
MediaId = mediaId,
|
MediaId = mediaId,
|
||||||
Captions = playStream.Captions,
|
Captions = playStream.Captions,
|
||||||
Subtitles = new Subtitles()
|
Subtitles = new Subtitles()
|
||||||
|
|
@ -2012,7 +2107,7 @@ public class CrunchyrollManager{
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
private async Task<bool> ParseChapters(string currentMediaId, List<string> compiledChapters){
|
private async Task ParseChapters(string currentMediaId, List<string> compiledChapters){
|
||||||
var showRequest = HttpClientReq.CreateRequestMessage($"https://static.crunchyroll.com/skip-events/production/{currentMediaId}.json", HttpMethod.Get, true, true, null);
|
var showRequest = HttpClientReq.CreateRequestMessage($"https://static.crunchyroll.com/skip-events/production/{currentMediaId}.json", HttpMethod.Get, true, true, null);
|
||||||
|
|
||||||
var showRequestResponse = await HttpClientReq.Instance.SendHttpRequest(showRequest, true);
|
var showRequestResponse = await HttpClientReq.Instance.SendHttpRequest(showRequest, true);
|
||||||
|
|
@ -2024,11 +2119,11 @@ public class CrunchyrollManager{
|
||||||
try{
|
try{
|
||||||
JObject jObject = JObject.Parse(showRequestResponse.ResponseContent);
|
JObject jObject = JObject.Parse(showRequestResponse.ResponseContent);
|
||||||
|
|
||||||
if (jObject.TryGetValue("lastUpdate", out JToken lastUpdateToken)){
|
if (jObject.TryGetValue("lastUpdate", out JToken? lastUpdateToken)){
|
||||||
chapterData.lastUpdate = lastUpdateToken.ToObject<DateTime>();
|
chapterData.lastUpdate = lastUpdateToken.ToObject<DateTime>();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jObject.TryGetValue("mediaId", out JToken mediaIdToken)){
|
if (jObject.TryGetValue("mediaId", out JToken? mediaIdToken)){
|
||||||
chapterData.mediaId = mediaIdToken.ToObject<string>();
|
chapterData.mediaId = mediaIdToken.ToObject<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2037,7 +2132,7 @@ public class CrunchyrollManager{
|
||||||
foreach (var property in jObject.Properties()){
|
foreach (var property in jObject.Properties()){
|
||||||
if (property.Value.Type == JTokenType.Object && property.Name != "lastUpdate" && property.Name != "mediaId"){
|
if (property.Value.Type == JTokenType.Object && property.Name != "lastUpdate" && property.Name != "mediaId"){
|
||||||
try{
|
try{
|
||||||
CrunchyChapter chapter = property.Value.ToObject<CrunchyChapter>();
|
CrunchyChapter chapter = property.Value.ToObject<CrunchyChapter>() ?? new CrunchyChapter();
|
||||||
chapterData.Chapters.Add(chapter);
|
chapterData.Chapters.Add(chapter);
|
||||||
} catch (Exception ex){
|
} catch (Exception ex){
|
||||||
Console.Error.WriteLine($"Error parsing chapter: {ex.Message}");
|
Console.Error.WriteLine($"Error parsing chapter: {ex.Message}");
|
||||||
|
|
@ -2046,7 +2141,7 @@ public class CrunchyrollManager{
|
||||||
}
|
}
|
||||||
} catch (Exception ex){
|
} catch (Exception ex){
|
||||||
Console.Error.WriteLine($"Error parsing JSON response: {ex.Message}");
|
Console.Error.WriteLine($"Error parsing JSON response: {ex.Message}");
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chapterData.Chapters.Count > 0){
|
if (chapterData.Chapters.Count > 0){
|
||||||
|
|
@ -2098,8 +2193,6 @@ public class CrunchyrollManager{
|
||||||
compiledChapters.Add($"CHAPTER{chapterNumber}NAME={formattedChapterType} End");
|
compiledChapters.Add($"CHAPTER{chapterNumber}NAME={formattedChapterType} End");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
} else{
|
} else{
|
||||||
Console.WriteLine("Chapter request failed, attempting old API ");
|
Console.WriteLine("Chapter request failed, attempting old API ");
|
||||||
|
|
@ -2109,7 +2202,7 @@ public class CrunchyrollManager{
|
||||||
showRequestResponse = await HttpClientReq.Instance.SendHttpRequest(showRequest, true);
|
showRequestResponse = await HttpClientReq.Instance.SendHttpRequest(showRequest, true);
|
||||||
|
|
||||||
if (showRequestResponse.IsOk){
|
if (showRequestResponse.IsOk){
|
||||||
CrunchyOldChapter chapterData = Helpers.Deserialize<CrunchyOldChapter>(showRequestResponse.ResponseContent, SettingsJsonSerializerSettings);
|
CrunchyOldChapter chapterData = Helpers.Deserialize<CrunchyOldChapter>(showRequestResponse.ResponseContent, SettingsJsonSerializerSettings) ?? new CrunchyOldChapter();
|
||||||
|
|
||||||
DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||||
|
|
||||||
|
|
@ -2137,13 +2230,10 @@ public class CrunchyrollManager{
|
||||||
chapterNumber = (compiledChapters.Count / 2) + 1;
|
chapterNumber = (compiledChapters.Count / 2) + 1;
|
||||||
compiledChapters.Add($"CHAPTER{chapterNumber}={endFormatted}");
|
compiledChapters.Add($"CHAPTER{chapterNumber}={endFormatted}");
|
||||||
compiledChapters.Add($"CHAPTER{chapterNumber}NAME=Episode");
|
compiledChapters.Add($"CHAPTER{chapterNumber}NAME=Episode");
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.Error.WriteLine("Chapter request failed");
|
Console.Error.WriteLine("Chapter request failed");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -12,8 +12,10 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using CRD.Utils;
|
using CRD.Utils;
|
||||||
using CRD.Utils.Ffmpeg_Encoding;
|
using CRD.Utils.Ffmpeg_Encoding;
|
||||||
|
using CRD.Utils.Files;
|
||||||
using CRD.Utils.Sonarr;
|
using CRD.Utils.Sonarr;
|
||||||
using CRD.Utils.Structs;
|
using CRD.Utils.Structs;
|
||||||
|
using CRD.Utils.Structs.Crunchyroll;
|
||||||
using CRD.Utils.Structs.History;
|
using CRD.Utils.Structs.History;
|
||||||
using CRD.ViewModels;
|
using CRD.ViewModels;
|
||||||
using CRD.ViewModels.Utils;
|
using CRD.ViewModels.Utils;
|
||||||
|
|
@ -42,7 +44,7 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _includeCcSubs;
|
private bool _includeCcSubs;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private ComboBoxItem _selectedScaledBorderAndShadow;
|
private ComboBoxItem _selectedScaledBorderAndShadow;
|
||||||
|
|
||||||
|
|
@ -77,10 +79,10 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _skipSubMux;
|
private bool _skipSubMux;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private double? _leadingNumbers;
|
private double? _leadingNumbers;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private double? _partSize;
|
private double? _partSize;
|
||||||
|
|
||||||
|
|
@ -107,7 +109,7 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private ComboBoxItem _selectedHSLang;
|
private ComboBoxItem _selectedHSLang;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private ComboBoxItem _selectedDescriptionLang;
|
private ComboBoxItem _selectedDescriptionLang;
|
||||||
|
|
||||||
|
|
@ -131,81 +133,78 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private ComboBoxItem? _selectedAudioQuality;
|
private ComboBoxItem? _selectedAudioQuality;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private ObservableCollection<ListBoxItem> _selectedSubLang = new();
|
private ObservableCollection<ListBoxItem> _selectedSubLang =[];
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private Color _listBoxColor;
|
private Color _listBoxColor;
|
||||||
|
|
||||||
public ObservableCollection<ComboBoxItem> VideoQualityList{ get; } = new(){
|
|
||||||
new ComboBoxItem(){ Content = "best" },
|
|
||||||
new ComboBoxItem(){ Content = "1080" },
|
|
||||||
new ComboBoxItem(){ Content = "720" },
|
|
||||||
new ComboBoxItem(){ Content = "480" },
|
|
||||||
new ComboBoxItem(){ Content = "360" },
|
|
||||||
new ComboBoxItem(){ Content = "240" },
|
|
||||||
new ComboBoxItem(){ Content = "worst" },
|
|
||||||
};
|
|
||||||
|
|
||||||
public ObservableCollection<ComboBoxItem> AudioQualityList{ get; } = new(){
|
public ObservableCollection<ComboBoxItem> VideoQualityList{ get; } =[
|
||||||
new ComboBoxItem(){ Content = "best" },
|
new(){ Content = "best" },
|
||||||
new ComboBoxItem(){ Content = "128kB/s" },
|
new(){ Content = "1080" },
|
||||||
new ComboBoxItem(){ Content = "96kB/s" },
|
new(){ Content = "720" },
|
||||||
new ComboBoxItem(){ Content = "64kB/s" },
|
new(){ Content = "480" },
|
||||||
new ComboBoxItem(){ Content = "worst" },
|
new(){ Content = "360" },
|
||||||
};
|
new(){ Content = "240" },
|
||||||
|
new(){ Content = "worst" }
|
||||||
|
];
|
||||||
|
|
||||||
public ObservableCollection<ComboBoxItem> HardSubLangList{ get; } = new(){
|
public ObservableCollection<ComboBoxItem> AudioQualityList{ get; } =[
|
||||||
new ComboBoxItem(){ Content = "none" },
|
new(){ Content = "best" },
|
||||||
};
|
new(){ Content = "128kB/s" },
|
||||||
|
new(){ Content = "96kB/s" },
|
||||||
public ObservableCollection<ComboBoxItem> DescriptionLangList{ get; } = new(){
|
new(){ Content = "64kB/s" },
|
||||||
new ComboBoxItem(){ Content = "default" },
|
new(){ Content = "worst" }
|
||||||
new ComboBoxItem(){ Content = "de-DE" },
|
];
|
||||||
new ComboBoxItem(){ Content = "en-US" },
|
|
||||||
new ComboBoxItem(){ Content = "es-419" },
|
|
||||||
new ComboBoxItem(){ Content = "es-ES" },
|
|
||||||
new ComboBoxItem(){ Content = "fr-FR" },
|
|
||||||
new ComboBoxItem(){ Content = "it-IT" },
|
|
||||||
new ComboBoxItem(){ Content = "pt-BR" },
|
|
||||||
new ComboBoxItem(){ Content = "pt-PT" },
|
|
||||||
new ComboBoxItem(){ Content = "ru-RU" },
|
|
||||||
new ComboBoxItem(){ Content = "hi-IN" },
|
|
||||||
new ComboBoxItem(){ Content = "ar-SA" },
|
|
||||||
};
|
|
||||||
|
|
||||||
public ObservableCollection<ListBoxItem> DubLangList{ get; } = new(){
|
public ObservableCollection<ComboBoxItem> HardSubLangList{ get; } =[
|
||||||
};
|
new(){ Content = "none" }
|
||||||
|
];
|
||||||
|
|
||||||
|
public ObservableCollection<ComboBoxItem> DescriptionLangList{ get; } =[
|
||||||
|
new(){ Content = "default" },
|
||||||
|
new(){ Content = "de-DE" },
|
||||||
|
new(){ Content = "en-US" },
|
||||||
|
new(){ Content = "es-419" },
|
||||||
|
new(){ Content = "es-ES" },
|
||||||
|
new(){ Content = "fr-FR" },
|
||||||
|
new(){ Content = "it-IT" },
|
||||||
|
new(){ Content = "pt-BR" },
|
||||||
|
new(){ Content = "pt-PT" },
|
||||||
|
new(){ Content = "ru-RU" },
|
||||||
|
new(){ Content = "hi-IN" },
|
||||||
|
new(){ Content = "ar-SA" }
|
||||||
|
];
|
||||||
|
|
||||||
|
public ObservableCollection<ListBoxItem> DubLangList{ get; } =[];
|
||||||
|
|
||||||
|
|
||||||
public ObservableCollection<ComboBoxItem> DefaultDubLangList{ get; } = new(){
|
public ObservableCollection<ComboBoxItem> DefaultDubLangList{ get; } =[];
|
||||||
};
|
|
||||||
|
|
||||||
public ObservableCollection<ComboBoxItem> DefaultSubLangList{ get; } = new(){
|
public ObservableCollection<ComboBoxItem> DefaultSubLangList{ get; } =[];
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
public ObservableCollection<ListBoxItem> SubLangList{ get; } = new(){
|
public ObservableCollection<ListBoxItem> SubLangList{ get; } =[
|
||||||
new ListBoxItem(){ Content = "all" },
|
new(){ Content = "all" },
|
||||||
new ListBoxItem(){ Content = "none" },
|
new(){ Content = "none" }
|
||||||
};
|
];
|
||||||
|
|
||||||
public ObservableCollection<ComboBoxItem> StreamEndpoints{ get; } = new(){
|
public ObservableCollection<ComboBoxItem> StreamEndpoints{ get; } =[
|
||||||
new ComboBoxItem(){ Content = "web/firefox" },
|
new(){ Content = "web/firefox" },
|
||||||
new ComboBoxItem(){ Content = "console/switch" },
|
new(){ Content = "console/switch" },
|
||||||
new ComboBoxItem(){ Content = "console/ps4" },
|
new(){ Content = "console/ps4" },
|
||||||
new ComboBoxItem(){ Content = "console/ps5" },
|
new(){ Content = "console/ps5" },
|
||||||
new ComboBoxItem(){ Content = "console/xbox_one" },
|
new(){ Content = "console/xbox_one" },
|
||||||
new ComboBoxItem(){ Content = "web/edge" },
|
new(){ Content = "web/edge" },
|
||||||
// new ComboBoxItem(){ Content = "web/safari" },
|
// new (){ Content = "web/safari" },
|
||||||
new ComboBoxItem(){ Content = "web/chrome" },
|
new(){ Content = "web/chrome" },
|
||||||
new ComboBoxItem(){ Content = "web/fallback" },
|
new(){ Content = "web/fallback" },
|
||||||
// new ComboBoxItem(){ Content = "ios/iphone" },
|
// new (){ Content = "ios/iphone" },
|
||||||
// new ComboBoxItem(){ Content = "ios/ipad" },
|
// new (){ Content = "ios/ipad" },
|
||||||
new ComboBoxItem(){ Content = "android/phone" },
|
new(){ Content = "android/phone" },
|
||||||
new ComboBoxItem(){ Content = "tv/samsung" },
|
new(){ Content = "tv/samsung" }
|
||||||
};
|
];
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _isEncodeEnabled;
|
private bool _isEncodeEnabled;
|
||||||
|
|
@ -214,8 +213,8 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
||||||
private StringItem _selectedEncodingPreset;
|
private StringItem _selectedEncodingPreset;
|
||||||
|
|
||||||
public ObservableCollection<StringItem> EncodingPresetsList{ get; } = new();
|
public ObservableCollection<StringItem> EncodingPresetsList{ get; } = new();
|
||||||
|
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _cCSubsMuxingFlag;
|
private bool _cCSubsMuxingFlag;
|
||||||
|
|
||||||
|
|
@ -224,11 +223,13 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _signsSubsAsForced;
|
private bool _signsSubsAsForced;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _searchFetchFeaturedMusic;
|
||||||
|
|
||||||
private bool settingsLoaded;
|
private bool settingsLoaded;
|
||||||
|
|
||||||
public CrunchyrollSettingsViewModel(){
|
public CrunchyrollSettingsViewModel(){
|
||||||
|
|
||||||
foreach (var languageItem in Languages.languages){
|
foreach (var languageItem in Languages.languages){
|
||||||
HardSubLangList.Add(new ComboBoxItem{ Content = languageItem.CrLocale });
|
HardSubLangList.Add(new ComboBoxItem{ Content = languageItem.CrLocale });
|
||||||
SubLangList.Add(new ListBoxItem{ Content = languageItem.CrLocale });
|
SubLangList.Add(new ListBoxItem{ Content = languageItem.CrLocale });
|
||||||
|
|
@ -244,7 +245,7 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
||||||
|
|
||||||
CrDownloadOptions options = CrunchyrollManager.Instance.CrunOptions;
|
CrDownloadOptions options = CrunchyrollManager.Instance.CrunOptions;
|
||||||
|
|
||||||
StringItem? encodingPresetSelected = EncodingPresetsList.FirstOrDefault(a => a.stringValue != null && a.stringValue == options.EncodingPresetName) ?? null;
|
StringItem? encodingPresetSelected = EncodingPresetsList.FirstOrDefault(a => !string.IsNullOrEmpty(a.stringValue) && a.stringValue == options.EncodingPresetName) ?? null;
|
||||||
SelectedEncodingPreset = encodingPresetSelected ?? EncodingPresetsList[0];
|
SelectedEncodingPreset = encodingPresetSelected ?? EncodingPresetsList[0];
|
||||||
|
|
||||||
ComboBoxItem? descriptionLang = DescriptionLangList.FirstOrDefault(a => a.Content != null && (string)a.Content == options.DescriptionLang) ?? null;
|
ComboBoxItem? descriptionLang = DescriptionLangList.FirstOrDefault(a => a.Content != null && (string)a.Content == options.DescriptionLang) ?? null;
|
||||||
|
|
@ -275,7 +276,7 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
||||||
foreach (var listBoxItem in dubLang){
|
foreach (var listBoxItem in dubLang){
|
||||||
SelectedDubLang.Add(listBoxItem);
|
SelectedDubLang.Add(listBoxItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
AddScaledBorderAndShadow = options.SubsAddScaledBorder is ScaledBorderAndShadowSelection.ScaledBorderAndShadowNo or ScaledBorderAndShadowSelection.ScaledBorderAndShadowYes;
|
AddScaledBorderAndShadow = options.SubsAddScaledBorder is ScaledBorderAndShadowSelection.ScaledBorderAndShadowNo or ScaledBorderAndShadowSelection.ScaledBorderAndShadowYes;
|
||||||
SelectedScaledBorderAndShadow = GetScaledBorderAndShadowFromOptions(options);
|
SelectedScaledBorderAndShadow = GetScaledBorderAndShadowFromOptions(options);
|
||||||
|
|
||||||
|
|
@ -301,22 +302,23 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
||||||
SkipSubMux = options.SkipSubsMux;
|
SkipSubMux = options.SkipSubsMux;
|
||||||
LeadingNumbers = options.Numbers;
|
LeadingNumbers = options.Numbers;
|
||||||
FileName = options.FileName;
|
FileName = options.FileName;
|
||||||
|
SearchFetchFeaturedMusic = options.SearchFetchFeaturedMusic;
|
||||||
|
|
||||||
ComboBoxItem? qualityAudio = AudioQualityList.FirstOrDefault(a => a.Content != null && (string)a.Content == options.QualityAudio) ?? null;
|
ComboBoxItem? qualityAudio = AudioQualityList.FirstOrDefault(a => a.Content != null && (string)a.Content == options.QualityAudio) ?? null;
|
||||||
SelectedAudioQuality = qualityAudio ?? AudioQualityList[0];
|
SelectedAudioQuality = qualityAudio ?? AudioQualityList[0];
|
||||||
|
|
||||||
ComboBoxItem? qualityVideo = VideoQualityList.FirstOrDefault(a => a.Content != null && (string)a.Content == options.QualityVideo) ?? null;
|
ComboBoxItem? qualityVideo = VideoQualityList.FirstOrDefault(a => a.Content != null && (string)a.Content == options.QualityVideo) ?? null;
|
||||||
SelectedVideoQuality = qualityVideo ?? VideoQualityList[0];
|
SelectedVideoQuality = qualityVideo ?? VideoQualityList[0];
|
||||||
|
|
||||||
MkvMergeOptions.Clear();
|
MkvMergeOptions.Clear();
|
||||||
if (options.MkvmergeOptions != null){
|
if (options.MkvmergeOptions is{ Count: > 0 }){
|
||||||
foreach (var mkvmergeParam in options.MkvmergeOptions){
|
foreach (var mkvmergeParam in options.MkvmergeOptions){
|
||||||
MkvMergeOptions.Add(new StringItem(){ stringValue = mkvmergeParam });
|
MkvMergeOptions.Add(new StringItem(){ stringValue = mkvmergeParam });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FfmpegOptions.Clear();
|
FfmpegOptions.Clear();
|
||||||
if (options.FfmpegOptions != null){
|
if (options.FfmpegOptions is{ Count: > 0 }){
|
||||||
foreach (var ffmpegParam in options.FfmpegOptions){
|
foreach (var ffmpegParam in options.FfmpegOptions){
|
||||||
FfmpegOptions.Add(new StringItem(){ stringValue = ffmpegParam });
|
FfmpegOptions.Add(new StringItem(){ stringValue = ffmpegParam });
|
||||||
}
|
}
|
||||||
|
|
@ -341,7 +343,7 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
||||||
if (!settingsLoaded){
|
if (!settingsLoaded){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CrunchyrollManager.Instance.CrunOptions.SignsSubsAsForced = SignsSubsAsForced;
|
CrunchyrollManager.Instance.CrunOptions.SignsSubsAsForced = SignsSubsAsForced;
|
||||||
CrunchyrollManager.Instance.CrunOptions.CcSubsMuxingFlag = CCSubsMuxingFlag;
|
CrunchyrollManager.Instance.CrunOptions.CcSubsMuxingFlag = CCSubsMuxingFlag;
|
||||||
CrunchyrollManager.Instance.CrunOptions.CcSubsFont = CCSubsFont;
|
CrunchyrollManager.Instance.CrunOptions.CcSubsFont = CCSubsFont;
|
||||||
|
|
@ -364,8 +366,9 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
||||||
CrunchyrollManager.Instance.CrunOptions.FileName = FileName;
|
CrunchyrollManager.Instance.CrunOptions.FileName = FileName;
|
||||||
CrunchyrollManager.Instance.CrunOptions.IncludeSignsSubs = IncludeSignSubs;
|
CrunchyrollManager.Instance.CrunOptions.IncludeSignsSubs = IncludeSignSubs;
|
||||||
CrunchyrollManager.Instance.CrunOptions.IncludeCcSubs = IncludeCcSubs;
|
CrunchyrollManager.Instance.CrunOptions.IncludeCcSubs = IncludeCcSubs;
|
||||||
CrunchyrollManager.Instance.CrunOptions.Partsize = Math.Clamp((int)(PartSize ?? 0), 0, 10000);
|
CrunchyrollManager.Instance.CrunOptions.Partsize = Math.Clamp((int)(PartSize ?? 1), 1, 10000);
|
||||||
|
CrunchyrollManager.Instance.CrunOptions.SearchFetchFeaturedMusic = SearchFetchFeaturedMusic;
|
||||||
|
|
||||||
CrunchyrollManager.Instance.CrunOptions.SubsAddScaledBorder = GetScaledBorderAndShadowSelection();
|
CrunchyrollManager.Instance.CrunOptions.SubsAddScaledBorder = GetScaledBorderAndShadowSelection();
|
||||||
|
|
||||||
List<string> softSubs = new List<string>();
|
List<string> softSubs = new List<string>();
|
||||||
|
|
@ -378,7 +381,7 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
||||||
string descLang = SelectedDescriptionLang.Content + "";
|
string descLang = SelectedDescriptionLang.Content + "";
|
||||||
|
|
||||||
CrunchyrollManager.Instance.CrunOptions.DescriptionLang = descLang != "default" ? descLang : CrunchyrollManager.Instance.DefaultLocale;
|
CrunchyrollManager.Instance.CrunOptions.DescriptionLang = descLang != "default" ? descLang : CrunchyrollManager.Instance.DefaultLocale;
|
||||||
|
|
||||||
string hslang = SelectedHSLang.Content + "";
|
string hslang = SelectedHSLang.Content + "";
|
||||||
|
|
||||||
CrunchyrollManager.Instance.CrunOptions.Hslang = hslang != "none" ? Languages.FindLang(hslang).Locale : hslang;
|
CrunchyrollManager.Instance.CrunOptions.Hslang = hslang != "none" ? Languages.FindLang(hslang).Locale : hslang;
|
||||||
|
|
@ -398,7 +401,7 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
||||||
|
|
||||||
CrunchyrollManager.Instance.CrunOptions.QualityAudio = SelectedAudioQuality?.Content + "";
|
CrunchyrollManager.Instance.CrunOptions.QualityAudio = SelectedAudioQuality?.Content + "";
|
||||||
CrunchyrollManager.Instance.CrunOptions.QualityVideo = SelectedVideoQuality?.Content + "";
|
CrunchyrollManager.Instance.CrunOptions.QualityVideo = SelectedVideoQuality?.Content + "";
|
||||||
|
|
||||||
List<string> mkvmergeParams = new List<string>();
|
List<string> mkvmergeParams = new List<string>();
|
||||||
foreach (var mkvmergeParam in MkvMergeOptions){
|
foreach (var mkvmergeParam in MkvMergeOptions){
|
||||||
mkvmergeParams.Add(mkvmergeParam.stringValue);
|
mkvmergeParams.Add(mkvmergeParam.stringValue);
|
||||||
|
|
@ -413,7 +416,7 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
||||||
|
|
||||||
CrunchyrollManager.Instance.CrunOptions.FfmpegOptions = ffmpegParams;
|
CrunchyrollManager.Instance.CrunOptions.FfmpegOptions = ffmpegParams;
|
||||||
|
|
||||||
CfgManager.WriteSettingsToFile();
|
CfgManager.WriteCrSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -469,7 +472,7 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
||||||
FfmpegOptions.Remove(param);
|
FfmpegOptions.Remove(param);
|
||||||
RaisePropertyChanged(nameof(FfmpegOptions));
|
RaisePropertyChanged(nameof(FfmpegOptions));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Changes(object? sender, NotifyCollectionChangedEventArgs e){
|
private void Changes(object? sender, NotifyCollectionChangedEventArgs e){
|
||||||
UpdateSettings();
|
UpdateSettings();
|
||||||
|
|
||||||
|
|
@ -515,7 +518,6 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
||||||
CrunchyrollManager.Instance.HistoryList =[];
|
CrunchyrollManager.Instance.HistoryList =[];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
|
|
@ -542,9 +544,8 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
||||||
}
|
}
|
||||||
|
|
||||||
settingsLoaded = true;
|
settingsLoaded = true;
|
||||||
StringItem? encodingPresetSelected = EncodingPresetsList.FirstOrDefault(a => a.stringValue != null && a.stringValue == CrunchyrollManager.Instance.CrunOptions.EncodingPresetName) ?? null;
|
StringItem? encodingPresetSelected = EncodingPresetsList.FirstOrDefault(a => string.IsNullOrEmpty(a.stringValue) && a.stringValue == CrunchyrollManager.Instance.CrunOptions.EncodingPresetName) ?? null;
|
||||||
SelectedEncodingPreset = encodingPresetSelected ?? EncodingPresetsList[0];
|
SelectedEncodingPreset = encodingPresetSelected ?? EncodingPresetsList[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -191,6 +191,22 @@
|
||||||
</controls:SettingsExpander>
|
</controls:SettingsExpander>
|
||||||
|
|
||||||
|
|
||||||
|
<controls:SettingsExpander Header="Search Settings"
|
||||||
|
IconSource="Find"
|
||||||
|
Description="Adjust search settings"
|
||||||
|
IsExpanded="False">
|
||||||
|
|
||||||
|
|
||||||
|
<controls:SettingsExpanderItem Content="Include featured Music"
|
||||||
|
Description="Fetch featured music when searching for a series">
|
||||||
|
<controls:SettingsExpanderItem.Footer>
|
||||||
|
<CheckBox IsChecked="{Binding SearchFetchFeaturedMusic}"> </CheckBox>
|
||||||
|
</controls:SettingsExpanderItem.Footer>
|
||||||
|
</controls:SettingsExpanderItem>
|
||||||
|
|
||||||
|
|
||||||
|
</controls:SettingsExpander>
|
||||||
|
|
||||||
<controls:SettingsExpander Header="Download Settings"
|
<controls:SettingsExpander Header="Download Settings"
|
||||||
IconSource="Download"
|
IconSource="Download"
|
||||||
Description="Adjust download settings"
|
Description="Adjust download settings"
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,14 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CRD.Downloader.Crunchyroll;
|
using CRD.Downloader.Crunchyroll;
|
||||||
using CRD.Utils;
|
using CRD.Utils;
|
||||||
|
using CRD.Utils.Files;
|
||||||
using CRD.Utils.Sonarr;
|
using CRD.Utils.Sonarr;
|
||||||
using CRD.Utils.Sonarr.Models;
|
using CRD.Utils.Sonarr.Models;
|
||||||
using CRD.Utils.Structs;
|
using CRD.Utils.Structs;
|
||||||
|
using CRD.Utils.Structs.Crunchyroll.Music;
|
||||||
using CRD.Utils.Structs.History;
|
using CRD.Utils.Structs.History;
|
||||||
using CRD.Views;
|
using CRD.Views;
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
|
|
@ -16,10 +17,10 @@ using ReactiveUI;
|
||||||
|
|
||||||
namespace CRD.Downloader;
|
namespace CRD.Downloader;
|
||||||
|
|
||||||
public class History(){
|
public class History{
|
||||||
private readonly CrunchyrollManager crunInstance = CrunchyrollManager.Instance;
|
private readonly CrunchyrollManager crunInstance = CrunchyrollManager.Instance;
|
||||||
|
|
||||||
public async Task<bool> CRUpdateSeries(string seriesId, string? seasonId){
|
public async Task<bool> CrUpdateSeries(string seriesId, string? seasonId){
|
||||||
await crunInstance.CrAuth.RefreshToken(true);
|
await crunInstance.CrAuth.RefreshToken(true);
|
||||||
|
|
||||||
CrSeriesSearch? parsedSeries = await crunInstance.CrSeries.ParseSeriesById(seriesId, "ja-JP", true);
|
CrSeriesSearch? parsedSeries = await crunInstance.CrSeries.ParseSeriesById(seriesId, "ja-JP", true);
|
||||||
|
|
@ -46,7 +47,8 @@ public class History(){
|
||||||
|
|
||||||
|
|
||||||
var seasonData = await crunInstance.CrSeries.GetSeasonDataById(sId, string.IsNullOrEmpty(crunInstance.CrunOptions.HistoryLang) ? crunInstance.DefaultLocale : crunInstance.CrunOptions.HistoryLang, true);
|
var seasonData = await crunInstance.CrSeries.GetSeasonDataById(sId, string.IsNullOrEmpty(crunInstance.CrunOptions.HistoryLang) ? crunInstance.DefaultLocale : crunInstance.CrunOptions.HistoryLang, true);
|
||||||
if (seasonData.Data is{ Count: > 0 }) await UpdateWithSeasonData(seasonData.Data);
|
|
||||||
|
if (seasonData.Data is{ Count: > 0 }) await UpdateWithSeasonData(seasonData.Data.ToList<IHistorySource>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -64,6 +66,141 @@ public class History(){
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task UpdateWithMusicEpisodeList(List<CrunchyMusicVideo> episodeList){
|
||||||
|
if (episodeList is{ Count: > 0 }){
|
||||||
|
if (crunInstance.CrunOptions is{ History: true, HistoryIncludeCrArtists: true }){
|
||||||
|
var concertGroups = episodeList.Where(e => e.EpisodeType == EpisodeType.Concert).GroupBy(e => e.Artist.Id);
|
||||||
|
var musicVideoGroups = episodeList.Where(e => e.EpisodeType == EpisodeType.MusicVideo).GroupBy(e => e.Artist.Id);
|
||||||
|
|
||||||
|
foreach (var concertGroup in concertGroups){
|
||||||
|
await UpdateWithSeasonData(concertGroup.ToList<IHistorySource>());
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var musicVideoGroup in musicVideoGroups){
|
||||||
|
await UpdateWithSeasonData(musicVideoGroup.ToList<IHistorySource>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateWithEpisodeList(List<CrunchyEpisode> episodeList){
|
||||||
|
if (episodeList is{ Count: > 0 }){
|
||||||
|
var episodeVersions = episodeList.First().Versions;
|
||||||
|
if (episodeVersions != null){
|
||||||
|
var version = episodeVersions.Find(a => a.Original);
|
||||||
|
if (version?.AudioLocale != episodeList.First().AudioLocale){
|
||||||
|
await CrUpdateSeries(episodeList.First().SeriesId, version?.SeasonGuid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else{
|
||||||
|
await CrUpdateSeries(episodeList.First().SeriesId, "");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await UpdateWithSeasonData(episodeList.ToList<IHistorySource>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This method updates the History with a list of episodes. The episodes have to be from the same season.
|
||||||
|
/// </summary>
|
||||||
|
private async Task UpdateWithSeasonData(List<IHistorySource> episodeList){
|
||||||
|
if (episodeList is{ Count: > 0 }){
|
||||||
|
var firstEpisode = episodeList.First();
|
||||||
|
var seriesId = firstEpisode.GetSeriesId();
|
||||||
|
var historySeries = crunInstance.HistoryList.FirstOrDefault(series => series.SeriesId == seriesId);
|
||||||
|
if (historySeries != null){
|
||||||
|
historySeries.HistorySeriesAddDate ??= DateTime.Now;
|
||||||
|
historySeries.SeriesType = firstEpisode.GetSeriesType();
|
||||||
|
historySeries.SeriesStreamingService = StreamingService.Crunchyroll;
|
||||||
|
|
||||||
|
await RefreshSeriesData(seriesId, historySeries);
|
||||||
|
|
||||||
|
var historySeason = historySeries.Seasons.FirstOrDefault(s => s.SeasonId == firstEpisode.GetSeasonId());
|
||||||
|
|
||||||
|
if (historySeason != null){
|
||||||
|
historySeason.SeasonTitle = firstEpisode.GetSeasonTitle();
|
||||||
|
historySeason.SeasonNum = firstEpisode.GetSeasonNum();
|
||||||
|
historySeason.SpecialSeason = firstEpisode.IsSpecialSeason();
|
||||||
|
foreach (var historySource in episodeList){
|
||||||
|
if (historySource.GetSeasonId() != historySeason.SeasonId){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var historyEpisode = historySeason.EpisodesList.Find(e => e.EpisodeId == historySource.GetEpisodeId());
|
||||||
|
|
||||||
|
if (historyEpisode == null){
|
||||||
|
var newHistoryEpisode = new HistoryEpisode{
|
||||||
|
EpisodeTitle = historySource.GetEpisodeTitle(),
|
||||||
|
EpisodeDescription = historySource.GetEpisodeDescription(),
|
||||||
|
EpisodeId = historySource.GetEpisodeId(),
|
||||||
|
Episode = historySource.GetEpisodeNumber(),
|
||||||
|
EpisodeSeasonNum = historySource.GetSeasonNum(),
|
||||||
|
SpecialEpisode = historySource.IsSpecialEpisode(),
|
||||||
|
HistoryEpisodeAvailableDubLang = historySource.GetEpisodeAvailableDubLang(),
|
||||||
|
HistoryEpisodeAvailableSoftSubs = historySource.GetEpisodeAvailableSoftSubs(),
|
||||||
|
EpisodeCrPremiumAirDate = historySource.GetAvailableDate(),
|
||||||
|
EpisodeType = historySource.GetEpisodeType()
|
||||||
|
};
|
||||||
|
|
||||||
|
historySeason.EpisodesList.Add(newHistoryEpisode);
|
||||||
|
} else{
|
||||||
|
//Update existing episode
|
||||||
|
historyEpisode.EpisodeTitle = historySource.GetEpisodeTitle();
|
||||||
|
historyEpisode.SpecialEpisode = historySource.IsSpecialEpisode();
|
||||||
|
historyEpisode.EpisodeDescription = historySource.GetEpisodeDescription();
|
||||||
|
historyEpisode.EpisodeId = historySource.GetEpisodeId();
|
||||||
|
historyEpisode.Episode = historySource.GetEpisodeNumber();
|
||||||
|
historyEpisode.EpisodeSeasonNum = historySource.GetSeasonNum();
|
||||||
|
historyEpisode.EpisodeCrPremiumAirDate = historySource.GetAvailableDate();
|
||||||
|
historyEpisode.EpisodeType = historySource.GetEpisodeType();
|
||||||
|
|
||||||
|
historyEpisode.HistoryEpisodeAvailableDubLang = historySource.GetEpisodeAvailableDubLang();
|
||||||
|
historyEpisode.HistoryEpisodeAvailableSoftSubs = historySource.GetEpisodeAvailableSoftSubs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
historySeason.EpisodesList.Sort(new NumericStringPropertyComparer());
|
||||||
|
} else{
|
||||||
|
var newSeason = NewHistorySeason(episodeList, firstEpisode);
|
||||||
|
|
||||||
|
newSeason.EpisodesList.Sort(new NumericStringPropertyComparer());
|
||||||
|
|
||||||
|
historySeries.Seasons.Add(newSeason);
|
||||||
|
newSeason.Init();
|
||||||
|
}
|
||||||
|
|
||||||
|
historySeries.UpdateNewEpisodes();
|
||||||
|
} else if (!string.IsNullOrEmpty(seriesId)){
|
||||||
|
historySeries = new HistorySeries{
|
||||||
|
SeriesTitle = firstEpisode.GetSeriesTitle(),
|
||||||
|
SeriesId = firstEpisode.GetSeriesId(),
|
||||||
|
Seasons =[],
|
||||||
|
HistorySeriesAddDate = DateTime.Now,
|
||||||
|
SeriesType = firstEpisode.GetSeriesType(),
|
||||||
|
SeriesStreamingService = StreamingService.Crunchyroll
|
||||||
|
};
|
||||||
|
crunInstance.HistoryList.Add(historySeries);
|
||||||
|
|
||||||
|
var newSeason = NewHistorySeason(episodeList, firstEpisode);
|
||||||
|
|
||||||
|
newSeason.EpisodesList.Sort(new NumericStringPropertyComparer());
|
||||||
|
|
||||||
|
await RefreshSeriesData(seriesId, historySeries);
|
||||||
|
|
||||||
|
historySeries.Seasons.Add(newSeason);
|
||||||
|
historySeries.UpdateNewEpisodes();
|
||||||
|
historySeries.Init();
|
||||||
|
newSeason.Init();
|
||||||
|
}
|
||||||
|
|
||||||
|
SortItems();
|
||||||
|
if (historySeries != null){
|
||||||
|
SortSeasons(historySeries);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void SetAsDownloaded(string? seriesId, string? seasonId, string episodeId){
|
public void SetAsDownloaded(string? seriesId, string? seasonId, string episodeId){
|
||||||
var historySeries = crunInstance.HistoryList.FirstOrDefault(series => series.SeriesId == seriesId);
|
var historySeries = crunInstance.HistoryList.FirstOrDefault(series => series.SeriesId == seriesId);
|
||||||
|
|
||||||
|
|
@ -230,175 +367,57 @@ public class History(){
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task UpdateWithSeasonData(List<CrunchyEpisode>? episodeList, bool skippVersionCheck = true){
|
private SeriesDataCache? cachedSeries;
|
||||||
if (episodeList != null){
|
|
||||||
if (!skippVersionCheck){
|
|
||||||
var episodeVersions = episodeList.First().Versions;
|
|
||||||
if (episodeVersions != null){
|
|
||||||
var version = episodeVersions.Find(a => a.Original);
|
|
||||||
if (version.AudioLocale != episodeList.First().AudioLocale){
|
|
||||||
await CRUpdateSeries(episodeList.First().SeriesId, version.SeasonGuid);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else{
|
|
||||||
await CRUpdateSeries(episodeList.First().SeriesId, "");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var firstEpisode = episodeList.First();
|
|
||||||
var seriesId = firstEpisode.SeriesId;
|
|
||||||
var historySeries = crunInstance.HistoryList.FirstOrDefault(series => series.SeriesId == seriesId);
|
|
||||||
if (historySeries != null){
|
|
||||||
historySeries.HistorySeriesAddDate ??= DateTime.Now;
|
|
||||||
|
|
||||||
await RefreshSeriesData(seriesId, historySeries);
|
|
||||||
|
|
||||||
var historySeason = historySeries.Seasons.FirstOrDefault(s => s.SeasonId == firstEpisode.SeasonId);
|
|
||||||
|
|
||||||
if (historySeason != null){
|
|
||||||
historySeason.SeasonTitle = firstEpisode.SeasonTitle;
|
|
||||||
historySeason.SeasonNum = Helpers.ExtractNumberAfterS(firstEpisode.Identifier) ?? firstEpisode.SeasonNumber + "";
|
|
||||||
historySeason.SpecialSeason = CheckStringForSpecial(firstEpisode.Identifier);
|
|
||||||
foreach (var crunchyEpisode in episodeList){
|
|
||||||
var historyEpisode = historySeason.EpisodesList.Find(e => e.EpisodeId == crunchyEpisode.Id);
|
|
||||||
|
|
||||||
if (historyEpisode == null){
|
|
||||||
var langList = new List<string>();
|
|
||||||
|
|
||||||
if (crunchyEpisode.Versions != null){
|
|
||||||
langList.AddRange(crunchyEpisode.Versions.Select(version => version.AudioLocale));
|
|
||||||
} else{
|
|
||||||
langList.Add(crunchyEpisode.AudioLocale);
|
|
||||||
}
|
|
||||||
|
|
||||||
var newHistoryEpisode = new HistoryEpisode{
|
|
||||||
EpisodeTitle = GetEpisodeTitle(crunchyEpisode),
|
|
||||||
EpisodeDescription = crunchyEpisode.Description,
|
|
||||||
EpisodeId = crunchyEpisode.Id,
|
|
||||||
Episode = crunchyEpisode.Episode,
|
|
||||||
EpisodeSeasonNum = Helpers.ExtractNumberAfterS(firstEpisode.Identifier) ?? firstEpisode.SeasonNumber + "",
|
|
||||||
SpecialEpisode = !int.TryParse(crunchyEpisode.Episode, out _),
|
|
||||||
HistoryEpisodeAvailableDubLang = Languages.SortListByLangList(langList),
|
|
||||||
HistoryEpisodeAvailableSoftSubs = Languages.SortListByLangList(crunchyEpisode.SubtitleLocales),
|
|
||||||
EpisodeCrPremiumAirDate = crunchyEpisode.PremiumAvailableDate
|
|
||||||
};
|
|
||||||
|
|
||||||
historySeason.EpisodesList.Add(newHistoryEpisode);
|
|
||||||
} else{
|
|
||||||
var langList = new List<string>();
|
|
||||||
|
|
||||||
if (crunchyEpisode.Versions != null){
|
|
||||||
langList.AddRange(crunchyEpisode.Versions.Select(version => version.AudioLocale));
|
|
||||||
} else{
|
|
||||||
langList.Add(crunchyEpisode.AudioLocale);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Update existing episode
|
|
||||||
historyEpisode.EpisodeTitle = GetEpisodeTitle(crunchyEpisode);
|
|
||||||
historyEpisode.SpecialEpisode = !int.TryParse(crunchyEpisode.Episode, out _);
|
|
||||||
historyEpisode.EpisodeDescription = crunchyEpisode.Description;
|
|
||||||
historyEpisode.EpisodeId = crunchyEpisode.Id;
|
|
||||||
historyEpisode.Episode = crunchyEpisode.Episode;
|
|
||||||
historyEpisode.EpisodeSeasonNum = Helpers.ExtractNumberAfterS(crunchyEpisode.Identifier) ?? crunchyEpisode.SeasonNumber + "";
|
|
||||||
historyEpisode.EpisodeCrPremiumAirDate = crunchyEpisode.PremiumAvailableDate;
|
|
||||||
|
|
||||||
historyEpisode.HistoryEpisodeAvailableDubLang = Languages.SortListByLangList(langList);
|
|
||||||
historyEpisode.HistoryEpisodeAvailableSoftSubs = Languages.SortListByLangList(crunchyEpisode.SubtitleLocales);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
historySeason.EpisodesList.Sort(new NumericStringPropertyComparer());
|
|
||||||
} else{
|
|
||||||
var newSeason = NewHistorySeason(episodeList, firstEpisode);
|
|
||||||
|
|
||||||
newSeason.EpisodesList.Sort(new NumericStringPropertyComparer());
|
|
||||||
|
|
||||||
historySeries.Seasons.Add(newSeason);
|
|
||||||
newSeason.Init();
|
|
||||||
}
|
|
||||||
|
|
||||||
historySeries.UpdateNewEpisodes();
|
|
||||||
} else{
|
|
||||||
historySeries = new HistorySeries{
|
|
||||||
SeriesTitle = firstEpisode.SeriesTitle,
|
|
||||||
SeriesId = firstEpisode.SeriesId,
|
|
||||||
Seasons =[],
|
|
||||||
HistorySeriesAddDate = DateTime.Now,
|
|
||||||
};
|
|
||||||
crunInstance.HistoryList.Add(historySeries);
|
|
||||||
|
|
||||||
var newSeason = NewHistorySeason(episodeList, firstEpisode);
|
|
||||||
|
|
||||||
newSeason.EpisodesList.Sort(new NumericStringPropertyComparer());
|
|
||||||
|
|
||||||
await RefreshSeriesData(seriesId, historySeries);
|
|
||||||
|
|
||||||
historySeries.Seasons.Add(newSeason);
|
|
||||||
historySeries.UpdateNewEpisodes();
|
|
||||||
historySeries.Init();
|
|
||||||
newSeason.Init();
|
|
||||||
}
|
|
||||||
|
|
||||||
SortItems();
|
|
||||||
SortSeasons(historySeries);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private CrSeriesBase? cachedSeries;
|
|
||||||
|
|
||||||
private string GetEpisodeTitle(CrunchyEpisode crunchyEpisode){
|
|
||||||
if (crunchyEpisode.Identifier.Contains("|M|")){
|
|
||||||
if (string.IsNullOrEmpty(crunchyEpisode.Title)){
|
|
||||||
if (crunchyEpisode.SeasonTitle.StartsWith(crunchyEpisode.SeriesTitle)){
|
|
||||||
var splitTitle = crunchyEpisode.SeasonTitle.Split(new[]{ crunchyEpisode.SeriesTitle }, StringSplitOptions.None);
|
|
||||||
var titlePart = splitTitle.Length > 1 ? splitTitle[1] : splitTitle[0];
|
|
||||||
var cleanedTitle = Regex.Replace(titlePart, @"^[^a-zA-Z]+", "");
|
|
||||||
|
|
||||||
return cleanedTitle;
|
|
||||||
}
|
|
||||||
|
|
||||||
return crunchyEpisode.SeasonTitle;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (crunchyEpisode.Title.StartsWith(crunchyEpisode.SeriesTitle)){
|
|
||||||
var splitTitle = crunchyEpisode.Title.Split(new[]{ crunchyEpisode.SeriesTitle }, StringSplitOptions.None);
|
|
||||||
var titlePart = splitTitle.Length > 1 ? splitTitle[1] : splitTitle[0];
|
|
||||||
var cleanedTitle = Regex.Replace(titlePart, @"^[^a-zA-Z]+", "");
|
|
||||||
|
|
||||||
return cleanedTitle;
|
|
||||||
}
|
|
||||||
|
|
||||||
return crunchyEpisode.Title;
|
|
||||||
}
|
|
||||||
|
|
||||||
return crunchyEpisode.Title;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task RefreshSeriesData(string seriesId, HistorySeries historySeries){
|
private async Task RefreshSeriesData(string seriesId, HistorySeries historySeries){
|
||||||
if (cachedSeries == null || (cachedSeries.Data != null && cachedSeries.Data.First().Id != seriesId)){
|
if (cachedSeries == null || (!string.IsNullOrEmpty(cachedSeries.SeriesId) && cachedSeries.SeriesId != seriesId)){
|
||||||
cachedSeries = await crunInstance.CrSeries.SeriesById(seriesId, string.IsNullOrEmpty(crunInstance.CrunOptions.HistoryLang) ? crunInstance.DefaultLocale : crunInstance.CrunOptions.HistoryLang, true);
|
if (historySeries.SeriesType == SeriesType.Series){
|
||||||
} else{
|
var seriesData = await crunInstance.CrSeries.SeriesById(seriesId, string.IsNullOrEmpty(crunInstance.CrunOptions.HistoryLang) ? crunInstance.DefaultLocale : crunInstance.CrunOptions.HistoryLang, true);
|
||||||
if (cachedSeries?.Data != null){
|
if (seriesData is{ Data: not null }){
|
||||||
var series = cachedSeries.Data.First();
|
var firstEpisode = seriesData.Data.First();
|
||||||
historySeries.SeriesDescription = series.Description;
|
cachedSeries = new SeriesDataCache{
|
||||||
historySeries.ThumbnailImageUrl = GetSeriesThumbnail(cachedSeries);
|
SeriesDescription = firstEpisode.Description,
|
||||||
historySeries.SeriesTitle = series.Title;
|
SeriesId = seriesId,
|
||||||
historySeries.HistorySeriesAvailableDubLang = Languages.SortListByLangList(series.AudioLocales);
|
SeriesTitle = firstEpisode.Title,
|
||||||
historySeries.HistorySeriesAvailableSoftSubs = Languages.SortListByLangList(series.SubtitleLocales);
|
ThumbnailImageUrl = GetSeriesThumbnail(seriesData),
|
||||||
|
HistorySeriesAvailableDubLang = Languages.SortListByLangList(firstEpisode.AudioLocales),
|
||||||
|
HistorySeriesAvailableSoftSubs = Languages.SortListByLangList(firstEpisode.SubtitleLocales)
|
||||||
|
};
|
||||||
|
|
||||||
|
historySeries.SeriesDescription = cachedSeries.SeriesDescription;
|
||||||
|
historySeries.ThumbnailImageUrl = cachedSeries.ThumbnailImageUrl;
|
||||||
|
historySeries.SeriesTitle = cachedSeries.SeriesTitle;
|
||||||
|
historySeries.HistorySeriesAvailableDubLang = cachedSeries.HistorySeriesAvailableDubLang;
|
||||||
|
historySeries.HistorySeriesAvailableSoftSubs = cachedSeries.HistorySeriesAvailableSoftSubs;
|
||||||
|
}
|
||||||
|
} else if (historySeries.SeriesType == SeriesType.Artist){
|
||||||
|
var artisteData = await crunInstance.CrMusic.ParseArtistByIdAsync(seriesId, string.IsNullOrEmpty(crunInstance.CrunOptions.HistoryLang) ? crunInstance.DefaultLocale : crunInstance.CrunOptions.HistoryLang,
|
||||||
|
true);
|
||||||
|
if (!string.IsNullOrEmpty(artisteData.Id)){
|
||||||
|
cachedSeries = new SeriesDataCache{
|
||||||
|
SeriesDescription = artisteData.Description ?? "",
|
||||||
|
SeriesId = artisteData.Id,
|
||||||
|
SeriesTitle = artisteData.Name ?? "",
|
||||||
|
ThumbnailImageUrl = artisteData.Images.PosterTall.FirstOrDefault(e => e.Height == 360)?.Source ?? "",
|
||||||
|
HistorySeriesAvailableDubLang =[],
|
||||||
|
HistorySeriesAvailableSoftSubs =[]
|
||||||
|
};
|
||||||
|
|
||||||
|
historySeries.SeriesDescription = cachedSeries.SeriesDescription;
|
||||||
|
historySeries.ThumbnailImageUrl = cachedSeries.ThumbnailImageUrl;
|
||||||
|
historySeries.SeriesTitle = cachedSeries.SeriesTitle;
|
||||||
|
historySeries.HistorySeriesAvailableDubLang = cachedSeries.HistorySeriesAvailableDubLang;
|
||||||
|
historySeries.HistorySeriesAvailableSoftSubs = cachedSeries.HistorySeriesAvailableSoftSubs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else{
|
||||||
|
if (cachedSeries != null){
|
||||||
|
historySeries.SeriesDescription = cachedSeries.SeriesDescription;
|
||||||
|
historySeries.ThumbnailImageUrl = cachedSeries.ThumbnailImageUrl;
|
||||||
|
historySeries.SeriesTitle = cachedSeries.SeriesTitle;
|
||||||
|
historySeries.HistorySeriesAvailableDubLang = cachedSeries.HistorySeriesAvailableDubLang;
|
||||||
|
historySeries.HistorySeriesAvailableSoftSubs = cachedSeries.HistorySeriesAvailableSoftSubs;
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cachedSeries?.Data != null){
|
|
||||||
var series = cachedSeries.Data.First();
|
|
||||||
historySeries.SeriesDescription = series.Description;
|
|
||||||
historySeries.ThumbnailImageUrl = GetSeriesThumbnail(cachedSeries);
|
|
||||||
historySeries.SeriesTitle = series.Title;
|
|
||||||
historySeries.HistorySeriesAvailableDubLang = Languages.SortListByLangList(series.AudioLocales);
|
|
||||||
historySeries.HistorySeriesAvailableSoftSubs = Languages.SortListByLangList(series.SubtitleLocales);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -441,8 +460,8 @@ public class History(){
|
||||||
var sortedSeriesDates = sortingDir
|
var sortedSeriesDates = sortingDir
|
||||||
? CrunchyrollManager.Instance.HistoryList
|
? CrunchyrollManager.Instance.HistoryList
|
||||||
.OrderByDescending(s => {
|
.OrderByDescending(s => {
|
||||||
var date = ParseDate(s.SonarrNextAirDate, today);
|
var date = ParseDate(s.SonarrNextAirDate ?? string.Empty, today);
|
||||||
return date.HasValue ? date.Value : DateTime.MinValue;
|
return date ?? DateTime.MinValue;
|
||||||
})
|
})
|
||||||
.ThenByDescending(s => s.SonarrNextAirDate == "Today" ? 1 : 0)
|
.ThenByDescending(s => s.SonarrNextAirDate == "Today" ? 1 : 0)
|
||||||
.ThenBy(s => string.IsNullOrEmpty(s.SonarrNextAirDate) ? 1 : 0)
|
.ThenBy(s => string.IsNullOrEmpty(s.SonarrNextAirDate) ? 1 : 0)
|
||||||
|
|
@ -452,8 +471,8 @@ public class History(){
|
||||||
.OrderByDescending(s => s.SonarrNextAirDate == "Today")
|
.OrderByDescending(s => s.SonarrNextAirDate == "Today")
|
||||||
.ThenBy(s => s.SonarrNextAirDate == "Today" ? s.SeriesTitle : null)
|
.ThenBy(s => s.SonarrNextAirDate == "Today" ? s.SeriesTitle : null)
|
||||||
.ThenBy(s => {
|
.ThenBy(s => {
|
||||||
var date = ParseDate(s.SonarrNextAirDate, today);
|
var date = ParseDate(s.SonarrNextAirDate ?? string.Empty, today);
|
||||||
return date.HasValue ? date.Value : DateTime.MaxValue;
|
return date ?? DateTime.MaxValue;
|
||||||
})
|
})
|
||||||
.ThenBy(s => s.SeriesTitle)
|
.ThenBy(s => s.SeriesTitle)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
@ -499,55 +518,40 @@ public class History(){
|
||||||
private string GetSeriesThumbnail(CrSeriesBase series){
|
private string GetSeriesThumbnail(CrSeriesBase series){
|
||||||
// var series = await crunInstance.CrSeries.SeriesById(seriesId);
|
// var series = await crunInstance.CrSeries.SeriesById(seriesId);
|
||||||
|
|
||||||
if ((series.Data ?? Array.Empty<SeriesBaseItem>()).First().Images.PosterTall?.Count > 0){
|
if (series.Data != null && series.Data.First().Images.PosterTall?.Count > 0){
|
||||||
return series.Data.First().Images.PosterTall.First().First(e => e.Height == 360).Source;
|
var imagesPosterTall = series.Data.First().Images.PosterTall;
|
||||||
|
if (imagesPosterTall != null) return imagesPosterTall.First().First(e => e.Height == 360).Source;
|
||||||
}
|
}
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CheckStringForSpecial(string identifier){
|
|
||||||
if (string.IsNullOrEmpty(identifier)){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Regex pattern to match any sequence that does NOT contain "|S" followed by one or more digits immediately after
|
private HistorySeason NewHistorySeason(List<IHistorySource> episodeList, IHistorySource firstEpisode){
|
||||||
string pattern = @"^(?!.*\|S\d+).*";
|
|
||||||
|
|
||||||
// Use Regex.IsMatch to check if the identifier matches the pattern
|
|
||||||
return Regex.IsMatch(identifier, pattern);
|
|
||||||
}
|
|
||||||
|
|
||||||
private HistorySeason NewHistorySeason(List<CrunchyEpisode> seasonData, CrunchyEpisode firstEpisode){
|
|
||||||
var newSeason = new HistorySeason{
|
var newSeason = new HistorySeason{
|
||||||
SeasonTitle = firstEpisode.SeasonTitle,
|
SeasonTitle = firstEpisode.GetSeasonTitle(),
|
||||||
SeasonId = firstEpisode.SeasonId,
|
SeasonId = firstEpisode.GetSeasonId(),
|
||||||
SeasonNum = Helpers.ExtractNumberAfterS(firstEpisode.Identifier) ?? firstEpisode.SeasonNumber + "",
|
SeasonNum = firstEpisode.GetSeasonNum(),
|
||||||
EpisodesList =[],
|
EpisodesList =[],
|
||||||
SpecialSeason = CheckStringForSpecial(firstEpisode.Identifier)
|
SpecialSeason = firstEpisode.IsSpecialSeason()
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var crunchyEpisode in seasonData){
|
foreach (var historySource in episodeList){
|
||||||
var langList = new List<string>();
|
if (historySource.GetSeasonId() != newSeason.SeasonId){
|
||||||
|
continue;
|
||||||
if (crunchyEpisode.Versions != null){
|
|
||||||
langList.AddRange(crunchyEpisode.Versions.Select(version => version.AudioLocale));
|
|
||||||
} else{
|
|
||||||
langList.Add(crunchyEpisode.AudioLocale);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Languages.SortListByLangList(langList);
|
|
||||||
|
|
||||||
var newHistoryEpisode = new HistoryEpisode{
|
var newHistoryEpisode = new HistoryEpisode{
|
||||||
EpisodeTitle = GetEpisodeTitle(crunchyEpisode),
|
EpisodeTitle = historySource.GetEpisodeTitle(),
|
||||||
EpisodeDescription = crunchyEpisode.Description,
|
EpisodeDescription = historySource.GetEpisodeDescription(),
|
||||||
EpisodeId = crunchyEpisode.Id,
|
EpisodeId = historySource.GetEpisodeId(),
|
||||||
Episode = crunchyEpisode.Episode,
|
Episode = historySource.GetEpisodeNumber(),
|
||||||
EpisodeSeasonNum = Helpers.ExtractNumberAfterS(firstEpisode.Identifier) ?? firstEpisode.SeasonNumber + "",
|
EpisodeSeasonNum = historySource.GetSeasonNum(),
|
||||||
SpecialEpisode = !int.TryParse(crunchyEpisode.Episode, out _),
|
SpecialEpisode = historySource.IsSpecialEpisode(),
|
||||||
HistoryEpisodeAvailableDubLang = langList,
|
HistoryEpisodeAvailableDubLang = historySource.GetEpisodeAvailableDubLang(),
|
||||||
HistoryEpisodeAvailableSoftSubs = crunchyEpisode.SubtitleLocales,
|
HistoryEpisodeAvailableSoftSubs = historySource.GetEpisodeAvailableSoftSubs(),
|
||||||
EpisodeCrPremiumAirDate = crunchyEpisode.PremiumAvailableDate
|
EpisodeCrPremiumAirDate = historySource.GetAvailableDate(),
|
||||||
|
EpisodeType = historySource.GetEpisodeType()
|
||||||
};
|
};
|
||||||
|
|
||||||
newSeason.EpisodesList.Add(newHistoryEpisode);
|
newSeason.EpisodesList.Add(newHistoryEpisode);
|
||||||
|
|
@ -563,7 +567,7 @@ public class History(){
|
||||||
|
|
||||||
foreach (var historySeries in crunInstance.HistoryList){
|
foreach (var historySeries in crunInstance.HistoryList){
|
||||||
if (updateAll || string.IsNullOrEmpty(historySeries.SonarrSeriesId)){
|
if (updateAll || string.IsNullOrEmpty(historySeries.SonarrSeriesId)){
|
||||||
var sonarrSeries = FindClosestMatch(historySeries.SeriesTitle);
|
var sonarrSeries = FindClosestMatch(historySeries.SeriesTitle ?? string.Empty);
|
||||||
if (sonarrSeries != null){
|
if (sonarrSeries != null){
|
||||||
historySeries.SonarrSeriesId = sonarrSeries.Id + "";
|
historySeries.SonarrSeriesId = sonarrSeries.Id + "";
|
||||||
historySeries.SonarrTvDbId = sonarrSeries.TvdbId + "";
|
historySeries.SonarrTvDbId = sonarrSeries.TvdbId + "";
|
||||||
|
|
@ -581,7 +585,7 @@ public class History(){
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(historySeries.SonarrSeriesId)){
|
if (!string.IsNullOrEmpty(historySeries.SonarrSeriesId)){
|
||||||
List<SonarrEpisode>? episodes = await SonarrClient.Instance.GetEpisodes(int.Parse(historySeries.SonarrSeriesId));
|
List<SonarrEpisode> episodes = await SonarrClient.Instance.GetEpisodes(int.Parse(historySeries.SonarrSeriesId));
|
||||||
|
|
||||||
historySeries.SonarrNextAirDate = GetNextAirDate(episodes);
|
historySeries.SonarrNextAirDate = GetNextAirDate(episodes);
|
||||||
|
|
||||||
|
|
@ -603,8 +607,10 @@ public class History(){
|
||||||
historyEpisode.SonarrEpisodeId = episode.Id + "";
|
historyEpisode.SonarrEpisodeId = episode.Id + "";
|
||||||
historyEpisode.SonarrEpisodeNumber = episode.EpisodeNumber + "";
|
historyEpisode.SonarrEpisodeNumber = episode.EpisodeNumber + "";
|
||||||
historyEpisode.SonarrHasFile = episode.HasFile;
|
historyEpisode.SonarrHasFile = episode.HasFile;
|
||||||
|
historyEpisode.SonarrIsMonitored = episode.Monitored;
|
||||||
historyEpisode.SonarrAbsolutNumber = episode.AbsoluteEpisodeNumber + "";
|
historyEpisode.SonarrAbsolutNumber = episode.AbsoluteEpisodeNumber + "";
|
||||||
historyEpisode.SonarrSeasonNumber = episode.SeasonNumber + "";
|
historyEpisode.SonarrSeasonNumber = episode.SeasonNumber + "";
|
||||||
|
|
||||||
lock (_lock){
|
lock (_lock){
|
||||||
episodes.Remove(episode);
|
episodes.Remove(episode);
|
||||||
}
|
}
|
||||||
|
|
@ -622,8 +628,8 @@ public class History(){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var episodeNumberStr = ele.EpisodeNumber.ToString() ?? string.Empty;
|
var episodeNumberStr = ele.EpisodeNumber.ToString();
|
||||||
var seasonNumberStr = ele.SeasonNumber.ToString() ?? string.Empty;
|
var seasonNumberStr = ele.SeasonNumber.ToString();
|
||||||
|
|
||||||
return episodeNumberStr == historyEpisode.Episode && seasonNumberStr == historyEpisode.EpisodeSeasonNum;
|
return episodeNumberStr == historyEpisode.Episode && seasonNumberStr == historyEpisode.EpisodeSeasonNum;
|
||||||
});
|
});
|
||||||
|
|
@ -631,6 +637,7 @@ public class History(){
|
||||||
historyEpisode.SonarrEpisodeId = episode.Id + "";
|
historyEpisode.SonarrEpisodeId = episode.Id + "";
|
||||||
historyEpisode.SonarrEpisodeNumber = episode.EpisodeNumber + "";
|
historyEpisode.SonarrEpisodeNumber = episode.EpisodeNumber + "";
|
||||||
historyEpisode.SonarrHasFile = episode.HasFile;
|
historyEpisode.SonarrHasFile = episode.HasFile;
|
||||||
|
historyEpisode.SonarrIsMonitored = episode.Monitored;
|
||||||
historyEpisode.SonarrAbsolutNumber = episode.AbsoluteEpisodeNumber + "";
|
historyEpisode.SonarrAbsolutNumber = episode.AbsoluteEpisodeNumber + "";
|
||||||
historyEpisode.SonarrSeasonNumber = episode.SeasonNumber + "";
|
historyEpisode.SonarrSeasonNumber = episode.SeasonNumber + "";
|
||||||
lock (_lock){
|
lock (_lock){
|
||||||
|
|
@ -649,6 +656,7 @@ public class History(){
|
||||||
historyEpisode.SonarrEpisodeId = episode1.Id + "";
|
historyEpisode.SonarrEpisodeId = episode1.Id + "";
|
||||||
historyEpisode.SonarrEpisodeNumber = episode1.EpisodeNumber + "";
|
historyEpisode.SonarrEpisodeNumber = episode1.EpisodeNumber + "";
|
||||||
historyEpisode.SonarrHasFile = episode1.HasFile;
|
historyEpisode.SonarrHasFile = episode1.HasFile;
|
||||||
|
historyEpisode.SonarrIsMonitored = episode1.Monitored;
|
||||||
historyEpisode.SonarrAbsolutNumber = episode1.AbsoluteEpisodeNumber + "";
|
historyEpisode.SonarrAbsolutNumber = episode1.AbsoluteEpisodeNumber + "";
|
||||||
historyEpisode.SonarrSeasonNumber = episode1.SeasonNumber + "";
|
historyEpisode.SonarrSeasonNumber = episode1.SeasonNumber + "";
|
||||||
lock (_lock){
|
lock (_lock){
|
||||||
|
|
@ -666,6 +674,7 @@ public class History(){
|
||||||
historyEpisode.SonarrEpisodeId = episode2.Id + "";
|
historyEpisode.SonarrEpisodeId = episode2.Id + "";
|
||||||
historyEpisode.SonarrEpisodeNumber = episode2.EpisodeNumber + "";
|
historyEpisode.SonarrEpisodeNumber = episode2.EpisodeNumber + "";
|
||||||
historyEpisode.SonarrHasFile = episode2.HasFile;
|
historyEpisode.SonarrHasFile = episode2.HasFile;
|
||||||
|
historyEpisode.SonarrIsMonitored = episode2.Monitored;
|
||||||
historyEpisode.SonarrAbsolutNumber = episode2.AbsoluteEpisodeNumber + "";
|
historyEpisode.SonarrAbsolutNumber = episode2.AbsoluteEpisodeNumber + "";
|
||||||
historyEpisode.SonarrSeasonNumber = episode2.SeasonNumber + "";
|
historyEpisode.SonarrSeasonNumber = episode2.SeasonNumber + "";
|
||||||
lock (_lock){
|
lock (_lock){
|
||||||
|
|
@ -677,8 +686,6 @@ public class History(){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -706,6 +713,10 @@ public class History(){
|
||||||
}
|
}
|
||||||
|
|
||||||
private SonarrSeries? FindClosestMatch(string title){
|
private SonarrSeries? FindClosestMatch(string title){
|
||||||
|
if (string.IsNullOrEmpty(title)){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
SonarrSeries? closestMatch = null;
|
SonarrSeries? closestMatch = null;
|
||||||
double highestSimilarity = 0.0;
|
double highestSimilarity = 0.0;
|
||||||
|
|
||||||
|
|
@ -748,7 +759,7 @@ public class History(){
|
||||||
|
|
||||||
Parallel.ForEach(episodeList, episode => {
|
Parallel.ForEach(episodeList, episode => {
|
||||||
if (episode != null){
|
if (episode != null){
|
||||||
double similarity = CalculateSimilarity(episode.Title, title);
|
double similarity = CalculateSimilarity(episode.Title ?? string.Empty, title);
|
||||||
lock (lockObject) // Ensure thread-safe access to shared variables
|
lock (lockObject) // Ensure thread-safe access to shared variables
|
||||||
{
|
{
|
||||||
if (similarity > highestSimilarity){
|
if (similarity > highestSimilarity){
|
||||||
|
|
@ -810,13 +821,13 @@ public class History(){
|
||||||
}
|
}
|
||||||
|
|
||||||
public class NumericStringPropertyComparer : IComparer<HistoryEpisode>{
|
public class NumericStringPropertyComparer : IComparer<HistoryEpisode>{
|
||||||
public int Compare(HistoryEpisode x, HistoryEpisode y){
|
public int Compare(HistoryEpisode? x, HistoryEpisode? y){
|
||||||
if (double.TryParse(x.Episode, NumberStyles.Any, CultureInfo.InvariantCulture, out double xDouble) &&
|
if (double.TryParse(x?.Episode, NumberStyles.Any, CultureInfo.InvariantCulture, out double xDouble) &&
|
||||||
double.TryParse(y.Episode, NumberStyles.Any, CultureInfo.InvariantCulture, out double yDouble)){
|
double.TryParse(y?.Episode, NumberStyles.Any, CultureInfo.InvariantCulture, out double yDouble)){
|
||||||
return xDouble.CompareTo(yDouble);
|
return xDouble.CompareTo(yDouble);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fall back to string comparison if not parseable as doubles
|
// Fall back to string comparison if not parseable as doubles
|
||||||
return string.Compare(x.Episode, y.Episode, StringComparison.Ordinal);
|
return string.Compare(x?.Episode, y?.Episode, StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -13,7 +12,6 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CRD.Downloader.Crunchyroll;
|
using CRD.Downloader.Crunchyroll;
|
||||||
using CRD.Utils;
|
using CRD.Utils;
|
||||||
using CRD.Utils.Structs;
|
using CRD.Utils.Structs;
|
||||||
using CRD.Utils.Structs.History;
|
|
||||||
using CRD.Utils.Updater;
|
using CRD.Utils.Updater;
|
||||||
using FluentAvalonia.Styling;
|
using FluentAvalonia.Styling;
|
||||||
|
|
||||||
|
|
@ -51,10 +49,10 @@ public partial class ProgramManager : ObservableObject{
|
||||||
private bool _updateAvailable = true;
|
private bool _updateAvailable = true;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _finishedLoading = false;
|
private bool _finishedLoading;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _navigationLock = false;
|
private bool _navigationLock;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
@ -66,7 +64,7 @@ public partial class ProgramManager : ObservableObject{
|
||||||
|
|
||||||
private Queue<Func<Task>> taskQueue = new Queue<Func<Task>>();
|
private Queue<Func<Task>> taskQueue = new Queue<Func<Task>>();
|
||||||
|
|
||||||
private bool exitOnTaskFinish = false;
|
private bool exitOnTaskFinish;
|
||||||
|
|
||||||
public IStorageProvider StorageProvider;
|
public IStorageProvider StorageProvider;
|
||||||
|
|
||||||
|
|
@ -111,7 +109,7 @@ public partial class ProgramManager : ObservableObject{
|
||||||
await Task.WhenAll(tasks);
|
await Task.WhenAll(tasks);
|
||||||
|
|
||||||
|
|
||||||
while (QueueManager.Instance.Queue.Any(e => e.DownloadProgress != null && e.DownloadProgress.Done != true)){
|
while (QueueManager.Instance.Queue.Any(e => e.DownloadProgress.Done != true)){
|
||||||
Console.WriteLine("Waiting for downloads to complete...");
|
Console.WriteLine("Waiting for downloads to complete...");
|
||||||
await Task.Delay(2000); // Wait for 2 second before checking again
|
await Task.Delay(2000); // Wait for 2 second before checking again
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ public class QueueManager{
|
||||||
if (e.Action == NotifyCollectionChangedAction.Remove){
|
if (e.Action == NotifyCollectionChangedAction.Remove){
|
||||||
if (e.OldItems != null)
|
if (e.OldItems != null)
|
||||||
foreach (var eOldItem in e.OldItems){
|
foreach (var eOldItem in e.OldItems){
|
||||||
var downloadItem = DownloadItemModels.FirstOrDefault(e => e.epMeta.Equals(eOldItem));
|
var downloadItem = DownloadItemModels.FirstOrDefault(downloadItem => downloadItem.epMeta.Equals(eOldItem));
|
||||||
if (downloadItem != null){
|
if (downloadItem != null){
|
||||||
DownloadItemModels.Remove(downloadItem);
|
DownloadItemModels.Remove(downloadItem);
|
||||||
} else{
|
} else{
|
||||||
|
|
@ -87,13 +87,17 @@ public class QueueManager{
|
||||||
|
|
||||||
|
|
||||||
public async Task CrAddEpisodeToQueue(string epId, string crLocale, List<string> dubLang, bool updateHistory = false, bool onlySubs = false){
|
public async Task CrAddEpisodeToQueue(string epId, string crLocale, List<string> dubLang, bool updateHistory = false, bool onlySubs = false){
|
||||||
|
if (string.IsNullOrEmpty(epId)){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await CrunchyrollManager.Instance.CrAuth.RefreshToken(true);
|
await CrunchyrollManager.Instance.CrAuth.RefreshToken(true);
|
||||||
|
|
||||||
var episodeL = await CrunchyrollManager.Instance.CrEpisode.ParseEpisodeById(epId, crLocale);
|
var episodeL = await CrunchyrollManager.Instance.CrEpisode.ParseEpisodeById(epId, crLocale);
|
||||||
|
|
||||||
|
|
||||||
if (episodeL != null){
|
if (episodeL != null){
|
||||||
if (episodeL.Value.IsPremiumOnly && !CrunchyrollManager.Instance.Profile.HasPremium){
|
if (episodeL.IsPremiumOnly && !CrunchyrollManager.Instance.Profile.HasPremium){
|
||||||
MessageBus.Current.SendMessage(new ToastMessage($"Episode is a premium episode – make sure that you are signed in with an account that has an active premium subscription", ToastType.Error, 3));
|
MessageBus.Current.SendMessage(new ToastMessage($"Episode is a premium episode – make sure that you are signed in with an account that has an active premium subscription", ToastType.Error, 3));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -206,6 +210,15 @@ public class QueueManager{
|
||||||
|
|
||||||
if (musicVideo != null){
|
if (musicVideo != null){
|
||||||
var musicVideoMeta = CrunchyrollManager.Instance.CrMusic.EpisodeMeta(musicVideo);
|
var musicVideoMeta = CrunchyrollManager.Instance.CrMusic.EpisodeMeta(musicVideo);
|
||||||
|
|
||||||
|
(HistoryEpisode? historyEpisode, List<string> dublist, List<string> sublist, string downloadDirPath, string videoQuality) historyEpisode = (null, [], [], "", "");
|
||||||
|
|
||||||
|
if (CrunchyrollManager.Instance.CrunOptions.History){
|
||||||
|
historyEpisode = CrunchyrollManager.Instance.History.GetHistoryEpisodeWithDubListAndDownloadDir(musicVideoMeta.SeriesId, musicVideoMeta.SeasonId, musicVideoMeta.Data.First().MediaId);
|
||||||
|
}
|
||||||
|
|
||||||
|
musicVideoMeta.VideoQuality = !string.IsNullOrEmpty(historyEpisode.videoQuality) ? historyEpisode.videoQuality : CrunchyrollManager.Instance.CrunOptions.QualityVideo;
|
||||||
|
|
||||||
Queue.Add(musicVideoMeta);
|
Queue.Add(musicVideoMeta);
|
||||||
MessageBus.Current.SendMessage(new ToastMessage($"Added music video to the queue", ToastType.Information, 1));
|
MessageBus.Current.SendMessage(new ToastMessage($"Added music video to the queue", ToastType.Information, 1));
|
||||||
}
|
}
|
||||||
|
|
@ -218,6 +231,14 @@ public class QueueManager{
|
||||||
|
|
||||||
if (concert != null){
|
if (concert != null){
|
||||||
var concertMeta = CrunchyrollManager.Instance.CrMusic.EpisodeMeta(concert);
|
var concertMeta = CrunchyrollManager.Instance.CrMusic.EpisodeMeta(concert);
|
||||||
|
|
||||||
|
(HistoryEpisode? historyEpisode, List<string> dublist, List<string> sublist, string downloadDirPath, string videoQuality) historyEpisode = (null, [], [], "", "");
|
||||||
|
|
||||||
|
if (CrunchyrollManager.Instance.CrunOptions.History){
|
||||||
|
historyEpisode = CrunchyrollManager.Instance.History.GetHistoryEpisodeWithDubListAndDownloadDir(concertMeta.SeriesId, concertMeta.SeasonId, concertMeta.Data.First().MediaId);
|
||||||
|
}
|
||||||
|
|
||||||
|
concertMeta.VideoQuality = !string.IsNullOrEmpty(historyEpisode.videoQuality) ? historyEpisode.videoQuality : CrunchyrollManager.Instance.CrunOptions.QualityVideo;
|
||||||
Queue.Add(concertMeta);
|
Queue.Add(concertMeta);
|
||||||
MessageBus.Current.SendMessage(new ToastMessage($"Added concert to the queue", ToastType.Information, 1));
|
MessageBus.Current.SendMessage(new ToastMessage($"Added concert to the queue", ToastType.Information, 1));
|
||||||
}
|
}
|
||||||
|
|
@ -232,7 +253,7 @@ public class QueueManager{
|
||||||
foreach (var crunchyEpMeta in selected.Values.ToList()){
|
foreach (var crunchyEpMeta in selected.Values.ToList()){
|
||||||
if (crunchyEpMeta.Data?.First() != null){
|
if (crunchyEpMeta.Data?.First() != null){
|
||||||
if (CrunchyrollManager.Instance.CrunOptions.History){
|
if (CrunchyrollManager.Instance.CrunOptions.History){
|
||||||
var historyEpisode = CrunchyrollManager.Instance.History.GetHistoryEpisodeWithDownloadDir(crunchyEpMeta.ShowId, crunchyEpMeta.SeasonId, crunchyEpMeta.Data.First().MediaId);
|
var historyEpisode = CrunchyrollManager.Instance.History.GetHistoryEpisodeWithDownloadDir(crunchyEpMeta.SeriesId, crunchyEpMeta.SeasonId, crunchyEpMeta.Data.First().MediaId);
|
||||||
if (CrunchyrollManager.Instance.CrunOptions.SonarrProperties is{ SonarrEnabled: true, UseSonarrNumbering: true }){
|
if (CrunchyrollManager.Instance.CrunOptions.SonarrProperties is{ SonarrEnabled: true, UseSonarrNumbering: true }){
|
||||||
if (historyEpisode.historyEpisode != null){
|
if (historyEpisode.historyEpisode != null){
|
||||||
if (!string.IsNullOrEmpty(historyEpisode.historyEpisode.SonarrEpisodeNumber)){
|
if (!string.IsNullOrEmpty(historyEpisode.historyEpisode.SonarrEpisodeNumber)){
|
||||||
|
|
@ -258,7 +279,7 @@ public class QueueManager{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var subLangList = CrunchyrollManager.Instance.History.GetSubList(crunchyEpMeta.ShowId, crunchyEpMeta.SeasonId);
|
var subLangList = CrunchyrollManager.Instance.History.GetSubList(crunchyEpMeta.SeriesId, crunchyEpMeta.SeasonId);
|
||||||
|
|
||||||
crunchyEpMeta.VideoQuality = !string.IsNullOrEmpty(subLangList.videoQuality) ? subLangList.videoQuality : CrunchyrollManager.Instance.CrunOptions.QualityVideo;
|
crunchyEpMeta.VideoQuality = !string.IsNullOrEmpty(subLangList.videoQuality) ? subLangList.videoQuality : CrunchyrollManager.Instance.CrunOptions.QualityVideo;
|
||||||
crunchyEpMeta.DownloadSubs = subLangList.sublist.Count > 0 ? subLangList.sublist : CrunchyrollManager.Instance.CrunOptions.DlSubs;
|
crunchyEpMeta.DownloadSubs = subLangList.sublist.Count > 0 ? subLangList.sublist : CrunchyrollManager.Instance.CrunOptions.DlSubs;
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ using ProtoBuf;
|
||||||
|
|
||||||
namespace CRD.Utils.DRM;
|
namespace CRD.Utils.DRM;
|
||||||
|
|
||||||
public struct ContentDecryptionModule{
|
public class ContentDecryptionModule{
|
||||||
public byte[] privateKey{ get; set; }
|
public byte[] privateKey{ get; set; }
|
||||||
public byte[] identifierBlob{ get; set; }
|
public byte[] identifierBlob{ get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.IO;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using CRD.Utils.Files;
|
||||||
|
|
||||||
namespace CRD.Utils.DRM;
|
namespace CRD.Utils.DRM;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,24 @@ using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace CRD.Utils;
|
namespace CRD.Utils;
|
||||||
|
|
||||||
|
public enum StreamingService{
|
||||||
|
Crunchyroll,
|
||||||
|
Unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum EpisodeType{
|
||||||
|
MusicVideo,
|
||||||
|
Concert,
|
||||||
|
Episode,
|
||||||
|
Unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum SeriesType{
|
||||||
|
Artist,
|
||||||
|
Series,
|
||||||
|
Unknown
|
||||||
|
}
|
||||||
|
|
||||||
[DataContract]
|
[DataContract]
|
||||||
[JsonConverter(typeof(LocaleConverter))]
|
[JsonConverter(typeof(LocaleConverter))]
|
||||||
public enum Locale{
|
public enum Locale{
|
||||||
|
|
|
||||||
|
|
@ -4,21 +4,25 @@ using System.IO;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using CRD.Downloader;
|
||||||
using CRD.Downloader.Crunchyroll;
|
using CRD.Downloader.Crunchyroll;
|
||||||
using CRD.Utils.Structs;
|
|
||||||
using CRD.Utils.Structs.Crunchyroll;
|
using CRD.Utils.Structs.Crunchyroll;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using YamlDotNet.RepresentationModel;
|
using YamlDotNet.RepresentationModel;
|
||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
using YamlDotNet.Serialization.NamingConventions;
|
using YamlDotNet.Serialization.NamingConventions;
|
||||||
|
|
||||||
namespace CRD.Utils;
|
namespace CRD.Utils.Files;
|
||||||
|
|
||||||
public class CfgManager{
|
public class CfgManager{
|
||||||
private static string WorkingDirectory = AppContext.BaseDirectory;
|
private static string WorkingDirectory = AppContext.BaseDirectory;
|
||||||
|
|
||||||
public static readonly string PathCrToken = Path.Combine(WorkingDirectory, "config", "cr_token.yml");
|
public static readonly string PathCrTokenOld = Path.Combine(WorkingDirectory, "config", "cr_token.yml");
|
||||||
public static readonly string PathCrDownloadOptions = Path.Combine(WorkingDirectory, "config", "settings.yml");
|
public static readonly string PathCrDownloadOptionsOld = Path.Combine(WorkingDirectory, "config", "settings.yml");
|
||||||
|
|
||||||
|
public static readonly string PathCrToken = Path.Combine(WorkingDirectory, "config", "cr_token.json");
|
||||||
|
public static readonly string PathCrDownloadOptions = Path.Combine(WorkingDirectory, "config", "settings.json");
|
||||||
|
|
||||||
public static readonly string PathCrHistory = Path.Combine(WorkingDirectory, "config", "history.json");
|
public static readonly string PathCrHistory = Path.Combine(WorkingDirectory, "config", "history.json");
|
||||||
public static readonly string PathWindowSettings = Path.Combine(WorkingDirectory, "config", "windowSettings.json");
|
public static readonly string PathWindowSettings = Path.Combine(WorkingDirectory, "config", "windowSettings.json");
|
||||||
|
|
||||||
|
|
@ -83,18 +87,36 @@ public class CfgManager{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void WriteJsonResponseToYamlFile(string jsonResponse, string filePath){
|
public static void WriteCrSettings(){
|
||||||
// Convert JSON to an object
|
WriteJsonToFile(PathCrDownloadOptions, CrunchyrollManager.Instance.CrunOptions);
|
||||||
var deserializer = new DeserializerBuilder()
|
}
|
||||||
.WithNamingConvention(UnderscoredNamingConvention.Instance) // Adjust this as needed
|
|
||||||
.Build();
|
|
||||||
var jsonObject = deserializer.Deserialize<object>(jsonResponse);
|
|
||||||
|
|
||||||
// Convert the object to YAML
|
// public static void WriteTokenToYamlFile(CrToken token, string filePath){
|
||||||
var serializer = new SerializerBuilder()
|
// // Convert the object to YAML
|
||||||
.WithNamingConvention(UnderscoredNamingConvention.Instance) // Ensure consistent naming convention
|
// var serializer = new SerializerBuilder()
|
||||||
.Build();
|
// .WithNamingConvention(UnderscoredNamingConvention.Instance) // Ensure consistent naming convention
|
||||||
var yaml = serializer.Serialize(jsonObject);
|
// .Build();
|
||||||
|
// var yaml = serializer.Serialize(token);
|
||||||
|
//
|
||||||
|
// string dirPath = Path.GetDirectoryName(filePath) ?? string.Empty;
|
||||||
|
//
|
||||||
|
// if (!Directory.Exists(dirPath)){
|
||||||
|
// Directory.CreateDirectory(dirPath);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (!File.Exists(filePath)){
|
||||||
|
// using (var fileStream = File.Create(filePath)){
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Write the YAML to a file
|
||||||
|
// File.WriteAllText(filePath, yaml);
|
||||||
|
// }
|
||||||
|
|
||||||
|
public static void UpdateSettingsFromFile<T>(T options, string filePath) where T : class{
|
||||||
|
if (options == null){
|
||||||
|
throw new ArgumentNullException(nameof(options));
|
||||||
|
}
|
||||||
|
|
||||||
string dirPath = Path.GetDirectoryName(filePath) ?? string.Empty;
|
string dirPath = Path.GetDirectoryName(filePath) ?? string.Empty;
|
||||||
|
|
||||||
|
|
@ -103,74 +125,81 @@ public class CfgManager{
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!File.Exists(filePath)){
|
if (!File.Exists(filePath)){
|
||||||
|
// Create the file if it doesn't exist
|
||||||
using (var fileStream = File.Create(filePath)){
|
using (var fileStream = File.Create(filePath)){
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Write the YAML to a file
|
|
||||||
File.WriteAllText(filePath, yaml);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void WriteTokenToYamlFile(CrToken token, string filePath){
|
|
||||||
// Convert the object to YAML
|
|
||||||
var serializer = new SerializerBuilder()
|
|
||||||
.WithNamingConvention(UnderscoredNamingConvention.Instance) // Ensure consistent naming convention
|
|
||||||
.Build();
|
|
||||||
var yaml = serializer.Serialize(token);
|
|
||||||
|
|
||||||
string dirPath = Path.GetDirectoryName(filePath) ?? string.Empty;
|
|
||||||
|
|
||||||
if (!Directory.Exists(dirPath)){
|
|
||||||
Directory.CreateDirectory(dirPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!File.Exists(filePath)){
|
|
||||||
using (var fileStream = File.Create(filePath)){
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the YAML to a file
|
|
||||||
File.WriteAllText(filePath, yaml);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void WriteSettingsToFile(){
|
|
||||||
var serializer = new SerializerBuilder()
|
|
||||||
.WithNamingConvention(UnderscoredNamingConvention.Instance) // Use the underscore style
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
string dirPath = Path.GetDirectoryName(PathCrDownloadOptions) ?? string.Empty;
|
|
||||||
|
|
||||||
if (!Directory.Exists(dirPath)){
|
|
||||||
Directory.CreateDirectory(dirPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!File.Exists(PathCrDownloadOptions)){
|
|
||||||
using (var fileStream = File.Create(PathCrDownloadOptions)){
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var yaml = serializer.Serialize(CrunchyrollManager.Instance.CrunOptions);
|
|
||||||
|
|
||||||
// Write to file
|
|
||||||
File.WriteAllText(PathCrDownloadOptions, yaml);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static void UpdateSettingsFromFile(CrDownloadOptions options){
|
|
||||||
string dirPath = Path.GetDirectoryName(PathCrDownloadOptions) ?? string.Empty;
|
|
||||||
|
|
||||||
if (!Directory.Exists(dirPath)){
|
|
||||||
Directory.CreateDirectory(dirPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!File.Exists(PathCrDownloadOptions)){
|
|
||||||
using (var fileStream = File.Create(PathCrDownloadOptions)){
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var input = File.ReadAllText(PathCrDownloadOptions);
|
var input = File.ReadAllText(filePath);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(input)){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deserialize JSON into a dictionary to get top-level properties
|
||||||
|
var propertiesPresentInJson = GetTopLevelPropertiesInJson(input);
|
||||||
|
|
||||||
|
// Deserialize JSON into the provided options object type
|
||||||
|
var loadedOptions = JsonConvert.DeserializeObject<T>(input);
|
||||||
|
|
||||||
|
if (loadedOptions == null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (PropertyInfo property in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)){
|
||||||
|
// Use the JSON property name if present, otherwise use the property name
|
||||||
|
string jsonPropertyName = property.Name;
|
||||||
|
var jsonPropertyAttribute = property.GetCustomAttribute<JsonPropertyAttribute>();
|
||||||
|
if (jsonPropertyAttribute != null){
|
||||||
|
jsonPropertyName = jsonPropertyAttribute.PropertyName ?? property.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (propertiesPresentInJson.Contains(jsonPropertyName)){
|
||||||
|
// Update the target property
|
||||||
|
var value = property.GetValue(loadedOptions);
|
||||||
|
var targetProperty = options.GetType().GetProperty(property.Name);
|
||||||
|
|
||||||
|
if (targetProperty != null && targetProperty.CanWrite){
|
||||||
|
targetProperty.SetValue(options, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HashSet<string> GetTopLevelPropertiesInJson(string jsonContent){
|
||||||
|
var properties = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
using (var reader = new JsonTextReader(new StringReader(jsonContent))){
|
||||||
|
while (reader.Read()){
|
||||||
|
if (reader.TokenType == JsonToken.PropertyName){
|
||||||
|
properties.Add(reader.Value?.ToString() ?? string.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region YAML OLD
|
||||||
|
|
||||||
|
public static void UpdateSettingsFromFileYAML(CrDownloadOptionsYaml options){
|
||||||
|
string dirPath = Path.GetDirectoryName(PathCrDownloadOptionsOld) ?? string.Empty;
|
||||||
|
|
||||||
|
if (!Directory.Exists(dirPath)){
|
||||||
|
Directory.CreateDirectory(dirPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!File.Exists(PathCrDownloadOptionsOld)){
|
||||||
|
using (var fileStream = File.Create(PathCrDownloadOptionsOld)){
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var input = File.ReadAllText(PathCrDownloadOptionsOld);
|
||||||
|
|
||||||
if (input.Length <= 0){
|
if (input.Length <= 0){
|
||||||
return;
|
return;
|
||||||
|
|
@ -180,17 +209,18 @@ public class CfgManager{
|
||||||
.WithNamingConvention(UnderscoredNamingConvention.Instance)
|
.WithNamingConvention(UnderscoredNamingConvention.Instance)
|
||||||
.IgnoreUnmatchedProperties()
|
.IgnoreUnmatchedProperties()
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
var propertiesPresentInYaml = GetTopLevelPropertiesInYaml(input);
|
var propertiesPresentInYaml = GetTopLevelPropertiesInYaml(input);
|
||||||
var loadedOptions = deserializer.Deserialize<CrDownloadOptions>(new StringReader(input));
|
var loadedOptions = deserializer.Deserialize<CrDownloadOptionsYaml>(new StringReader(input));
|
||||||
var instanceOptions = options;
|
var instanceOptions = options;
|
||||||
|
|
||||||
foreach (PropertyInfo property in typeof(CrDownloadOptions).GetProperties()){
|
foreach (PropertyInfo property in typeof(CrDownloadOptionsYaml).GetProperties()){
|
||||||
var yamlMemberAttribute = property.GetCustomAttribute<YamlMemberAttribute>();
|
var yamlMemberAttribute = property.GetCustomAttribute<YamlMemberAttribute>();
|
||||||
string yamlPropertyName = yamlMemberAttribute?.Alias ?? property.Name;
|
// var jsonMemberAttribute = property.GetCustomAttribute<JsonPropertyAttribute>();
|
||||||
|
string yamlPropertyName = yamlMemberAttribute?.Alias ?? property.Name;
|
||||||
|
|
||||||
if (propertiesPresentInYaml.Contains(yamlPropertyName)){
|
if (propertiesPresentInYaml.Contains(yamlPropertyName)){
|
||||||
PropertyInfo instanceProperty = instanceOptions.GetType().GetProperty(property.Name);
|
PropertyInfo? instanceProperty = instanceOptions.GetType().GetProperty(property.Name);
|
||||||
if (instanceProperty != null && instanceProperty.CanWrite){
|
if (instanceProperty != null && instanceProperty.CanWrite){
|
||||||
instanceProperty.SetValue(instanceOptions, property.GetValue(loadedOptions));
|
instanceProperty.SetValue(instanceOptions, property.GetValue(loadedOptions));
|
||||||
}
|
}
|
||||||
|
|
@ -216,6 +246,9 @@ public class CfgManager{
|
||||||
return properties;
|
return properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
public static void UpdateHistoryFile(){
|
public static void UpdateHistoryFile(){
|
||||||
if (!CrunchyrollManager.Instance.CrunOptions.History){
|
if (!CrunchyrollManager.Instance.CrunOptions.History){
|
||||||
return;
|
return;
|
||||||
|
|
@ -308,12 +341,32 @@ public class CfgManager{
|
||||||
return Directory.Exists(dirPath) && File.Exists(filePath);
|
return Directory.Exists(dirPath) && File.Exists(filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static T DeserializeFromFile<T>(string filePath){
|
// public static T DeserializeFromFile<T>(string filePath){
|
||||||
var deserializer = new DeserializerBuilder()
|
// var deserializer = new DeserializerBuilder()
|
||||||
.Build();
|
// .Build();
|
||||||
|
//
|
||||||
|
// using (var reader = new StreamReader(filePath)){
|
||||||
|
// return deserializer.Deserialize<T>(reader);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
using (var reader = new StreamReader(filePath)){
|
public static T? ReadJsonFromFile<T>(string pathToFile) where T : class{
|
||||||
return deserializer.Deserialize<T>(reader);
|
try{
|
||||||
|
if (!File.Exists(pathToFile)){
|
||||||
|
throw new FileNotFoundException($"The file at path {pathToFile} does not exist.");
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (fileLock){
|
||||||
|
using (var fileStream = new FileStream(pathToFile, FileMode.Open, FileAccess.Read))
|
||||||
|
using (var streamReader = new StreamReader(fileStream))
|
||||||
|
using (var jsonReader = new JsonTextReader(streamReader)){
|
||||||
|
var serializer = new JsonSerializer();
|
||||||
|
return serializer.Deserialize<T>(jsonReader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ex){
|
||||||
|
Console.Error.WriteLine($"An error occurred while reading the JSON file: {ex.Message}");
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -95,7 +95,7 @@ public class HlsDownloader{
|
||||||
|
|
||||||
// Check if the file exists and it is not a resume download
|
// Check if the file exists and it is not a resume download
|
||||||
if (File.Exists(fn) && !_data.IsResume){
|
if (File.Exists(fn) && !_data.IsResume){
|
||||||
string rwts = _data.Override ?? "Y";
|
string rwts = !string.IsNullOrEmpty(_data.Override) ? _data.Override : "Y";
|
||||||
rwts = rwts.ToUpper(); // ?? "N"
|
rwts = rwts.ToUpper(); // ?? "N"
|
||||||
|
|
||||||
if (rwts.StartsWith("Y")){
|
if (rwts.StartsWith("Y")){
|
||||||
|
|
@ -304,7 +304,7 @@ public class HlsDownloader{
|
||||||
double downloadSpeed = downloadedBytes / (dateElapsed / 1000);
|
double downloadSpeed = downloadedBytes / (dateElapsed / 1000);
|
||||||
|
|
||||||
int partsLeft = partsTotal - partsDownloaded;
|
int partsLeft = partsTotal - partsDownloaded;
|
||||||
double remainingTime = (partsLeft * (totalDownloadedBytes / partsDownloaded)) / downloadSpeed;
|
double remainingTime = (partsLeft * ((double)totalDownloadedBytes / partsDownloaded)) / downloadSpeed;
|
||||||
|
|
||||||
return new Info{
|
return new Info{
|
||||||
Percent = percent,
|
Percent = percent,
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,10 @@ using Avalonia.Media;
|
||||||
using Avalonia.Media.Imaging;
|
using Avalonia.Media.Imaging;
|
||||||
using CRD.Downloader;
|
using CRD.Downloader;
|
||||||
using CRD.Utils.Ffmpeg_Encoding;
|
using CRD.Utils.Ffmpeg_Encoding;
|
||||||
|
using CRD.Utils.Files;
|
||||||
using CRD.Utils.JsonConv;
|
using CRD.Utils.JsonConv;
|
||||||
using CRD.Utils.Structs;
|
using CRD.Utils.Structs;
|
||||||
|
using CRD.Utils.Structs.Crunchyroll;
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
|
@ -747,4 +749,95 @@ public class Helpers{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static CrDownloadOptions MigrateSettings(CrDownloadOptionsYaml yaml){
|
||||||
|
if (yaml == null){
|
||||||
|
throw new ArgumentNullException(nameof(yaml));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CrDownloadOptions{
|
||||||
|
// General Settings
|
||||||
|
AutoDownload = yaml.AutoDownload,
|
||||||
|
RemoveFinishedDownload = yaml.RemoveFinishedDownload,
|
||||||
|
Timeout = yaml.Timeout,
|
||||||
|
FsRetryTime = yaml.FsRetryTime,
|
||||||
|
Force = yaml.Force,
|
||||||
|
SimultaneousDownloads = yaml.SimultaneousDownloads,
|
||||||
|
Theme = yaml.Theme,
|
||||||
|
AccentColor = yaml.AccentColor,
|
||||||
|
BackgroundImagePath = yaml.BackgroundImagePath,
|
||||||
|
BackgroundImageOpacity = yaml.BackgroundImageOpacity,
|
||||||
|
BackgroundImageBlurRadius = yaml.BackgroundImageBlurRadius,
|
||||||
|
Override = yaml.Override,
|
||||||
|
CcTag = yaml.CcTag,
|
||||||
|
Nocleanup = yaml.Nocleanup,
|
||||||
|
History = yaml.History,
|
||||||
|
HistoryIncludeCrArtists = yaml.HistoryIncludeCrArtists,
|
||||||
|
HistoryLang = yaml.HistoryLang,
|
||||||
|
HistoryAddSpecials = yaml.HistoryAddSpecials,
|
||||||
|
HistorySkipUnmonitored = yaml.HistorySkipUnmonitored,
|
||||||
|
HistoryCountSonarr = yaml.HistoryCountSonarr,
|
||||||
|
SonarrProperties = yaml.SonarrProperties,
|
||||||
|
LogMode = yaml.LogMode,
|
||||||
|
DownloadDirPath = yaml.DownloadDirPath,
|
||||||
|
DownloadTempDirPath = yaml.DownloadTempDirPath,
|
||||||
|
DownloadToTempFolder = yaml.DownloadToTempFolder,
|
||||||
|
HistoryPageProperties = yaml.HistoryPageProperties,
|
||||||
|
SeasonsPageProperties = yaml.SeasonsPageProperties,
|
||||||
|
DownloadSpeedLimit = yaml.DownloadSpeedLimit,
|
||||||
|
ProxyEnabled = yaml.ProxyEnabled,
|
||||||
|
ProxySocks = yaml.ProxySocks,
|
||||||
|
ProxyHost = yaml.ProxyHost,
|
||||||
|
ProxyPort = yaml.ProxyPort,
|
||||||
|
ProxyUsername = yaml.ProxyUsername,
|
||||||
|
ProxyPassword = yaml.ProxyPassword,
|
||||||
|
|
||||||
|
// Crunchyroll Settings
|
||||||
|
Hslang = yaml.Hslang,
|
||||||
|
Kstream = yaml.Kstream,
|
||||||
|
Novids = yaml.Novids,
|
||||||
|
Noaudio = yaml.Noaudio,
|
||||||
|
StreamServer = yaml.StreamServer,
|
||||||
|
QualityVideo = yaml.QualityVideo,
|
||||||
|
QualityAudio = yaml.QualityAudio,
|
||||||
|
FileName = yaml.FileName,
|
||||||
|
Numbers = yaml.Numbers,
|
||||||
|
Partsize = yaml.Partsize,
|
||||||
|
DlSubs = yaml.DlSubs,
|
||||||
|
SkipSubs = yaml.SkipSubs,
|
||||||
|
SkipSubsMux = yaml.SkipSubsMux,
|
||||||
|
SubsAddScaledBorder = yaml.SubsAddScaledBorder,
|
||||||
|
IncludeSignsSubs = yaml.IncludeSignsSubs,
|
||||||
|
SignsSubsAsForced = yaml.SignsSubsAsForced,
|
||||||
|
IncludeCcSubs = yaml.IncludeCcSubs,
|
||||||
|
CcSubsFont = yaml.CcSubsFont,
|
||||||
|
CcSubsMuxingFlag = yaml.CcSubsMuxingFlag,
|
||||||
|
Mp4 = yaml.Mp4,
|
||||||
|
VideoTitle = yaml.VideoTitle,
|
||||||
|
IncludeVideoDescription = yaml.IncludeVideoDescription,
|
||||||
|
DescriptionLang = yaml.DescriptionLang,
|
||||||
|
FfmpegOptions = yaml.FfmpegOptions,
|
||||||
|
MkvmergeOptions = yaml.MkvmergeOptions,
|
||||||
|
DefaultSub = yaml.DefaultSub,
|
||||||
|
DefaultSubSigns = yaml.DefaultSubSigns,
|
||||||
|
DefaultSubForcedDisplay = yaml.DefaultSubForcedDisplay,
|
||||||
|
DefaultAudio = yaml.DefaultAudio,
|
||||||
|
DlVideoOnce = yaml.DlVideoOnce,
|
||||||
|
KeepDubsSeperate = yaml.KeepDubsSeperate,
|
||||||
|
SkipMuxing = yaml.SkipMuxing,
|
||||||
|
SyncTiming = yaml.SyncTiming,
|
||||||
|
IsEncodeEnabled = yaml.IsEncodeEnabled,
|
||||||
|
EncodingPresetName = yaml.EncodingPresetName,
|
||||||
|
Chapters = yaml.Chapters,
|
||||||
|
DubLang = yaml.DubLang,
|
||||||
|
SelectedCalendarLanguage = yaml.SelectedCalendarLanguage,
|
||||||
|
CalendarDubFilter = yaml.CalendarDubFilter,
|
||||||
|
CustomCalendar = yaml.CustomCalendar,
|
||||||
|
CalendarHideDubs = yaml.CalendarHideDubs,
|
||||||
|
CalendarFilterByAirDate = yaml.CalendarFilterByAirDate,
|
||||||
|
CalendarShowUpcomingEpisodes = yaml.CalendarShowUpcomingEpisodes,
|
||||||
|
StreamEndpoint = yaml.StreamEndpoint,
|
||||||
|
SearchFetchFeaturedMusic = yaml.SearchFetchFeaturedMusic
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -249,26 +249,26 @@ public static class ApiUrls{
|
||||||
public static readonly string ApiBeta = "https://beta-api.crunchyroll.com";
|
public static readonly string ApiBeta = "https://beta-api.crunchyroll.com";
|
||||||
public static readonly string ApiN = "https://www.crunchyroll.com";
|
public static readonly string ApiN = "https://www.crunchyroll.com";
|
||||||
public static readonly string Anilist = "https://graphql.anilist.co";
|
public static readonly string Anilist = "https://graphql.anilist.co";
|
||||||
|
|
||||||
public static readonly string Auth = ApiN + "/auth/v1/token";
|
public static readonly string Auth = ApiN + "/auth/v1/token";
|
||||||
public static readonly string BetaAuth = ApiBeta + "/auth/v1/token";
|
public static readonly string Profile = ApiN + "/accounts/v1/me/profile";
|
||||||
public static readonly string BetaProfile = ApiBeta + "/accounts/v1/me/profile";
|
public static readonly string CmsToken = ApiN + "/index/v2";
|
||||||
public static readonly string BetaCmsToken = ApiBeta + "/index/v2";
|
public static readonly string Search = ApiN + "/content/v2/discover/search";
|
||||||
public static readonly string Search = ApiBeta + "/content/v2/discover/search";
|
public static readonly string Browse = ApiN + "/content/v2/discover/browse";
|
||||||
public static readonly string Browse = ApiBeta + "/content/v2/discover/browse";
|
public static readonly string Cms = ApiN + "/content/v2/cms";
|
||||||
public static readonly string Cms = ApiBeta + "/content/v2/cms";
|
public static readonly string Content = ApiN + "/content/v2";
|
||||||
public static readonly string Content = ApiBeta + "/content/v2";
|
|
||||||
public static readonly string BetaBrowse = ApiBeta + "/content/v1/browse";
|
public static readonly string BetaBrowse = ApiBeta + "/content/v1/browse";
|
||||||
public static readonly string BetaCms = ApiBeta + "/cms/v2";
|
public static readonly string BetaCms = ApiBeta + "/cms/v2";
|
||||||
public static readonly string DRM = ApiBeta + "/drm/v1/auth";
|
public static readonly string DRM = ApiBeta + "/drm/v1/auth";
|
||||||
|
|
||||||
public static readonly string Subscription = ApiBeta + "/subs/v3/subscriptions/";
|
public static readonly string Subscription = ApiN + "/subs/v3/subscriptions/";
|
||||||
public static readonly string CmsN = ApiN + "/content/v2/cms";
|
|
||||||
|
|
||||||
public static readonly string authBasic = "Basic bm9haWhkZXZtXzZpeWcwYThsMHE6";
|
public static readonly string authBasic = "Basic bm9haWhkZXZtXzZpeWcwYThsMHE6";
|
||||||
|
|
||||||
public static readonly string authBasicMob = "Basic dXU4aG0wb2g4dHFpOWV0eXl2aGo6SDA2VnVjRnZUaDJ1dEYxM0FBS3lLNE85UTRhX3BlX1o=";
|
public static readonly string authBasicMob = "Basic ZG1yeWZlc2NkYm90dWJldW56NXo6NU45aThPV2cyVmtNcm1oekNfNUNXekRLOG55SXo0QU0=";
|
||||||
public static readonly string authBasicSwitch = "Basic dC1rZGdwMmg4YzNqdWI4Zm4wZnE6eWZMRGZNZnJZdktYaDRKWFMxTEVJMmNDcXUxdjVXYW4=";
|
public static readonly string authBasicSwitch = "Basic dC1rZGdwMmg4YzNqdWI4Zm4wZnE6eWZMRGZNZnJZdktYaDRKWFMxTEVJMmNDcXUxdjVXYW4=";
|
||||||
|
|
||||||
public static readonly string ChromeUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36";
|
public static readonly string ChromeUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36";
|
||||||
|
public static readonly string MobileUserAgent = "Crunchyroll/3.74.2 Android/14 okhttp/4.12.0";
|
||||||
}
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using CRD.Utils.Files;
|
||||||
using CRD.Utils.Structs;
|
using CRD.Utils.Structs;
|
||||||
|
|
||||||
namespace CRD.Utils.Muxing;
|
namespace CRD.Utils.Muxing;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using CRD.Downloader.Crunchyroll;
|
using CRD.Downloader.Crunchyroll;
|
||||||
|
using CRD.Utils.Files;
|
||||||
using CRD.Utils.Structs;
|
using CRD.Utils.Structs;
|
||||||
|
|
||||||
namespace CRD.Utils.Muxing;
|
namespace CRD.Utils.Muxing;
|
||||||
|
|
@ -332,18 +333,25 @@ public class Merger{
|
||||||
Time = GetTimeFromFileName(fp, extractFramesCompareEnd.frameRate)
|
Time = GetTimeFromFileName(fp, extractFramesCompareEnd.frameRate)
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Calculate offsets
|
// Calculate offsets
|
||||||
var startOffset = SyncingHelper.CalculateOffset(baseFramesStart, compareFramesStart);
|
var startOffset = SyncingHelper.CalculateOffset(baseFramesStart, compareFramesStart);
|
||||||
var endOffset = SyncingHelper.CalculateOffset(baseFramesEnd, compareFramesEnd,true);
|
var endOffset = SyncingHelper.CalculateOffset(baseFramesEnd, compareFramesEnd,true);
|
||||||
|
|
||||||
var lengthDiff = Math.Abs(baseVideoDurationTimeSpan.Value.TotalMicroseconds - compareVideoDurationTimeSpan.Value.TotalMicroseconds) / 1000000;
|
var lengthDiff = (baseVideoDurationTimeSpan.Value.TotalMicroseconds - compareVideoDurationTimeSpan.Value.TotalMicroseconds) / 1000000;
|
||||||
|
|
||||||
endOffset += lengthDiff;
|
endOffset += lengthDiff;
|
||||||
|
|
||||||
Console.WriteLine($"Start offset: {startOffset} seconds");
|
Console.WriteLine($"Start offset: {startOffset} seconds");
|
||||||
Console.WriteLine($"End offset: {endOffset} seconds");
|
Console.WriteLine($"End offset: {endOffset} seconds");
|
||||||
|
|
||||||
CleanupDirectory(cleanupDir);
|
CleanupDirectory(cleanupDir);
|
||||||
|
|
||||||
|
baseFramesStart.Clear();
|
||||||
|
baseFramesEnd.Clear();
|
||||||
|
compareFramesStart.Clear();
|
||||||
|
compareFramesEnd.Clear();
|
||||||
|
|
||||||
var difference = Math.Abs(startOffset - endOffset);
|
var difference = Math.Abs(startOffset - endOffset);
|
||||||
|
|
||||||
|
|
@ -370,7 +378,7 @@ public class Merger{
|
||||||
private static double GetTimeFromFileName(string fileName, double frameRate){
|
private static double GetTimeFromFileName(string fileName, double frameRate){
|
||||||
var match = Regex.Match(Path.GetFileName(fileName), @"frame(\d+)");
|
var match = Regex.Match(Path.GetFileName(fileName), @"frame(\d+)");
|
||||||
if (match.Success){
|
if (match.Success){
|
||||||
return int.Parse(match.Groups[1].Value) / frameRate; // Assuming 30 fps
|
return int.Parse(match.Groups[1].Value) / frameRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using CRD.Utils.Files;
|
||||||
using CRD.Utils.Structs;
|
using CRD.Utils.Structs;
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
|
@ -16,7 +17,7 @@ namespace CRD.Utils.Muxing;
|
||||||
public class SyncingHelper{
|
public class SyncingHelper{
|
||||||
public static async Task<(bool IsOk, int ErrorCode, double frameRate)> ExtractFrames(string videoPath, string outputDir, double offset, double duration){
|
public static async Task<(bool IsOk, int ErrorCode, double frameRate)> ExtractFrames(string videoPath, string outputDir, double offset, double duration){
|
||||||
var ffmpegPath = CfgManager.PathFFMPEG;
|
var ffmpegPath = CfgManager.PathFFMPEG;
|
||||||
var arguments = $"-i \"{videoPath}\" -vf \"select='gt(scene,0.1)',showinfo\" -fps_mode vfr -frame_pts true -t {duration} -ss {offset} \"{outputDir}\\frame%03d.png\"";
|
var arguments = $"-i \"{videoPath}\" -vf \"select='gt(scene,0.1)',showinfo\" -fps_mode vfr -frame_pts true -t {duration} -ss {offset} \"{outputDir}\\frame%05d.png\"";
|
||||||
|
|
||||||
var output = "";
|
var output = "";
|
||||||
|
|
||||||
|
|
@ -68,7 +69,7 @@ public class SyncingHelper{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static double CalculateSSIM(float[] pixels1, float[] pixels2, int width, int height){
|
private static double CalculateSSIM(float[] pixels1, float[] pixels2){
|
||||||
double mean1 = pixels1.Average();
|
double mean1 = pixels1.Average();
|
||||||
double mean2 = pixels2.Average();
|
double mean2 = pixels2.Average();
|
||||||
|
|
||||||
|
|
@ -110,7 +111,7 @@ public class SyncingHelper{
|
||||||
return pixels;
|
return pixels;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static double ComputeSSIM(string imagePath1, string imagePath2, int targetWidth, int targetHeight){
|
public static (double ssim, double pixelDiff) ComputeSSIM(string imagePath1, string imagePath2, int targetWidth, int targetHeight){
|
||||||
using (var image1 = Image.Load<Rgba32>(imagePath1))
|
using (var image1 = Image.Load<Rgba32>(imagePath1))
|
||||||
using (var image2 = Image.Load<Rgba32>(imagePath2)){
|
using (var image2 = Image.Load<Rgba32>(imagePath2)){
|
||||||
// Preprocess images (resize and convert to grayscale)
|
// Preprocess images (resize and convert to grayscale)
|
||||||
|
|
@ -131,13 +132,24 @@ public class SyncingHelper{
|
||||||
// Check if any frame is completely black, if so, skip SSIM calculation
|
// Check if any frame is completely black, if so, skip SSIM calculation
|
||||||
if (IsBlackFrame(pixels1) || IsBlackFrame(pixels2)){
|
if (IsBlackFrame(pixels1) || IsBlackFrame(pixels2)){
|
||||||
// Return a negative value or zero to indicate no SSIM comparison for black frames.
|
// Return a negative value or zero to indicate no SSIM comparison for black frames.
|
||||||
return -1.0;
|
return (-1.0,99);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute SSIM
|
// Compute SSIM
|
||||||
return CalculateSSIM(pixels1, pixels2, targetWidth, targetHeight);
|
return (CalculateSSIM(pixels1, pixels2),CalculatePixelDifference(pixels1,pixels2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static double CalculatePixelDifference(float[] pixels1, float[] pixels2){
|
||||||
|
double totalDifference = 0;
|
||||||
|
int count = pixels1.Length;
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++){
|
||||||
|
totalDifference += Math.Abs(pixels1[i] - pixels2[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalDifference / count; // Average difference
|
||||||
|
}
|
||||||
|
|
||||||
private static bool IsBlackFrame(float[] pixels, float threshold = 1.0f){
|
private static bool IsBlackFrame(float[] pixels, float threshold = 1.0f){
|
||||||
// Check if all pixel values are below the threshold, indicating a black frame.
|
// Check if all pixel values are below the threshold, indicating a black frame.
|
||||||
|
|
@ -145,10 +157,13 @@ public class SyncingHelper{
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool AreFramesSimilar(string imagePath1, string imagePath2, double ssimThreshold){
|
public static bool AreFramesSimilar(string imagePath1, string imagePath2, double ssimThreshold){
|
||||||
double ssim = ComputeSSIM(imagePath1, imagePath2, 256, 256);
|
var (ssim, pixelDiff) = ComputeSSIM(imagePath1, imagePath2, 256, 256);
|
||||||
// Console.WriteLine($"SSIM: {ssim}");
|
// Console.WriteLine($"SSIM: {ssim}");
|
||||||
return ssim > ssimThreshold;
|
// Console.WriteLine(pixelDiff);
|
||||||
|
|
||||||
|
return ssim > ssimThreshold && pixelDiff < 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static double CalculateOffset(List<FrameData> baseFrames, List<FrameData> compareFrames,bool reverseCompare = false, double ssimThreshold = 0.9){
|
public static double CalculateOffset(List<FrameData> baseFrames, List<FrameData> compareFrames,bool reverseCompare = false, double ssimThreshold = 0.9){
|
||||||
|
|
||||||
|
|
@ -160,7 +175,9 @@ public class SyncingHelper{
|
||||||
foreach (var baseFrame in baseFrames){
|
foreach (var baseFrame in baseFrames){
|
||||||
var matchingFrame = compareFrames.FirstOrDefault(f => AreFramesSimilar(baseFrame.FilePath, f.FilePath, ssimThreshold));
|
var matchingFrame = compareFrames.FirstOrDefault(f => AreFramesSimilar(baseFrame.FilePath, f.FilePath, ssimThreshold));
|
||||||
if (matchingFrame != null){
|
if (matchingFrame != null){
|
||||||
Console.WriteLine($"Matched Frame: Base Frame Time: {baseFrame.Time}, Compare Frame Time: {matchingFrame.Time}");
|
Console.WriteLine($"Matched Frame:");
|
||||||
|
Console.WriteLine($"\t Base Frame Path: {baseFrame.FilePath} Time: {baseFrame.Time},");
|
||||||
|
Console.WriteLine($"\t Compare Frame Path: {matchingFrame.FilePath} Time: {matchingFrame.Time}");
|
||||||
return baseFrame.Time - matchingFrame.Time;
|
return baseFrame.Time - matchingFrame.Time;
|
||||||
} else{
|
} else{
|
||||||
// Console.WriteLine($"No Match Found for Base Frame Time: {baseFrame.Time}");
|
// Console.WriteLine($"No Match Found for Base Frame Time: {baseFrame.Time}");
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ public static class MPDParser{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
var foundLanguage = Languages.FindLang(Languages.languages.FirstOrDefault(a => a.Code == item.language).CrLocale ?? "unknown");
|
var foundLanguage = Languages.FindLang(Languages.languages.FirstOrDefault(a => a.Code == item.language)?.CrLocale ?? "unknown");
|
||||||
LanguageItem? audioLang = item.language != null ? foundLanguage : (language != null ? language : foundLanguage);
|
LanguageItem? audioLang = item.language != null ? foundLanguage : (language != null ? language : foundLanguage);
|
||||||
|
|
||||||
var pItem = new AudioPlaylist{
|
var pItem = new AudioPlaylist{
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,13 @@ using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace CRD.Utils.Structs;
|
namespace CRD.Utils.Structs;
|
||||||
|
|
||||||
public struct CrunchyChapters{
|
public class CrunchyChapters{
|
||||||
public List<CrunchyChapter> Chapters { get; set; }
|
public List<CrunchyChapter> Chapters { get; set; }
|
||||||
public DateTime lastUpdate { get; set; }
|
public DateTime lastUpdate { get; set; }
|
||||||
public string? mediaId { get; set; }
|
public string? mediaId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct CrunchyChapter{
|
public class CrunchyChapter{
|
||||||
public string approverId { get; set; }
|
public string approverId { get; set; }
|
||||||
public string distributionNumber { get; set; }
|
public string distributionNumber { get; set; }
|
||||||
public double? end { get; set; }
|
public double? end { get; set; }
|
||||||
|
|
@ -22,7 +22,7 @@ public struct CrunchyChapter{
|
||||||
public string type { get; set; }
|
public string type { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct CrunchyOldChapter{
|
public class CrunchyOldChapter{
|
||||||
public string media_id { get; set; }
|
public string media_id { get; set; }
|
||||||
public double startTime { get; set; }
|
public double startTime { get; set; }
|
||||||
public double endTime { get; set; }
|
public double endTime { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,261 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using CRD.Utils.Sonarr;
|
using CRD.Utils.Sonarr;
|
||||||
using CRD.ViewModels;
|
using CRD.ViewModels;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
|
|
||||||
namespace CRD.Utils.Structs;
|
namespace CRD.Utils.Structs.Crunchyroll;
|
||||||
|
|
||||||
public class CrDownloadOptions{
|
public class CrDownloadOptions{
|
||||||
#region General Settings
|
#region General Settings
|
||||||
|
|
||||||
|
[JsonProperty("auto_download")]
|
||||||
|
public bool AutoDownload{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("remove_finished_downloads")]
|
||||||
|
public bool RemoveFinishedDownload{ get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public int Timeout{ get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public int FsRetryTime{ get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public string Force{ get; set; } = "";
|
||||||
|
|
||||||
|
[JsonProperty("simultaneous_downloads")]
|
||||||
|
public int SimultaneousDownloads{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("theme")]
|
||||||
|
public string Theme{ get; set; } = "";
|
||||||
|
|
||||||
|
[JsonProperty("accent_color")]
|
||||||
|
public string? AccentColor{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("background_image_path")]
|
||||||
|
public string? BackgroundImagePath{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("background_image_opacity")]
|
||||||
|
public double BackgroundImageOpacity{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("background_image_blur_radius")]
|
||||||
|
public double BackgroundImageBlurRadius{ get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public List<string> Override{ get; set; } =[];
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public string CcTag{ get; set; } = "";
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public bool Nocleanup{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("history")]
|
||||||
|
public bool History{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("history_include_cr_artists")]
|
||||||
|
public bool HistoryIncludeCrArtists{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("history_lang")]
|
||||||
|
public string? HistoryLang{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("history_add_specials")]
|
||||||
|
public bool HistoryAddSpecials{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("history_skip_unmonitored")]
|
||||||
|
public bool HistorySkipUnmonitored{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("history_count_sonarr")]
|
||||||
|
public bool HistoryCountSonarr{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("sonarr_properties")]
|
||||||
|
public SonarrProperties? SonarrProperties{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("log_mode")]
|
||||||
|
public bool LogMode{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("download_dir_path")]
|
||||||
|
public string? DownloadDirPath{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("download_temp_dir_path")]
|
||||||
|
public string? DownloadTempDirPath{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("download_to_temp_folder")]
|
||||||
|
public bool DownloadToTempFolder{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("history_page_properties")]
|
||||||
|
public HistoryPageProperties? HistoryPageProperties{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("seasons_page_properties")]
|
||||||
|
public SeasonsPageProperties? SeasonsPageProperties{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("download_speed_limit")]
|
||||||
|
public int DownloadSpeedLimit{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("proxy_enabled")]
|
||||||
|
public bool ProxyEnabled{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("proxy_socks")]
|
||||||
|
public bool ProxySocks{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("proxy_host")]
|
||||||
|
public string? ProxyHost{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("proxy_port")]
|
||||||
|
public int ProxyPort{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("proxy_username")]
|
||||||
|
public string? ProxyUsername{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("proxy_password")]
|
||||||
|
public string? ProxyPassword{ get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Crunchyroll Settings
|
||||||
|
|
||||||
|
[JsonProperty("hard_sub_lang")]
|
||||||
|
public string Hslang{ get; set; } = "";
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public int Kstream{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("no_video")]
|
||||||
|
public bool Novids{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("no_audio")]
|
||||||
|
public bool Noaudio{ get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public int StreamServer{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("quality_video")]
|
||||||
|
public string QualityVideo{ get; set; } = "";
|
||||||
|
|
||||||
|
[JsonProperty("quality_audio")]
|
||||||
|
public string QualityAudio{ get; set; } = "";
|
||||||
|
|
||||||
|
[JsonProperty("file_name")]
|
||||||
|
public string FileName{ get; set; } = "";
|
||||||
|
|
||||||
|
[JsonProperty("leading_numbers")]
|
||||||
|
public int Numbers{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("download_part_size")]
|
||||||
|
public int Partsize{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("soft_subs")]
|
||||||
|
public List<string> DlSubs{ get; set; } =[];
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public bool SkipSubs{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("mux_skip_subs")]
|
||||||
|
public bool SkipSubsMux{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("subs_add_scaled_border")]
|
||||||
|
public ScaledBorderAndShadowSelection SubsAddScaledBorder{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("include_signs_subs")]
|
||||||
|
public bool IncludeSignsSubs{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("mux_signs_subs_flag")]
|
||||||
|
public bool SignsSubsAsForced{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("include_cc_subs")]
|
||||||
|
public bool IncludeCcSubs{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("cc_subs_font")]
|
||||||
|
public string? CcSubsFont{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("mux_cc_subs_flag")]
|
||||||
|
public bool CcSubsMuxingFlag{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("mux_mp4")]
|
||||||
|
public bool Mp4{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("mux_video_title")]
|
||||||
|
public string? VideoTitle{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("mux_video_description")]
|
||||||
|
public bool IncludeVideoDescription{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("mux_description_lang")]
|
||||||
|
public string? DescriptionLang{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("mux_ffmpeg")]
|
||||||
|
public List<string> FfmpegOptions{ get; set; } =[];
|
||||||
|
|
||||||
|
[JsonProperty("mux_mkvmerge")]
|
||||||
|
public List<string> MkvmergeOptions{ get; set; } =[];
|
||||||
|
|
||||||
|
[JsonProperty("mux_default_sub")]
|
||||||
|
public string DefaultSub{ get; set; } = "";
|
||||||
|
|
||||||
|
[JsonProperty("mux_default_sub_signs")]
|
||||||
|
public bool DefaultSubSigns{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("mux_default_sub_forced_display")]
|
||||||
|
public bool DefaultSubForcedDisplay{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("mux_default_dub")]
|
||||||
|
public string DefaultAudio{ get; set; } = "";
|
||||||
|
|
||||||
|
[JsonProperty("dl_video_once")]
|
||||||
|
public bool DlVideoOnce{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("keep_dubs_seperate")]
|
||||||
|
public bool KeepDubsSeperate{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("mux_skip_muxing")]
|
||||||
|
public bool SkipMuxing{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("mux_sync_dubs")]
|
||||||
|
public bool SyncTiming{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("encode_enabled")]
|
||||||
|
public bool IsEncodeEnabled{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("encode_preset")]
|
||||||
|
public string? EncodingPresetName{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("chapters")]
|
||||||
|
public bool Chapters{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("dub_lang")]
|
||||||
|
public List<string> DubLang{ get; set; } =[];
|
||||||
|
|
||||||
|
[JsonProperty("calendar_language")]
|
||||||
|
public string? SelectedCalendarLanguage{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("calendar_dub_filter")]
|
||||||
|
public string? CalendarDubFilter{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("calendar_custom")]
|
||||||
|
public bool CustomCalendar{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("calendar_hide_dubs")]
|
||||||
|
public bool CalendarHideDubs{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("calendar_filter_by_air_date")]
|
||||||
|
public bool CalendarFilterByAirDate{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("calendar_show_upcoming_episodes")]
|
||||||
|
public bool CalendarShowUpcomingEpisodes{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("stream_endpoint")]
|
||||||
|
public string? StreamEndpoint{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("search_fetch_featured_music")]
|
||||||
|
public bool SearchFetchFeaturedMusic{ get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CrDownloadOptionsYaml{
|
||||||
|
#region General Settings
|
||||||
|
|
||||||
[YamlMember(Alias = "auto_download", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "auto_download", ApplyNamingConventions = false)]
|
||||||
public bool AutoDownload{ get; set; }
|
public bool AutoDownload{ get; set; }
|
||||||
|
|
||||||
|
|
@ -21,13 +269,13 @@ public class CrDownloadOptions{
|
||||||
public int FsRetryTime{ get; set; }
|
public int FsRetryTime{ get; set; }
|
||||||
|
|
||||||
[YamlIgnore]
|
[YamlIgnore]
|
||||||
public string Force{ get; set; }
|
public string Force{ get; set; } = "";
|
||||||
|
|
||||||
[YamlMember(Alias = "simultaneous_downloads", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "simultaneous_downloads", ApplyNamingConventions = false)]
|
||||||
public int SimultaneousDownloads{ get; set; }
|
public int SimultaneousDownloads{ get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "theme", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "theme", ApplyNamingConventions = false)]
|
||||||
public string Theme{ get; set; }
|
public string Theme{ get; set; } = "";
|
||||||
|
|
||||||
[YamlMember(Alias = "accent_color", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "accent_color", ApplyNamingConventions = false)]
|
||||||
public string? AccentColor{ get; set; }
|
public string? AccentColor{ get; set; }
|
||||||
|
|
@ -43,23 +291,29 @@ public class CrDownloadOptions{
|
||||||
|
|
||||||
|
|
||||||
[YamlIgnore]
|
[YamlIgnore]
|
||||||
public List<string> Override{ get; set; }
|
public List<string> Override{ get; } =[];
|
||||||
|
|
||||||
[YamlIgnore]
|
[YamlIgnore]
|
||||||
public string CcTag{ get; set; }
|
public string CcTag{ get; set; } = "";
|
||||||
|
|
||||||
[YamlIgnore]
|
[YamlIgnore]
|
||||||
public bool Nocleanup{ get; set; }
|
public bool Nocleanup{ get; } = false;
|
||||||
|
|
||||||
[YamlMember(Alias = "history", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "history", ApplyNamingConventions = false)]
|
||||||
public bool History{ get; set; }
|
public bool History{ get; set; }
|
||||||
|
|
||||||
|
[YamlMember(Alias = "history_include_cr_artists", ApplyNamingConventions = false)]
|
||||||
|
public bool HistoryIncludeCrArtists{ get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "history_lang", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "history_lang", ApplyNamingConventions = false)]
|
||||||
public string? HistoryLang{ get; set; }
|
public string? HistoryLang{ get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "history_add_specials", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "history_add_specials", ApplyNamingConventions = false)]
|
||||||
public bool HistoryAddSpecials{ get; set; }
|
public bool HistoryAddSpecials{ get; set; }
|
||||||
|
|
||||||
|
[YamlMember(Alias = "history_skip_unmonitored", ApplyNamingConventions = false)]
|
||||||
|
public bool HistorySkipUnmonitored{ get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "history_count_sonarr", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "history_count_sonarr", ApplyNamingConventions = false)]
|
||||||
public bool HistoryCountSonarr{ get; set; }
|
public bool HistoryCountSonarr{ get; set; }
|
||||||
|
|
||||||
|
|
@ -80,7 +334,7 @@ public class CrDownloadOptions{
|
||||||
|
|
||||||
[YamlMember(Alias = "history_page_properties", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "history_page_properties", ApplyNamingConventions = false)]
|
||||||
public HistoryPageProperties? HistoryPageProperties{ get; set; }
|
public HistoryPageProperties? HistoryPageProperties{ get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "seasons_page_properties", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "seasons_page_properties", ApplyNamingConventions = false)]
|
||||||
public SeasonsPageProperties? SeasonsPageProperties{ get; set; }
|
public SeasonsPageProperties? SeasonsPageProperties{ get; set; }
|
||||||
|
|
||||||
|
|
@ -89,7 +343,7 @@ public class CrDownloadOptions{
|
||||||
|
|
||||||
[YamlMember(Alias = "proxy_enabled", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "proxy_enabled", ApplyNamingConventions = false)]
|
||||||
public bool ProxyEnabled{ get; set; }
|
public bool ProxyEnabled{ get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "proxy_socks", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "proxy_socks", ApplyNamingConventions = false)]
|
||||||
public bool ProxySocks{ get; set; }
|
public bool ProxySocks{ get; set; }
|
||||||
|
|
||||||
|
|
@ -98,20 +352,20 @@ public class CrDownloadOptions{
|
||||||
|
|
||||||
[YamlMember(Alias = "proxy_port", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "proxy_port", ApplyNamingConventions = false)]
|
||||||
public int ProxyPort{ get; set; }
|
public int ProxyPort{ get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "proxy_username", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "proxy_username", ApplyNamingConventions = false)]
|
||||||
public string? ProxyUsername{ get; set; }
|
public string? ProxyUsername{ get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "proxy_password", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "proxy_password", ApplyNamingConventions = false)]
|
||||||
public string? ProxyPassword{ get; set; }
|
public string? ProxyPassword{ get; set; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
#region Crunchyroll Settings
|
#region Crunchyroll Settings
|
||||||
|
|
||||||
[YamlMember(Alias = "hard_sub_lang", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "hard_sub_lang", ApplyNamingConventions = false)]
|
||||||
public string Hslang{ get; set; }
|
public string Hslang{ get; set; } = "";
|
||||||
|
|
||||||
[YamlIgnore]
|
[YamlIgnore]
|
||||||
public int Kstream{ get; set; }
|
public int Kstream{ get; set; }
|
||||||
|
|
@ -126,23 +380,23 @@ public class CrDownloadOptions{
|
||||||
public int StreamServer{ get; set; }
|
public int StreamServer{ get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "quality_video", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "quality_video", ApplyNamingConventions = false)]
|
||||||
public string QualityVideo{ get; set; }
|
public string QualityVideo{ get; set; } = "";
|
||||||
|
|
||||||
[YamlMember(Alias = "quality_audio", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "quality_audio", ApplyNamingConventions = false)]
|
||||||
public string QualityAudio{ get; set; }
|
public string QualityAudio{ get; set; } = "";
|
||||||
|
|
||||||
[YamlMember(Alias = "file_name", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "file_name", ApplyNamingConventions = false)]
|
||||||
public string FileName{ get; set; }
|
public string FileName{ get; set; } = "";
|
||||||
|
|
||||||
[YamlMember(Alias = "leading_numbers", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "leading_numbers", ApplyNamingConventions = false)]
|
||||||
public int Numbers{ get; set; }
|
public int Numbers{ get; set; }
|
||||||
|
|
||||||
[YamlIgnore]
|
[YamlMember(Alias = "download_part_size", ApplyNamingConventions = false)]
|
||||||
public int Partsize{ get; set; }
|
public int Partsize{ get; set; }
|
||||||
|
|
||||||
|
|
||||||
[YamlMember(Alias = "soft_subs", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "soft_subs", ApplyNamingConventions = false)]
|
||||||
public List<string> DlSubs{ get; set; }
|
public List<string> DlSubs{ get; set; } =[];
|
||||||
|
|
||||||
[YamlIgnore]
|
[YamlIgnore]
|
||||||
public bool SkipSubs{ get; set; }
|
public bool SkipSubs{ get; set; }
|
||||||
|
|
@ -181,13 +435,13 @@ public class CrDownloadOptions{
|
||||||
public string? DescriptionLang{ get; set; }
|
public string? DescriptionLang{ get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "mux_ffmpeg", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "mux_ffmpeg", ApplyNamingConventions = false)]
|
||||||
public List<string> FfmpegOptions{ get; set; }
|
public List<string> FfmpegOptions{ get; set; } =[];
|
||||||
|
|
||||||
[YamlMember(Alias = "mux_mkvmerge", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "mux_mkvmerge", ApplyNamingConventions = false)]
|
||||||
public List<string> MkvmergeOptions{ get; set; }
|
public List<string> MkvmergeOptions{ get; set; } =[];
|
||||||
|
|
||||||
[YamlMember(Alias = "mux_default_sub", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "mux_default_sub", ApplyNamingConventions = false)]
|
||||||
public string DefaultSub{ get; set; }
|
public string DefaultSub{ get; set; } = "";
|
||||||
|
|
||||||
[YamlMember(Alias = "mux_default_sub_signs", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "mux_default_sub_signs", ApplyNamingConventions = false)]
|
||||||
public bool DefaultSubSigns{ get; set; }
|
public bool DefaultSubSigns{ get; set; }
|
||||||
|
|
@ -196,7 +450,7 @@ public class CrDownloadOptions{
|
||||||
public bool DefaultSubForcedDisplay{ get; set; }
|
public bool DefaultSubForcedDisplay{ get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "mux_default_dub", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "mux_default_dub", ApplyNamingConventions = false)]
|
||||||
public string DefaultAudio{ get; set; }
|
public string DefaultAudio{ get; set; } = "";
|
||||||
|
|
||||||
[YamlMember(Alias = "dl_video_once", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "dl_video_once", ApplyNamingConventions = false)]
|
||||||
public bool DlVideoOnce{ get; set; }
|
public bool DlVideoOnce{ get; set; }
|
||||||
|
|
@ -220,8 +474,7 @@ public class CrDownloadOptions{
|
||||||
public bool Chapters{ get; set; }
|
public bool Chapters{ get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "dub_lang", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "dub_lang", ApplyNamingConventions = false)]
|
||||||
public List<string> DubLang{ get; set; }
|
public List<string> DubLang{ get; set; } =[];
|
||||||
|
|
||||||
|
|
||||||
[YamlMember(Alias = "calendar_language", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "calendar_language", ApplyNamingConventions = false)]
|
||||||
public string? SelectedCalendarLanguage{ get; set; }
|
public string? SelectedCalendarLanguage{ get; set; }
|
||||||
|
|
@ -237,12 +490,15 @@ public class CrDownloadOptions{
|
||||||
|
|
||||||
[YamlMember(Alias = "calendar_filter_by_air_date", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "calendar_filter_by_air_date", ApplyNamingConventions = false)]
|
||||||
public bool CalendarFilterByAirDate{ get; set; }
|
public bool CalendarFilterByAirDate{ get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "calendar_show_upcoming_episodes", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "calendar_show_upcoming_episodes", ApplyNamingConventions = false)]
|
||||||
public bool CalendarShowUpcomingEpisodes{ get; set; }
|
public bool CalendarShowUpcomingEpisodes{ get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "stream_endpoint", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "stream_endpoint", ApplyNamingConventions = false)]
|
||||||
public string? StreamEndpoint{ get; set; }
|
public string? StreamEndpoint{ get; set; }
|
||||||
|
|
||||||
|
[YamlMember(Alias = "search_fetch_featured_music", ApplyNamingConventions = false)]
|
||||||
|
public bool SearchFetchFeaturedMusic{ get; set; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace CRD.Utils.Structs;
|
namespace CRD.Utils.Structs;
|
||||||
|
|
||||||
public struct CrunchyMovieList{
|
public class CrunchyMovieList{
|
||||||
public int Total{ get; set; }
|
public int Total{ get; set; }
|
||||||
public List<CrunchyMovie>? Data{ get; set; }
|
public List<CrunchyMovie>? Data{ get; set; }
|
||||||
public Meta Meta{ get; set; }
|
public Meta Meta{ get; set; }
|
||||||
|
|
|
||||||
|
|
@ -185,7 +185,7 @@ public class CrBrowseEpisodeMetaData{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct CrBrowseEpisodeVersion{
|
public class CrBrowseEpisodeVersion{
|
||||||
[JsonProperty("audio_locale")]
|
[JsonProperty("audio_locale")]
|
||||||
public Locale? AudioLocale{ get; set; }
|
public Locale? AudioLocale{ get; set; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,19 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using CRD.Utils.Structs.History;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace CRD.Utils.Structs;
|
namespace CRD.Utils.Structs;
|
||||||
|
|
||||||
public struct CrunchyEpisodeList{
|
public class CrunchyEpisodeList{
|
||||||
public int Total{ get; set; }
|
public int Total{ get; set; }
|
||||||
public List<CrunchyEpisode>? Data{ get; set; }
|
public List<CrunchyEpisode>? Data{ get; set; }
|
||||||
public Meta Meta{ get; set; }
|
public Meta Meta{ get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct CrunchyEpisode{
|
public class CrunchyEpisode : IHistorySource{
|
||||||
[JsonProperty("next_episode_id")]
|
[JsonProperty("next_episode_id")]
|
||||||
public string NextEpisodeId{ get; set; }
|
public string NextEpisodeId{ get; set; }
|
||||||
|
|
||||||
|
|
@ -81,7 +84,7 @@ public struct CrunchyEpisode{
|
||||||
[JsonProperty("audio_locale")]
|
[JsonProperty("audio_locale")]
|
||||||
public string AudioLocale{ get; set; }
|
public string AudioLocale{ get; set; }
|
||||||
|
|
||||||
public string Id{ get; set; }
|
public required string Id{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("media_type")]
|
[JsonProperty("media_type")]
|
||||||
public string? MediaType{ get; set; }
|
public string? MediaType{ get; set; }
|
||||||
|
|
@ -173,9 +176,123 @@ public struct CrunchyEpisode{
|
||||||
|
|
||||||
[JsonProperty("__links__")]
|
[JsonProperty("__links__")]
|
||||||
public Links? Links{ get; set; }
|
public Links? Links{ get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public EpisodeType EpisodeType{ get; set; } = EpisodeType.Episode;
|
||||||
|
|
||||||
|
#region Interface
|
||||||
|
|
||||||
|
public string GetSeriesId(){
|
||||||
|
return SeriesId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetSeriesTitle(){
|
||||||
|
return SeriesTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetSeasonTitle(){
|
||||||
|
return SeasonTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetSeasonNum(){
|
||||||
|
return Helpers.ExtractNumberAfterS(Identifier) ?? SeasonNumber + "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetSeasonId(){
|
||||||
|
return SeasonId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetEpisodeId(){
|
||||||
|
return Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetEpisodeNumber(){
|
||||||
|
return Episode ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetEpisodeTitle(){
|
||||||
|
if (Identifier.Contains("|M|")){
|
||||||
|
if (string.IsNullOrEmpty(Title)){
|
||||||
|
if (SeasonTitle.StartsWith(SeriesTitle)){
|
||||||
|
var splitTitle = SeasonTitle.Split(new[]{ SeriesTitle }, StringSplitOptions.None);
|
||||||
|
var titlePart = splitTitle.Length > 1 ? splitTitle[1] : splitTitle[0];
|
||||||
|
var cleanedTitle = Regex.Replace(titlePart, @"^[^a-zA-Z]+", "");
|
||||||
|
|
||||||
|
return cleanedTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SeasonTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Title.StartsWith(SeriesTitle)){
|
||||||
|
var splitTitle = Title.Split(new[]{ SeriesTitle }, StringSplitOptions.None);
|
||||||
|
var titlePart = splitTitle.Length > 1 ? splitTitle[1] : splitTitle[0];
|
||||||
|
var cleanedTitle = Regex.Replace(titlePart, @"^[^a-zA-Z]+", "");
|
||||||
|
|
||||||
|
return cleanedTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Title;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetEpisodeDescription(){
|
||||||
|
return Description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsSpecialSeason(){
|
||||||
|
if (string.IsNullOrEmpty(Identifier)){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// does NOT contain "|S" followed by one or more digits immediately after
|
||||||
|
string pattern = @"^(?!.*\|S\d+).*";
|
||||||
|
|
||||||
|
return Regex.IsMatch(Identifier, pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsSpecialEpisode(){
|
||||||
|
return !int.TryParse(Episode, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<string> GetAnimeIds(){
|
||||||
|
return[];
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<string> GetEpisodeAvailableDubLang(){
|
||||||
|
var langList = new List<string>();
|
||||||
|
|
||||||
|
if (Versions != null){
|
||||||
|
langList.AddRange(Versions.Select(version => version.AudioLocale));
|
||||||
|
} else{
|
||||||
|
langList.Add(AudioLocale);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Languages.SortListByLangList(langList);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<string> GetEpisodeAvailableSoftSubs(){
|
||||||
|
return Languages.SortListByLangList(SubtitleLocales);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime GetAvailableDate(){
|
||||||
|
return PremiumAvailableDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SeriesType GetSeriesType(){
|
||||||
|
return SeriesType.Series;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EpisodeType GetEpisodeType(){
|
||||||
|
return EpisodeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Images{
|
public class Images{
|
||||||
[JsonProperty("poster_tall")]
|
[JsonProperty("poster_tall")]
|
||||||
public List<List<Image>>? PosterTall{ get; set; }
|
public List<List<Image>>? PosterTall{ get; set; }
|
||||||
|
|
||||||
|
|
@ -188,14 +305,14 @@ public struct Images{
|
||||||
public List<List<Image>>? Thumbnail{ get; set; }
|
public List<List<Image>>? Thumbnail{ get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Image{
|
public class Image{
|
||||||
public int Height{ get; set; }
|
public int Height{ get; set; }
|
||||||
public string Source{ get; set; }
|
public string Source{ get; set; }
|
||||||
public ImageType Type{ get; set; }
|
public ImageType Type{ get; set; }
|
||||||
public int Width{ get; set; }
|
public int Width{ get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct EpisodeVersion{
|
public class EpisodeVersion{
|
||||||
[JsonProperty("audio_locale")]
|
[JsonProperty("audio_locale")]
|
||||||
public string AudioLocale{ get; set; }
|
public string AudioLocale{ get; set; }
|
||||||
|
|
||||||
|
|
@ -215,11 +332,11 @@ public struct EpisodeVersion{
|
||||||
public string Variant{ get; set; }
|
public string Variant{ get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Link{
|
public class Link{
|
||||||
public string Href{ get; set; }
|
public string Href{ get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Links(){
|
public class Links(){
|
||||||
public Dictionary<string, Link> LinkMappings{ get; set; } = new(){
|
public Dictionary<string, Link> LinkMappings{ get; set; } = new(){
|
||||||
{ "episode/channel", default },
|
{ "episode/channel", default },
|
||||||
{ "episode/next_episode", default },
|
{ "episode/next_episode", default },
|
||||||
|
|
@ -230,7 +347,7 @@ public struct Links(){
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CrunchyEpMeta{
|
public class CrunchyEpMeta{
|
||||||
public List<CrunchyEpMetaData>? Data{ get; set; }
|
public List<CrunchyEpMetaData> Data{ get; set; } =[];
|
||||||
|
|
||||||
public string? SeriesTitle{ get; set; }
|
public string? SeriesTitle{ get; set; }
|
||||||
public string? SeasonTitle{ get; set; }
|
public string? SeasonTitle{ get; set; }
|
||||||
|
|
@ -239,11 +356,11 @@ public class CrunchyEpMeta{
|
||||||
public string? Description{ get; set; }
|
public string? Description{ get; set; }
|
||||||
public string? SeasonId{ get; set; }
|
public string? SeasonId{ get; set; }
|
||||||
public string? Season{ get; set; }
|
public string? Season{ get; set; }
|
||||||
public string? ShowId{ get; set; }
|
public string? SeriesId{ get; set; }
|
||||||
public string? AbsolutEpisodeNumberE{ get; set; }
|
public string? AbsolutEpisodeNumberE{ get; set; }
|
||||||
public string? Image{ get; set; }
|
public string? Image{ get; set; }
|
||||||
public bool Paused{ get; set; }
|
public bool Paused{ get; set; }
|
||||||
public DownloadProgress? DownloadProgress{ get; set; }
|
public DownloadProgress DownloadProgress{ get; set; } = new();
|
||||||
|
|
||||||
public List<string>? SelectedDubs{ get; set; }
|
public List<string>? SelectedDubs{ get; set; }
|
||||||
|
|
||||||
|
|
@ -259,7 +376,7 @@ public class CrunchyEpMeta{
|
||||||
public string Resolution{ get; set; }
|
public string Resolution{ get; set; }
|
||||||
|
|
||||||
public List<string> downloadedFiles{ get; set; } =[];
|
public List<string> downloadedFiles{ get; set; } =[];
|
||||||
|
|
||||||
public bool OnlySubs{ get; set; }
|
public bool OnlySubs{ get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -274,7 +391,7 @@ public class DownloadProgress{
|
||||||
public double DownloadSpeed{ get; set; }
|
public double DownloadSpeed{ get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct CrunchyEpMetaData{
|
public class CrunchyEpMetaData{
|
||||||
public string MediaId{ get; set; }
|
public string MediaId{ get; set; }
|
||||||
public LanguageItem? Lang{ get; set; }
|
public LanguageItem? Lang{ get; set; }
|
||||||
public string? Playback{ get; set; }
|
public string? Playback{ get; set; }
|
||||||
|
|
@ -283,7 +400,7 @@ public struct CrunchyEpMetaData{
|
||||||
public bool IsDubbed{ get; set; }
|
public bool IsDubbed{ get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct CrunchyRollEpisodeData{
|
public class CrunchyRollEpisodeData{
|
||||||
public string Key{ get; set; }
|
public string Key{ get; set; }
|
||||||
public EpisodeAndLanguage EpisodeAndLanguages{ get; set; }
|
public EpisodeAndLanguage EpisodeAndLanguages{ get; set; }
|
||||||
}
|
}
|
||||||
35
CRD/Utils/Structs/Crunchyroll/Music/CrArtist.cs
Normal file
35
CRD/Utils/Structs/Crunchyroll/Music/CrArtist.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace CRD.Utils.Structs.Crunchyroll.Music;
|
||||||
|
|
||||||
|
|
||||||
|
public class CrunchyArtistList{
|
||||||
|
public int Total{ get; set; }
|
||||||
|
public List<CrArtist> Data{ get; set; } =[];
|
||||||
|
public Meta? Meta{ get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CrArtist{
|
||||||
|
[JsonProperty("description")]
|
||||||
|
public string? Description{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("name")]
|
||||||
|
public string? Name{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("slug")]
|
||||||
|
public string? Slug{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("type")]
|
||||||
|
public string? Type{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("id")]
|
||||||
|
public string? Id{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("publishDate")]
|
||||||
|
public DateTime? PublishDate{ get; set; }
|
||||||
|
|
||||||
|
public MusicImages Images{ get; set; } = new();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,132 +1,207 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using CRD.Utils.Structs.History;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace CRD.Utils.Structs.Crunchyroll.Music;
|
namespace CRD.Utils.Structs.Crunchyroll.Music;
|
||||||
|
|
||||||
public struct CrunchyMusicVideoList{
|
public class CrunchyMusicVideoList{
|
||||||
public int Total{ get; set; }
|
public int Total{ get; set; }
|
||||||
public List<CrunchyMusicVideo>? Data{ get; set; }
|
public List<CrunchyMusicVideo> Data{ get; set; } =[];
|
||||||
public Meta Meta{ get; set; }
|
public Meta? Meta{ get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CrunchyMusicVideo{
|
public class CrunchyMusicVideo : IHistorySource{
|
||||||
|
|
||||||
[JsonProperty("copyright")]
|
[JsonProperty("copyright")]
|
||||||
public string? Copyright{ get; set; }
|
public string? Copyright{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("hash")]
|
[JsonProperty("hash")]
|
||||||
public string? Hash{ get; set; }
|
public string? Hash{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("availability")]
|
[JsonProperty("availability")]
|
||||||
public MusicVideoAvailability? Availability{ get; set; }
|
public MusicVideoAvailability? Availability{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("isMature")]
|
[JsonProperty("isMature")]
|
||||||
public bool IsMature{ get; set; }
|
public bool IsMature{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("maturityRatings")]
|
[JsonProperty("maturityRatings")]
|
||||||
public object? MaturityRatings{ get; set; }
|
public object? MaturityRatings{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("title")]
|
[JsonProperty("title")]
|
||||||
public string? Title{ get; set; }
|
public string? Title{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("artists")]
|
[JsonProperty("artists")]
|
||||||
public object? Artists{ get; set; }
|
public object? Artists{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("displayArtistNameRequired")]
|
[JsonProperty("displayArtistNameRequired")]
|
||||||
public bool DisplayArtistNameRequired{ get; set; }
|
public bool DisplayArtistNameRequired{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("streams_link")]
|
[JsonProperty("streams_link")]
|
||||||
public string? StreamsLink{ get; set; }
|
public string? StreamsLink{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("matureBlocked")]
|
[JsonProperty("matureBlocked")]
|
||||||
public bool MatureBlocked{ get; set; }
|
public bool MatureBlocked{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("originalRelease")]
|
[JsonProperty("originalRelease")]
|
||||||
public DateTime OriginalRelease{ get; set; }
|
public DateTime OriginalRelease{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("sequenceNumber")]
|
[JsonProperty("sequenceNumber")]
|
||||||
public int SequenceNumber{ get; set; }
|
public int SequenceNumber{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("type")]
|
[JsonProperty("type")]
|
||||||
public string? Type{ get; set; }
|
public string? Type{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("animeIds")]
|
[JsonProperty("animeIds")]
|
||||||
public List<string>? AnimeIds{ get; set; }
|
public List<string>? AnimeIds{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("description")]
|
[JsonProperty("description")]
|
||||||
public string? Description{ get; set; }
|
public string? Description{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("durationMs")]
|
[JsonProperty("durationMs")]
|
||||||
public int DurationMs{ get; set; }
|
public int DurationMs{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("licensor")]
|
[JsonProperty("licensor")]
|
||||||
public string? Licensor{ get; set; }
|
public string? Licensor{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("slug")]
|
[JsonProperty("slug")]
|
||||||
public string? Slug{ get; set; }
|
public string? Slug{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("artist")]
|
[JsonProperty("artist")]
|
||||||
public MusicVideoArtist? Artist{ get; set; }
|
public required MusicVideoArtist Artist{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("isPremiumOnly")]
|
[JsonProperty("isPremiumOnly")]
|
||||||
public bool IsPremiumOnly{ get; set; }
|
public bool IsPremiumOnly{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("isPublic")]
|
[JsonProperty("isPublic")]
|
||||||
public bool IsPublic{ get; set; }
|
public bool IsPublic{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("publishDate")]
|
[JsonProperty("publishDate")]
|
||||||
public DateTime PublishDate{ get; set; }
|
public DateTime PublishDate{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("displayArtistName")]
|
[JsonProperty("displayArtistName")]
|
||||||
public string? DisplayArtistName{ get; set; }
|
public string? DisplayArtistName{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("genres")]
|
[JsonProperty("genres")]
|
||||||
public object? genres{ get; set; }
|
public object? Genres{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("readyToPublish")]
|
[JsonProperty("readyToPublish")]
|
||||||
public bool ReadyToPublish{ get; set; }
|
public bool ReadyToPublish{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("id")]
|
[JsonProperty("id")]
|
||||||
public string? Id{ get; set; }
|
public required string Id{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("createdAt")]
|
[JsonProperty("createdAt")]
|
||||||
public DateTime CreatedAt{ get; set; }
|
public DateTime CreatedAt{ get; set; }
|
||||||
|
|
||||||
public MusicImages? Images{ get; set; }
|
public MusicImages? Images{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("updatedAt")]
|
[JsonProperty("updatedAt")]
|
||||||
public DateTime UpdatedAt{ get; set; }
|
public DateTime UpdatedAt{ get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public EpisodeType EpisodeType{ get; set; } = EpisodeType.MusicVideo;
|
||||||
|
|
||||||
|
#region Interface
|
||||||
|
|
||||||
|
public string GetSeriesId(){
|
||||||
|
return Artist.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetSeriesTitle(){
|
||||||
|
return Artist.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetSeasonTitle(){
|
||||||
|
return EpisodeType == EpisodeType.MusicVideo ? "Music Videos" : "Concerts";
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetSeasonNum(){
|
||||||
|
return EpisodeType == EpisodeType.MusicVideo ? "1" : "2";
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetSeasonId(){
|
||||||
|
return EpisodeType == EpisodeType.MusicVideo ? "MusicVideos" : "Concerts";
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetEpisodeId(){
|
||||||
|
return Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetEpisodeNumber(){
|
||||||
|
return SequenceNumber + "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetEpisodeTitle(){
|
||||||
|
return Title ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetEpisodeDescription(){
|
||||||
|
return Description ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsSpecialSeason(){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsSpecialEpisode(){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<string> GetAnimeIds(){
|
||||||
|
return AnimeIds ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<string> GetEpisodeAvailableDubLang(){
|
||||||
|
return[];
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<string> GetEpisodeAvailableSoftSubs(){
|
||||||
|
return[];
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime GetAvailableDate(){
|
||||||
|
return PublishDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SeriesType GetSeriesType(){
|
||||||
|
return SeriesType.Artist;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EpisodeType GetEpisodeType(){
|
||||||
|
return EpisodeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct MusicImages{
|
public class MusicImages{
|
||||||
[JsonProperty("poster_tall")]
|
[JsonProperty("poster_tall")]
|
||||||
public List<Image>? PosterTall{ get; set; }
|
public List<Image> PosterTall{ get; set; } =[];
|
||||||
|
|
||||||
[JsonProperty("poster_wide")]
|
[JsonProperty("poster_wide")]
|
||||||
public List<Image>? PosterWide{ get; set; }
|
public List<Image> PosterWide{ get; set; } =[];
|
||||||
|
|
||||||
[JsonProperty("promo_image")]
|
[JsonProperty("promo_image")]
|
||||||
public List<Image>? PromoImage{ get; set; }
|
public List<Image> PromoImage{ get; set; } =[];
|
||||||
|
|
||||||
public List<Image>? Thumbnail{ get; set; }
|
public List<Image> Thumbnail{ get; set; } =[];
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct MusicVideoArtist{
|
public class MusicVideoArtist{
|
||||||
[JsonProperty("id")]
|
[JsonProperty("id")]
|
||||||
public string? Id{ get; set; }
|
public required string Id{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("name")]
|
[JsonProperty("name")]
|
||||||
public string? Name{ get; set; }
|
public required string Name{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("slug")]
|
[JsonProperty("slug")]
|
||||||
public string? Slug{ get; set; }
|
public string? Slug{ get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct MusicVideoAvailability{
|
public class MusicVideoAvailability{
|
||||||
[JsonProperty("endDate")]
|
[JsonProperty("endDate")]
|
||||||
public DateTime EndDate{ get; set; }
|
public DateTime EndDate{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("startDate")]
|
[JsonProperty("startDate")]
|
||||||
public DateTime StartDate{ get; set; }
|
public DateTime StartDate{ get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -9,7 +9,7 @@ public class CrSeriesBase{
|
||||||
public Meta Meta{ get; set; }
|
public Meta Meta{ get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct SeriesBaseItem{
|
public class SeriesBaseItem{
|
||||||
[JsonProperty("extended_maturity_rating")]
|
[JsonProperty("extended_maturity_rating")]
|
||||||
public Dictionary<object, object>
|
public Dictionary<object, object>
|
||||||
ExtendedMaturityRating{ get; set; }
|
ExtendedMaturityRating{ get; set; }
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ public class CrSeriesSearch{
|
||||||
public Meta Meta{ get; set; }
|
public Meta Meta{ get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct SeriesSearchItem{
|
public class SeriesSearchItem{
|
||||||
public string Description{ get; set; }
|
public string Description{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("seo_description")]
|
[JsonProperty("seo_description")]
|
||||||
|
|
@ -91,7 +91,7 @@ public struct SeriesSearchItem{
|
||||||
public string SeoTitle{ get; set; }
|
public string SeoTitle{ get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Version{
|
public class Version{
|
||||||
[JsonProperty("audio_locale")]
|
[JsonProperty("audio_locale")]
|
||||||
public string? AudioLocale{ get; set; }
|
public string? AudioLocale{ get; set; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace CRD.Utils.Structs;
|
namespace CRD.Utils.Structs;
|
||||||
|
|
||||||
public struct AuthData{
|
public class AuthData{
|
||||||
public string Username{ get; set; }
|
public string Username{ get; set; }
|
||||||
public string Password{ get; set; }
|
public string Password{ get; set; }
|
||||||
}
|
}
|
||||||
|
|
@ -18,12 +18,12 @@ public class DrmAuthData{
|
||||||
public string? Token{ get; set; }
|
public string? Token{ get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Meta{
|
public class Meta{
|
||||||
[JsonProperty("versions_considered")]
|
[JsonProperty("versions_considered")]
|
||||||
public bool? VersionsConsidered{ get; set; }
|
public bool? VersionsConsidered{ get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct LanguageItem{
|
public class LanguageItem{
|
||||||
[JsonProperty("cr_locale")]
|
[JsonProperty("cr_locale")]
|
||||||
public string CrLocale{ get; set; }
|
public string CrLocale{ get; set; }
|
||||||
|
|
||||||
|
|
@ -33,12 +33,12 @@ public struct LanguageItem{
|
||||||
public string Language{ get; set; }
|
public string Language{ get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct EpisodeAndLanguage{
|
public class EpisodeAndLanguage{
|
||||||
public List<CrunchyEpisode> Items{ get; set; }
|
public List<CrunchyEpisode> Items{ get; set; }
|
||||||
public List<LanguageItem> Langs{ get; set; }
|
public List<LanguageItem> Langs{ get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct CrunchyMultiDownload(List<string> dubLang, bool? all = null, bool? but = null, List<string>? e = null, string? s = null){
|
public class CrunchyMultiDownload(List<string> dubLang, bool? all = null, bool? but = null, List<string>? e = null, string? s = null){
|
||||||
public List<string> DubLang{ get; set; } = dubLang; //lang code
|
public List<string> DubLang{ get; set; } = dubLang; //lang code
|
||||||
public bool? AllEpisodes{ get; set; } = all; // download all episodes
|
public bool? AllEpisodes{ get; set; } = all; // download all episodes
|
||||||
public bool? But{ get; set; } = but; //download all except selected episodes
|
public bool? But{ get; set; } = but; //download all except selected episodes
|
||||||
|
|
@ -46,12 +46,12 @@ public struct CrunchyMultiDownload(List<string> dubLang, bool? all = null, bool?
|
||||||
public string? S{ get; set; } = s; //season id
|
public string? S{ get; set; } = s; //season id
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct CrunchySeriesList{
|
public class CrunchySeriesList{
|
||||||
public List<Episode> List{ get; set; }
|
public List<Episode> List{ get; set; }
|
||||||
public Dictionary<string, EpisodeAndLanguage> Data{ get; set; }
|
public Dictionary<string, EpisodeAndLanguage> Data{ get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Episode{
|
public class Episode{
|
||||||
public string E{ get; set; }
|
public string E{ get; set; }
|
||||||
public List<string> Lang{ get; set; }
|
public List<string> Lang{ get; set; }
|
||||||
public string Name{ get; set; }
|
public string Name{ get; set; }
|
||||||
|
|
@ -63,9 +63,11 @@ public struct Episode{
|
||||||
public string Img{ get; set; }
|
public string Img{ get; set; }
|
||||||
public string Description{ get; set; }
|
public string Description{ get; set; }
|
||||||
public string Time{ get; set; }
|
public string Time{ get; set; }
|
||||||
|
|
||||||
|
public EpisodeType EpisodeType{ get; set; } = EpisodeType.Unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct DownloadResponse{
|
public class DownloadResponse{
|
||||||
public List<DownloadedMedia> Data{ get; set; }
|
public List<DownloadedMedia> Data{ get; set; }
|
||||||
public string? FileName{ get; set; }
|
public string? FileName{ get; set; }
|
||||||
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.ComponentModel;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CRD.Downloader;
|
using CRD.Downloader;
|
||||||
using CRD.Downloader.Crunchyroll;
|
using CRD.Downloader.Crunchyroll;
|
||||||
|
using CRD.Utils.Files;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace CRD.Utils.Structs.History;
|
namespace CRD.Utils.Structs.History;
|
||||||
|
|
@ -32,12 +33,18 @@ public class HistoryEpisode : INotifyPropertyChanged{
|
||||||
|
|
||||||
[JsonProperty("episode_special_episode")]
|
[JsonProperty("episode_special_episode")]
|
||||||
public bool SpecialEpisode{ get; set; }
|
public bool SpecialEpisode{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("episode_type")]
|
||||||
|
public EpisodeType EpisodeType{ get; set; } = EpisodeType.Unknown;
|
||||||
|
|
||||||
[JsonProperty("sonarr_episode_id")]
|
[JsonProperty("sonarr_episode_id")]
|
||||||
public string? SonarrEpisodeId{ get; set; }
|
public string? SonarrEpisodeId{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("sonarr_has_file")]
|
[JsonProperty("sonarr_has_file")]
|
||||||
public bool SonarrHasFile{ get; set; }
|
public bool SonarrHasFile{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("sonarr_is_monitored")]
|
||||||
|
public bool SonarrIsMonitored{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("sonarr_episode_number")]
|
[JsonProperty("sonarr_episode_number")]
|
||||||
public string? SonarrEpisodeNumber{ get; set; }
|
public string? SonarrEpisodeNumber{ get; set; }
|
||||||
|
|
@ -77,8 +84,22 @@ public class HistoryEpisode : INotifyPropertyChanged{
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DownloadEpisode(bool onlySubs = false){
|
public async Task DownloadEpisode(bool onlySubs = false){
|
||||||
await QueueManager.Instance.CrAddEpisodeToQueue(EpisodeId,
|
switch (EpisodeType){
|
||||||
string.IsNullOrEmpty(CrunchyrollManager.Instance.CrunOptions.HistoryLang) ? CrunchyrollManager.Instance.DefaultLocale : CrunchyrollManager.Instance.CrunOptions.HistoryLang,
|
case EpisodeType.MusicVideo:
|
||||||
CrunchyrollManager.Instance.CrunOptions.DubLang, false, onlySubs);
|
await QueueManager.Instance.CrAddMusicVideoToQueue(EpisodeId ?? string.Empty);
|
||||||
|
break;
|
||||||
|
case EpisodeType.Concert:
|
||||||
|
await QueueManager.Instance.CrAddConcertToQueue(EpisodeId ?? string.Empty);
|
||||||
|
break;
|
||||||
|
case EpisodeType.Episode:
|
||||||
|
case EpisodeType.Unknown:
|
||||||
|
default:
|
||||||
|
await QueueManager.Instance.CrAddEpisodeToQueue(EpisodeId ?? string.Empty,
|
||||||
|
string.IsNullOrEmpty(CrunchyrollManager.Instance.CrunOptions.HistoryLang) ? CrunchyrollManager.Instance.DefaultLocale : CrunchyrollManager.Instance.CrunOptions.HistoryLang,
|
||||||
|
CrunchyrollManager.Instance.CrunOptions.DubLang, false, onlySubs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ using System.Collections.ObjectModel;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using CRD.Utils.Files;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace CRD.Utils.Structs.History;
|
namespace CRD.Utils.Structs.History;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
|
|
@ -8,11 +8,18 @@ using System.Threading.Tasks;
|
||||||
using Avalonia.Media.Imaging;
|
using Avalonia.Media.Imaging;
|
||||||
using CRD.Downloader.Crunchyroll;
|
using CRD.Downloader.Crunchyroll;
|
||||||
using CRD.Utils.CustomList;
|
using CRD.Utils.CustomList;
|
||||||
|
using CRD.Utils.Files;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace CRD.Utils.Structs.History;
|
namespace CRD.Utils.Structs.History;
|
||||||
|
|
||||||
public class HistorySeries : INotifyPropertyChanged{
|
public class HistorySeries : INotifyPropertyChanged{
|
||||||
|
[JsonProperty("series_streaming_service")]
|
||||||
|
public StreamingService SeriesStreamingService{ get; set; } = StreamingService.Unknown;
|
||||||
|
|
||||||
|
[JsonProperty("series_type")]
|
||||||
|
public SeriesType SeriesType{ get; set; } = SeriesType.Unknown;
|
||||||
|
|
||||||
[JsonProperty("series_title")]
|
[JsonProperty("series_title")]
|
||||||
public string? SeriesTitle{ get; set; }
|
public string? SeriesTitle{ get; set; }
|
||||||
|
|
||||||
|
|
@ -96,7 +103,7 @@ public class HistorySeries : INotifyPropertyChanged{
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
private bool Loading = false;
|
private bool Loading = false;
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public StringItem? _selectedVideoQualityItem;
|
public StringItem? _selectedVideoQualityItem;
|
||||||
|
|
||||||
|
|
@ -111,7 +118,6 @@ public class HistorySeries : INotifyPropertyChanged{
|
||||||
if (!Loading){
|
if (!Loading){
|
||||||
CfgManager.UpdateHistoryFile();
|
CfgManager.UpdateHistoryFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -230,16 +236,48 @@ public class HistorySeries : INotifyPropertyChanged{
|
||||||
int count = 0;
|
int count = 0;
|
||||||
bool foundWatched = false;
|
bool foundWatched = false;
|
||||||
var historyAddSpecials = CrunchyrollManager.Instance.CrunOptions.HistoryAddSpecials;
|
var historyAddSpecials = CrunchyrollManager.Instance.CrunOptions.HistoryAddSpecials;
|
||||||
var sonarrEnabled = CrunchyrollManager.Instance.CrunOptions.SonarrProperties != null && CrunchyrollManager.Instance.CrunOptions.SonarrProperties.SonarrEnabled;
|
var sonarrEnabled = SeriesType != SeriesType.Artist && CrunchyrollManager.Instance.CrunOptions.SonarrProperties != null && CrunchyrollManager.Instance.CrunOptions.SonarrProperties.SonarrEnabled &&
|
||||||
|
!string.IsNullOrEmpty(SonarrSeriesId);
|
||||||
|
|
||||||
if (sonarrEnabled && CrunchyrollManager.Instance.CrunOptions.HistoryCountSonarr && !string.IsNullOrEmpty(SonarrSeriesId)){
|
var sonarrSkipUnmonitored = CrunchyrollManager.Instance.CrunOptions.HistorySkipUnmonitored;
|
||||||
|
|
||||||
|
if (sonarrEnabled && CrunchyrollManager.Instance.CrunOptions.HistoryCountSonarr){
|
||||||
for (int i = Seasons.Count - 1; i >= 0; i--){
|
for (int i = Seasons.Count - 1; i >= 0; i--){
|
||||||
var season = Seasons[i];
|
var season = Seasons[i];
|
||||||
|
|
||||||
|
if (season.SpecialSeason == true){
|
||||||
|
if (historyAddSpecials){
|
||||||
|
var episodes = season.EpisodesList;
|
||||||
|
for (int j = episodes.Count - 1; j >= 0; j--){
|
||||||
|
if (sonarrSkipUnmonitored && !episodes[j].SonarrIsMonitored){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(episodes[j].SonarrEpisodeId) && !episodes[j].SonarrHasFile){
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var episodesList = season.EpisodesList;
|
var episodesList = season.EpisodesList;
|
||||||
for (int j = episodesList.Count - 1; j >= 0; j--){
|
for (int j = episodesList.Count - 1; j >= 0; j--){
|
||||||
var episode = episodesList[j];
|
var episode = episodesList[j];
|
||||||
|
|
||||||
|
if (sonarrSkipUnmonitored && !episode.SonarrIsMonitored){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (episode.SpecialEpisode){
|
||||||
|
if (historyAddSpecials && !episode.SonarrHasFile){
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(episode.SonarrEpisodeId) && !episode.SonarrHasFile){
|
if (!string.IsNullOrEmpty(episode.SonarrEpisodeId) && !episode.SonarrHasFile){
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
|
|
@ -253,6 +291,10 @@ public class HistorySeries : INotifyPropertyChanged{
|
||||||
if (historyAddSpecials){
|
if (historyAddSpecials){
|
||||||
var episodes = season.EpisodesList;
|
var episodes = season.EpisodesList;
|
||||||
for (int j = episodes.Count - 1; j >= 0; j--){
|
for (int j = episodes.Count - 1; j >= 0; j--){
|
||||||
|
if (sonarrEnabled && sonarrSkipUnmonitored && !episodes[j].SonarrIsMonitored){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!episodes[j].WasDownloaded){
|
if (!episodes[j].WasDownloaded){
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
|
|
@ -266,6 +308,10 @@ public class HistorySeries : INotifyPropertyChanged{
|
||||||
for (int j = episodesList.Count - 1; j >= 0; j--){
|
for (int j = episodesList.Count - 1; j >= 0; j--){
|
||||||
var episode = episodesList[j];
|
var episode = episodesList[j];
|
||||||
|
|
||||||
|
if (sonarrEnabled && sonarrSkipUnmonitored && !episode.SonarrIsMonitored){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (episode.SpecialEpisode){
|
if (episode.SpecialEpisode){
|
||||||
if (historyAddSpecials && !episode.WasDownloaded){
|
if (historyAddSpecials && !episode.WasDownloaded){
|
||||||
count++;
|
count++;
|
||||||
|
|
@ -303,47 +349,103 @@ public class HistorySeries : INotifyPropertyChanged{
|
||||||
public async Task AddNewMissingToDownloads(){
|
public async Task AddNewMissingToDownloads(){
|
||||||
bool foundWatched = false;
|
bool foundWatched = false;
|
||||||
var historyAddSpecials = CrunchyrollManager.Instance.CrunOptions.HistoryAddSpecials;
|
var historyAddSpecials = CrunchyrollManager.Instance.CrunOptions.HistoryAddSpecials;
|
||||||
|
var sonarrEnabled = SeriesType != SeriesType.Artist && CrunchyrollManager.Instance.CrunOptions.SonarrProperties != null && CrunchyrollManager.Instance.CrunOptions.SonarrProperties.SonarrEnabled &&
|
||||||
|
!string.IsNullOrEmpty(SonarrSeriesId);
|
||||||
|
|
||||||
for (int i = Seasons.Count - 1; i >= 0; i--){
|
var sonarrSkipUnmonitored = CrunchyrollManager.Instance.CrunOptions.HistorySkipUnmonitored;
|
||||||
var season = Seasons[i];
|
|
||||||
|
|
||||||
if (season.SpecialSeason == true){
|
if (sonarrEnabled && CrunchyrollManager.Instance.CrunOptions.HistoryCountSonarr){
|
||||||
if (historyAddSpecials){
|
for (int i = Seasons.Count - 1; i >= 0; i--){
|
||||||
var episodes = season.EpisodesList;
|
var season = Seasons[i];
|
||||||
for (int j = episodes.Count - 1; j >= 0; j--){
|
|
||||||
if (!episodes[j].WasDownloaded){
|
if (season.SpecialSeason == true){
|
||||||
await Seasons[i].EpisodesList[j].DownloadEpisode();
|
if (historyAddSpecials){
|
||||||
|
var episodes = season.EpisodesList;
|
||||||
|
for (int j = episodes.Count - 1; j >= 0; j--){
|
||||||
|
if (sonarrSkipUnmonitored && !episodes[j].SonarrIsMonitored){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(episodes[j].SonarrEpisodeId) && !episodes[j].SonarrHasFile){
|
||||||
|
await Seasons[i].EpisodesList[j].DownloadEpisode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var episodesList = season.EpisodesList;
|
|
||||||
for (int j = episodesList.Count - 1; j >= 0; j--){
|
|
||||||
var episode = episodesList[j];
|
|
||||||
|
|
||||||
if (episode.SpecialEpisode){
|
|
||||||
if (historyAddSpecials && !episode.WasDownloaded){
|
|
||||||
await Seasons[i].EpisodesList[j].DownloadEpisode();
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!episode.WasDownloaded && !foundWatched){
|
var episodesList = season.EpisodesList;
|
||||||
await Seasons[i].EpisodesList[j].DownloadEpisode();
|
for (int j = episodesList.Count - 1; j >= 0; j--){
|
||||||
} else{
|
var episode = episodesList[j];
|
||||||
foundWatched = true;
|
|
||||||
if (!historyAddSpecials){
|
if (sonarrEnabled && sonarrSkipUnmonitored && !episode.SonarrIsMonitored){
|
||||||
break;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (episode.SpecialEpisode){
|
||||||
|
if (historyAddSpecials && !episode.SonarrHasFile){
|
||||||
|
await Seasons[i].EpisodesList[j].DownloadEpisode();
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(episode.SonarrEpisodeId) && !episode.SonarrHasFile){
|
||||||
|
await Seasons[i].EpisodesList[j].DownloadEpisode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else{
|
||||||
|
for (int i = Seasons.Count - 1; i >= 0; i--){
|
||||||
|
var season = Seasons[i];
|
||||||
|
|
||||||
if (foundWatched && !historyAddSpecials){
|
if (season.SpecialSeason == true){
|
||||||
break;
|
if (historyAddSpecials){
|
||||||
|
var episodes = season.EpisodesList;
|
||||||
|
for (int j = episodes.Count - 1; j >= 0; j--){
|
||||||
|
if (sonarrSkipUnmonitored && !episodes[j].SonarrIsMonitored){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!episodes[j].WasDownloaded){
|
||||||
|
await Seasons[i].EpisodesList[j].DownloadEpisode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var episodesList = season.EpisodesList;
|
||||||
|
for (int j = episodesList.Count - 1; j >= 0; j--){
|
||||||
|
var episode = episodesList[j];
|
||||||
|
|
||||||
|
if (sonarrEnabled && sonarrSkipUnmonitored && !episode.SonarrIsMonitored){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (episode.SpecialEpisode){
|
||||||
|
if (historyAddSpecials && !episode.WasDownloaded){
|
||||||
|
await Seasons[i].EpisodesList[j].DownloadEpisode();
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!episode.WasDownloaded && !foundWatched){
|
||||||
|
await Seasons[i].EpisodesList[j].DownloadEpisode();
|
||||||
|
} else{
|
||||||
|
foundWatched = true;
|
||||||
|
if (!historyAddSpecials){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundWatched && !historyAddSpecials){
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -352,13 +454,32 @@ public class HistorySeries : INotifyPropertyChanged{
|
||||||
Console.WriteLine($"Fetching Data for: {SeriesTitle}");
|
Console.WriteLine($"Fetching Data for: {SeriesTitle}");
|
||||||
FetchingData = true;
|
FetchingData = true;
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FetchingData)));
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FetchingData)));
|
||||||
try{
|
|
||||||
await CrunchyrollManager.Instance.History.CRUpdateSeries(SeriesId, seasonId);
|
switch (SeriesType){
|
||||||
} catch (Exception e){
|
case SeriesType.Artist:
|
||||||
Console.Error.WriteLine("Failed to update History series");
|
try{
|
||||||
Console.Error.WriteLine(e);
|
await CrunchyrollManager.Instance.CrMusic.ParseArtistVideosByIdAsync(SeriesId,
|
||||||
|
string.IsNullOrEmpty(CrunchyrollManager.Instance.CrunOptions.HistoryLang) ? CrunchyrollManager.Instance.DefaultLocale : CrunchyrollManager.Instance.CrunOptions.HistoryLang, true, true);
|
||||||
|
} catch (Exception e){
|
||||||
|
Console.Error.WriteLine("Failed to update History artist");
|
||||||
|
Console.Error.WriteLine(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case SeriesType.Series:
|
||||||
|
case SeriesType.Unknown:
|
||||||
|
default:
|
||||||
|
try{
|
||||||
|
await CrunchyrollManager.Instance.History.CrUpdateSeries(SeriesId, seasonId);
|
||||||
|
} catch (Exception e){
|
||||||
|
Console.Error.WriteLine("Failed to update History series");
|
||||||
|
Console.Error.WriteLine(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SeriesTitle)));
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SeriesTitle)));
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SeriesDescription)));
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SeriesDescription)));
|
||||||
UpdateNewEpisodes();
|
UpdateNewEpisodes();
|
||||||
|
|
@ -384,6 +505,15 @@ public class HistorySeries : INotifyPropertyChanged{
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OpenCrPage(){
|
public void OpenCrPage(){
|
||||||
Helpers.OpenUrl($"https://www.crunchyroll.com/series/{SeriesId}");
|
switch (SeriesType){
|
||||||
|
case SeriesType.Artist:
|
||||||
|
Helpers.OpenUrl($"https://www.crunchyroll.com/artist/{SeriesId}");
|
||||||
|
break;
|
||||||
|
case SeriesType.Series:
|
||||||
|
case SeriesType.Unknown:
|
||||||
|
default:
|
||||||
|
Helpers.OpenUrl($"https://www.crunchyroll.com/series/{SeriesId}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
30
CRD/Utils/Structs/History/IHistorySource.cs
Normal file
30
CRD/Utils/Structs/History/IHistorySource.cs
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace CRD.Utils.Structs.History;
|
||||||
|
|
||||||
|
public interface IHistorySource{
|
||||||
|
string GetSeriesId();
|
||||||
|
string GetSeriesTitle();
|
||||||
|
string GetSeasonTitle();
|
||||||
|
string GetSeasonNum();
|
||||||
|
string GetSeasonId();
|
||||||
|
|
||||||
|
string GetEpisodeId();
|
||||||
|
string GetEpisodeNumber();
|
||||||
|
string GetEpisodeTitle();
|
||||||
|
string GetEpisodeDescription();
|
||||||
|
|
||||||
|
bool IsSpecialSeason();
|
||||||
|
bool IsSpecialEpisode();
|
||||||
|
|
||||||
|
List<string> GetAnimeIds();
|
||||||
|
|
||||||
|
List<string> GetEpisodeAvailableDubLang();
|
||||||
|
List<string> GetEpisodeAvailableSoftSubs();
|
||||||
|
|
||||||
|
DateTime GetAvailableDate();
|
||||||
|
|
||||||
|
SeriesType GetSeriesType();
|
||||||
|
EpisodeType GetEpisodeType();
|
||||||
|
}
|
||||||
17
CRD/Utils/Structs/History/SeriesDataCache.cs
Normal file
17
CRD/Utils/Structs/History/SeriesDataCache.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace CRD.Utils.Structs.History;
|
||||||
|
|
||||||
|
public class SeriesDataCache{
|
||||||
|
|
||||||
|
public string SeriesId{ get; set; } = "";
|
||||||
|
|
||||||
|
public string SeriesTitle{ get; set; } = "";
|
||||||
|
|
||||||
|
public string SeriesDescription{ get; set; } = "";
|
||||||
|
public string ThumbnailImageUrl{ get; set; } = "";
|
||||||
|
|
||||||
|
public List<string> HistorySeriesAvailableDubLang{ get; set; } =[];
|
||||||
|
|
||||||
|
public List<string> HistorySeriesAvailableSoftSubs{ get; set; } =[];
|
||||||
|
}
|
||||||
|
|
@ -54,7 +54,7 @@ public class Languages{
|
||||||
else if (yExists)
|
else if (yExists)
|
||||||
return 1; // y comes before any missing value
|
return 1; // y comes before any missing value
|
||||||
else
|
else
|
||||||
return string.Compare(x, y); // Sort alphabetically or by another logic for missing values
|
return string.CompareOrdinal(x, y); // Sort alphabetically or by another logic for missing values
|
||||||
});
|
});
|
||||||
|
|
||||||
return langList;
|
return langList;
|
||||||
|
|
@ -116,8 +116,8 @@ public class Languages{
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LanguageItem FindLang(string crLocale){
|
public static LanguageItem FindLang(string crLocale){
|
||||||
LanguageItem lang = languages.FirstOrDefault(l => l.CrLocale == crLocale);
|
LanguageItem? lang = languages.FirstOrDefault(l => l.CrLocale == crLocale);
|
||||||
if (lang.CrLocale != null){
|
if (lang?.CrLocale != null){
|
||||||
return lang;
|
return lang;
|
||||||
} else{
|
} else{
|
||||||
return new LanguageItem{
|
return new LanguageItem{
|
||||||
|
|
@ -159,7 +159,7 @@ public class Languages{
|
||||||
var property = typeof(T).GetProperty(sortKey, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
|
var property = typeof(T).GetProperty(sortKey, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
|
||||||
if (property == null) throw new ArgumentException($"Property '{sortKey}' not found on type '{typeof(T).Name}'.");
|
if (property == null) throw new ArgumentException($"Property '{sortKey}' not found on type '{typeof(T).Name}'.");
|
||||||
|
|
||||||
var value = property.GetValue(item) as string;
|
var value = property.GetValue(item) as string ?? string.Empty;
|
||||||
int index = idx.ContainsKey(value) ? idx[value] : 50;
|
int index = idx.ContainsKey(value) ? idx[value] : 50;
|
||||||
return index;
|
return index;
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
|
||||||
26
CRD/Utils/UI/UiListHasElementsConverter.cs
Normal file
26
CRD/Utils/UI/UiListHasElementsConverter.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Globalization;
|
||||||
|
using Avalonia.Data.Converters;
|
||||||
|
|
||||||
|
namespace CRD.Utils.UI;
|
||||||
|
|
||||||
|
public class UiListHasElementsConverter : IValueConverter{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture){
|
||||||
|
if (value is IEnumerable enumerable){
|
||||||
|
// Check if the collection has any elements
|
||||||
|
foreach (var _ in enumerable){
|
||||||
|
return true; // At least one element exists
|
||||||
|
}
|
||||||
|
|
||||||
|
return false; // No elements
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return false if the input is not a collection or is null
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){
|
||||||
|
throw new NotSupportedException("ListToBooleanConverter does not support ConvertBack.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,7 @@ using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using CRD.Utils.Files;
|
||||||
|
|
||||||
namespace CRD.Utils.Updater;
|
namespace CRD.Utils.Updater;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,19 +29,19 @@ public partial class AccountPageViewModel : ViewModelBase{
|
||||||
private static DispatcherTimer? _timer;
|
private static DispatcherTimer? _timer;
|
||||||
private DateTime _targetTime;
|
private DateTime _targetTime;
|
||||||
|
|
||||||
private bool IsCancelled = false;
|
private bool IsCancelled;
|
||||||
private bool UnknownEndDate = false;
|
private bool UnknownEndDate;
|
||||||
private bool EndedButMaybeActive = false;
|
private bool EndedButMaybeActive;
|
||||||
|
|
||||||
public AccountPageViewModel(){
|
public AccountPageViewModel(){
|
||||||
UpdatetProfile();
|
UpdatetProfile();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Timer_Tick(object sender, EventArgs e){
|
private void Timer_Tick(object? sender, EventArgs e){
|
||||||
var remaining = _targetTime - DateTime.Now;
|
var remaining = _targetTime - DateTime.Now;
|
||||||
if (remaining <= TimeSpan.Zero){
|
if (remaining <= TimeSpan.Zero){
|
||||||
RemainingTime = "No active Subscription";
|
RemainingTime = "No active Subscription";
|
||||||
_timer.Stop();
|
_timer?.Stop();
|
||||||
if (UnknownEndDate){
|
if (UnknownEndDate){
|
||||||
RemainingTime = "Unknown Subscription end date";
|
RemainingTime = "Unknown Subscription end date";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,28 +32,28 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
||||||
private string _buttonTextSelectSeason = "Select Season";
|
private string _buttonTextSelectSeason = "Select Season";
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _addAllEpisodes = false;
|
private bool _addAllEpisodes;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _buttonEnabled = false;
|
private bool _buttonEnabled;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _allButtonEnabled = false;
|
private bool _allButtonEnabled;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _showLoading = false;
|
private bool _showLoading;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _searchEnabled = false;
|
private bool _searchEnabled;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _searchVisible = true;
|
private bool _searchVisible = true;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _slectSeasonVisible = false;
|
private bool _slectSeasonVisible;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _searchPopupVisible = false;
|
private bool _searchPopupVisible;
|
||||||
|
|
||||||
public ObservableCollection<ItemModel> Items{ get; set; } = new();
|
public ObservableCollection<ItemModel> Items{ get; set; } = new();
|
||||||
public ObservableCollection<CrBrowseSeries> SearchItems{ get; set; } = new();
|
public ObservableCollection<CrBrowseSeries> SearchItems{ get; set; } = new();
|
||||||
|
|
@ -69,13 +69,13 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
||||||
|
|
||||||
private Dictionary<string, List<ItemModel>> episodesBySeason = new();
|
private Dictionary<string, List<ItemModel>> episodesBySeason = new();
|
||||||
|
|
||||||
private List<string> selectedEpisodes = new();
|
private List<ItemModel> selectedEpisodes = new();
|
||||||
|
|
||||||
private CrunchySeriesList? currentSeriesList;
|
private CrunchySeriesList? currentSeriesList;
|
||||||
|
|
||||||
private CrunchyMusicVideoList? currentMusicVideoList;
|
private CrunchyMusicVideoList? currentMusicVideoList;
|
||||||
|
|
||||||
private bool CurrentSeasonFullySelected = false;
|
private bool CurrentSeasonFullySelected;
|
||||||
|
|
||||||
public AddDownloadPageViewModel(){
|
public AddDownloadPageViewModel(){
|
||||||
SelectedItems.CollectionChanged += OnSelectedItemsChanged;
|
SelectedItems.CollectionChanged += OnSelectedItemsChanged;
|
||||||
|
|
@ -98,9 +98,9 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
||||||
if (episode.ImageBitmap == null){
|
if (episode.ImageBitmap == null){
|
||||||
if (episode.Images.PosterTall != null){
|
if (episode.Images.PosterTall != null){
|
||||||
var posterTall = episode.Images.PosterTall.First();
|
var posterTall = episode.Images.PosterTall.First();
|
||||||
var imageUrl = posterTall.Find(ele => ele.Height == 180).Source
|
var imageUrl = posterTall.Find(ele => ele.Height == 180)?.Source
|
||||||
?? (posterTall.Count >= 2 ? posterTall[1].Source : posterTall.FirstOrDefault().Source);
|
?? (posterTall.Count >= 2 ? posterTall[1].Source : posterTall.FirstOrDefault()?.Source);
|
||||||
episode.LoadImage(imageUrl);
|
episode.LoadImage(imageUrl ?? string.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,14 +171,16 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
||||||
#region OnButtonPress
|
#region OnButtonPress
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
public async void OnButtonPress(){
|
public async Task OnButtonPress(){
|
||||||
if (HasSelectedItemsOrEpisodes()){
|
if (HasSelectedItemsOrEpisodes()){
|
||||||
Console.WriteLine("Added to Queue");
|
Console.WriteLine("Added to Queue");
|
||||||
|
|
||||||
if (currentMusicVideoList != null){
|
if (currentMusicVideoList != null){
|
||||||
AddSelectedMusicVideosToQueue();
|
AddSelectedMusicVideosToQueue();
|
||||||
} else{
|
}
|
||||||
AddSelectedEpisodesToQueue();
|
|
||||||
|
if (currentSeriesList != null){
|
||||||
|
await AddSelectedEpisodesToQueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
ResetState();
|
ResetState();
|
||||||
|
|
@ -194,10 +196,12 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddSelectedMusicVideosToQueue(){
|
private void AddSelectedMusicVideosToQueue(){
|
||||||
if (SelectedItems.Count > 0){
|
AddItemsToSelectedEpisodes();
|
||||||
|
|
||||||
|
if (selectedEpisodes.Count > 0){
|
||||||
var musicClass = CrunchyrollManager.Instance.CrMusic;
|
var musicClass = CrunchyrollManager.Instance.CrMusic;
|
||||||
foreach (var selectedItem in SelectedItems){
|
foreach (var selectedItem in selectedEpisodes){
|
||||||
var music = currentMusicVideoList.Value.Data?.FirstOrDefault(ele => ele.Id == selectedItem.Id);
|
var music = currentMusicVideoList?.Data?.FirstOrDefault(ele => ele.Id == selectedItem.Id);
|
||||||
|
|
||||||
if (music != null){
|
if (music != null){
|
||||||
var meta = musicClass.EpisodeMeta(music);
|
var meta = musicClass.EpisodeMeta(music);
|
||||||
|
|
@ -207,24 +211,24 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void AddSelectedEpisodesToQueue(){
|
private async Task AddSelectedEpisodesToQueue(){
|
||||||
AddItemsToSelectedEpisodes();
|
AddItemsToSelectedEpisodes();
|
||||||
|
|
||||||
if (currentSeriesList != null){
|
if (currentSeriesList != null){
|
||||||
await QueueManager.Instance.CrAddSeriesToQueue(
|
await QueueManager.Instance.CrAddSeriesToQueue(
|
||||||
currentSeriesList.Value,
|
currentSeriesList,
|
||||||
new CrunchyMultiDownload(
|
new CrunchyMultiDownload(
|
||||||
CrunchyrollManager.Instance.CrunOptions.DubLang,
|
CrunchyrollManager.Instance.CrunOptions.DubLang,
|
||||||
AddAllEpisodes,
|
AddAllEpisodes,
|
||||||
false,
|
false,
|
||||||
selectedEpisodes));
|
selectedEpisodes.Select(selectedEpisode => selectedEpisode.AbsolutNum).ToList()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddItemsToSelectedEpisodes(){
|
private void AddItemsToSelectedEpisodes(){
|
||||||
foreach (var selectedItem in SelectedItems){
|
foreach (var selectedItem in SelectedItems){
|
||||||
if (!selectedEpisodes.Contains(selectedItem.AbsolutNum)){
|
if (!selectedEpisodes.Contains(selectedItem)){
|
||||||
selectedEpisodes.Add(selectedItem.AbsolutNum);
|
selectedEpisodes.Add(selectedItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -256,7 +260,7 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
||||||
|
|
||||||
var matchResult = ExtractLocaleAndIdFromUrl();
|
var matchResult = ExtractLocaleAndIdFromUrl();
|
||||||
|
|
||||||
if (matchResult is (string locale, string id)){
|
if (matchResult is ({ } locale, { } id)){
|
||||||
switch (GetUrlType()){
|
switch (GetUrlType()){
|
||||||
case CrunchyUrlType.Artist:
|
case CrunchyUrlType.Artist:
|
||||||
await HandleArtistUrlAsync(locale, id);
|
await HandleArtistUrlAsync(locale, id);
|
||||||
|
|
@ -299,8 +303,8 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
||||||
private async Task HandleArtistUrlAsync(string locale, string id){
|
private async Task HandleArtistUrlAsync(string locale, string id){
|
||||||
SetLoadingState(true);
|
SetLoadingState(true);
|
||||||
|
|
||||||
var list = await CrunchyrollManager.Instance.CrMusic.ParseArtistMusicVideosByIdAsync(
|
var list = await CrunchyrollManager.Instance.CrMusic.ParseArtistVideosByIdAsync(
|
||||||
id, DetermineLocale(locale), true);
|
id, DetermineLocale(locale), true, true);
|
||||||
|
|
||||||
SetLoadingState(false);
|
SetLoadingState(false);
|
||||||
|
|
||||||
|
|
@ -335,6 +339,16 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
||||||
id, DetermineLocale(locale),
|
id, DetermineLocale(locale),
|
||||||
new CrunchyMultiDownload(CrunchyrollManager.Instance.CrunOptions.DubLang, true), true);
|
new CrunchyMultiDownload(CrunchyrollManager.Instance.CrunOptions.DubLang, true), true);
|
||||||
|
|
||||||
|
if (CrunchyrollManager.Instance.CrunOptions.SearchFetchFeaturedMusic){
|
||||||
|
var musicList = await CrunchyrollManager.Instance.CrMusic.ParseFeaturedMusicVideoByIdAsync(id, DetermineLocale(locale), true);
|
||||||
|
|
||||||
|
if (musicList != null){
|
||||||
|
currentMusicVideoList = musicList;
|
||||||
|
PopulateItemsFromMusicVideoList();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
SetLoadingState(false);
|
SetLoadingState(false);
|
||||||
|
|
||||||
if (list != null){
|
if (list != null){
|
||||||
|
|
@ -348,16 +362,37 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
||||||
|
|
||||||
private void PopulateItemsFromMusicVideoList(){
|
private void PopulateItemsFromMusicVideoList(){
|
||||||
if (currentMusicVideoList?.Data != null){
|
if (currentMusicVideoList?.Data != null){
|
||||||
foreach (var episode in currentMusicVideoList.Value.Data){
|
foreach (var episode in currentMusicVideoList.Data){
|
||||||
var imageUrl = episode.Images?.Thumbnail?.FirstOrDefault().Source ?? "";
|
string seasonKey;
|
||||||
|
switch (episode.EpisodeType){
|
||||||
|
case EpisodeType.MusicVideo:
|
||||||
|
seasonKey = "Music Videos ";
|
||||||
|
break;
|
||||||
|
case EpisodeType.Concert:
|
||||||
|
seasonKey = "Concerts ";
|
||||||
|
break;
|
||||||
|
case EpisodeType.Episode:
|
||||||
|
case EpisodeType.Unknown:
|
||||||
|
default:
|
||||||
|
seasonKey = "Unknown ";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var imageUrl = episode.Images?.Thumbnail.FirstOrDefault()?.Source ?? "";
|
||||||
var time = $"{(episode.DurationMs / 1000) / 60}:{(episode.DurationMs / 1000) % 60:D2}";
|
var time = $"{(episode.DurationMs / 1000) / 60}:{(episode.DurationMs / 1000) % 60:D2}";
|
||||||
|
|
||||||
var newItem = new ItemModel(episode.Id ?? "", imageUrl, episode.Description ?? "", time, episode.Title ?? "", "",
|
var newItem = new ItemModel(episode.Id, imageUrl, episode.Description ?? "", time, episode.Title ?? "", seasonKey,
|
||||||
episode.SequenceNumber.ToString(), episode.SequenceNumber.ToString(), new List<string>());
|
episode.SequenceNumber.ToString(), episode.Id, new List<string>(), episode.EpisodeType);
|
||||||
|
|
||||||
newItem.LoadImage(imageUrl);
|
if (!episodesBySeason.ContainsKey(seasonKey)){
|
||||||
Items.Add(newItem);
|
episodesBySeason[seasonKey] = new List<ItemModel>{ newItem };
|
||||||
|
SeasonList.Add(new ComboBoxItem{ Content = seasonKey });
|
||||||
|
} else{
|
||||||
|
episodesBySeason[seasonKey].Add(newItem);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CurrentSelectedSeason = SeasonList.First();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -367,7 +402,7 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
||||||
var itemModel = new ItemModel(
|
var itemModel = new ItemModel(
|
||||||
episode.Id, episode.Img, episode.Description, episode.Time, episode.Name, seasonKey,
|
episode.Id, episode.Img, episode.Description, episode.Time, episode.Name, seasonKey,
|
||||||
episode.EpisodeNum.StartsWith("SP") ? episode.EpisodeNum : "E" + episode.EpisodeNum,
|
episode.EpisodeNum.StartsWith("SP") ? episode.EpisodeNum : "E" + episode.EpisodeNum,
|
||||||
episode.E, episode.Lang);
|
episode.E, episode.Lang, episode.EpisodeType);
|
||||||
|
|
||||||
if (!episodesBySeason.ContainsKey(seasonKey)){
|
if (!episodesBySeason.ContainsKey(seasonKey)){
|
||||||
episodesBySeason[seasonKey] = new List<ItemModel>{ itemModel };
|
episodesBySeason[seasonKey] = new List<ItemModel>{ itemModel };
|
||||||
|
|
@ -407,7 +442,7 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
||||||
public void OnSelectSeasonPressed(){
|
public void OnSelectSeasonPressed(){
|
||||||
if (CurrentSeasonFullySelected){
|
if (CurrentSeasonFullySelected){
|
||||||
foreach (var item in Items){
|
foreach (var item in Items){
|
||||||
selectedEpisodes.Remove(item.AbsolutNum);
|
selectedEpisodes.Remove(item);
|
||||||
SelectedItems.Remove(item);
|
SelectedItems.Remove(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -426,10 +461,9 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnCurrentSelectedSeasonChanging(ComboBoxItem? oldValue, ComboBoxItem newValue){
|
partial void OnCurrentSelectedSeasonChanging(ComboBoxItem? oldValue, ComboBoxItem newValue){
|
||||||
if (SelectedItems == null) return;
|
|
||||||
foreach (var selectedItem in SelectedItems){
|
foreach (var selectedItem in SelectedItems){
|
||||||
if (!selectedEpisodes.Contains(selectedItem.AbsolutNum)){
|
if (!selectedEpisodes.Contains(selectedItem)){
|
||||||
selectedEpisodes.Add(selectedItem.AbsolutNum);
|
selectedEpisodes.Add(selectedItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -443,7 +477,6 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSelectedItemsChanged(object? sender, NotifyCollectionChangedEventArgs e){
|
private void OnSelectedItemsChanged(object? sender, NotifyCollectionChangedEventArgs e){
|
||||||
if (Items == null) return;
|
|
||||||
|
|
||||||
CurrentSeasonFullySelected = Items.All(item => SelectedItems.Contains(item));
|
CurrentSeasonFullySelected = Items.All(item => SelectedItems.Contains(item));
|
||||||
|
|
||||||
|
|
@ -486,7 +519,7 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
||||||
|
|
||||||
if (list != null){
|
if (list != null){
|
||||||
currentSeriesList = list;
|
currentSeriesList = list;
|
||||||
SearchPopulateEpisodesBySeason();
|
await SearchPopulateEpisodesBySeason(value.Id);
|
||||||
UpdateUiForEpisodeSelection();
|
UpdateUiForEpisodeSelection();
|
||||||
} else{
|
} else{
|
||||||
ButtonEnabled = true;
|
ButtonEnabled = true;
|
||||||
|
|
@ -514,7 +547,7 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
||||||
new CrunchyMultiDownload(CrunchyrollManager.Instance.CrunOptions.DubLang, true), true);
|
new CrunchyMultiDownload(CrunchyrollManager.Instance.CrunOptions.DubLang, true), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SearchPopulateEpisodesBySeason(){
|
private async Task SearchPopulateEpisodesBySeason(string seriesId){
|
||||||
if (currentSeriesList?.List == null){
|
if (currentSeriesList?.List == null){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -528,8 +561,7 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
||||||
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
|
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
|
||||||
GC.Collect();
|
GC.Collect();
|
||||||
|
|
||||||
|
foreach (var episode in currentSeriesList.List){
|
||||||
foreach (var episode in currentSeriesList.Value.List){
|
|
||||||
var seasonKey = "S" + episode.Season;
|
var seasonKey = "S" + episode.Season;
|
||||||
var episodeModel = new ItemModel(
|
var episodeModel = new ItemModel(
|
||||||
episode.Id,
|
episode.Id,
|
||||||
|
|
@ -540,7 +572,7 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
||||||
seasonKey,
|
seasonKey,
|
||||||
episode.EpisodeNum.StartsWith("SP") ? episode.EpisodeNum : "E" + episode.EpisodeNum,
|
episode.EpisodeNum.StartsWith("SP") ? episode.EpisodeNum : "E" + episode.EpisodeNum,
|
||||||
episode.E,
|
episode.E,
|
||||||
episode.Lang);
|
episode.Lang, episode.EpisodeType);
|
||||||
|
|
||||||
if (!episodesBySeason.ContainsKey(seasonKey)){
|
if (!episodesBySeason.ContainsKey(seasonKey)){
|
||||||
episodesBySeason[seasonKey] = new List<ItemModel>{ episodeModel };
|
episodesBySeason[seasonKey] = new List<ItemModel>{ episodeModel };
|
||||||
|
|
@ -549,6 +581,19 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
||||||
episodesBySeason[seasonKey].Add(episodeModel);
|
episodesBySeason[seasonKey].Add(episodeModel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (CrunchyrollManager.Instance.CrunOptions.SearchFetchFeaturedMusic){
|
||||||
|
var locale = string.IsNullOrEmpty(CrunchyrollManager.Instance.CrunOptions.HistoryLang)
|
||||||
|
? CrunchyrollManager.Instance.DefaultLocale
|
||||||
|
: CrunchyrollManager.Instance.CrunOptions.HistoryLang;
|
||||||
|
var musicList = await CrunchyrollManager.Instance.CrMusic.ParseFeaturedMusicVideoByIdAsync(seriesId, DetermineLocale(locale), true);
|
||||||
|
|
||||||
|
if (musicList != null){
|
||||||
|
currentMusicVideoList = musicList;
|
||||||
|
PopulateItemsFromMusicVideoList();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
CurrentSelectedSeason = SeasonList.First();
|
CurrentSelectedSeason = SeasonList.First();
|
||||||
}
|
}
|
||||||
|
|
@ -575,12 +620,12 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
||||||
if (episode.ImageBitmap == null){
|
if (episode.ImageBitmap == null){
|
||||||
episode.LoadImage(episode.ImageUrl);
|
episode.LoadImage(episode.ImageUrl);
|
||||||
Items.Add(episode);
|
Items.Add(episode);
|
||||||
if (selectedEpisodes.Contains(episode.AbsolutNum)){
|
if (selectedEpisodes.Contains(episode)){
|
||||||
SelectedItems.Add(episode);
|
SelectedItems.Add(episode);
|
||||||
}
|
}
|
||||||
} else{
|
} else{
|
||||||
Items.Add(episode);
|
Items.Add(episode);
|
||||||
if (selectedEpisodes.Contains(episode.AbsolutNum)){
|
if (selectedEpisodes.Contains(episode)){
|
||||||
SelectedItems.Add(episode);
|
SelectedItems.Add(episode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -604,21 +649,16 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
||||||
|
|
||||||
// Clear collections and other managed resources
|
// Clear collections and other managed resources
|
||||||
Items.Clear();
|
Items.Clear();
|
||||||
Items = null;
|
|
||||||
SearchItems.Clear();
|
SearchItems.Clear();
|
||||||
SearchItems = null;
|
|
||||||
SelectedItems.Clear();
|
SelectedItems.Clear();
|
||||||
SelectedItems = null;
|
|
||||||
SeasonList.Clear();
|
SeasonList.Clear();
|
||||||
SeasonList = null;
|
|
||||||
episodesBySeason.Clear();
|
episodesBySeason.Clear();
|
||||||
episodesBySeason = null;
|
|
||||||
selectedEpisodes.Clear();
|
selectedEpisodes.Clear();
|
||||||
selectedEpisodes = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ItemModel(string id, string imageUrl, string description, string time, string title, string season, string episode, string absolutNum, List<string> availableAudios) : INotifyPropertyChanged{
|
public class ItemModel(string id, string imageUrl, string description, string time, string title, string season, string episode, string absolutNum, List<string> availableAudios, EpisodeType epType)
|
||||||
|
: INotifyPropertyChanged{
|
||||||
public string Id{ get; set; } = id;
|
public string Id{ get; set; } = id;
|
||||||
public string ImageUrl{ get; set; } = imageUrl;
|
public string ImageUrl{ get; set; } = imageUrl;
|
||||||
public Bitmap? ImageBitmap{ get; set; }
|
public Bitmap? ImageBitmap{ get; set; }
|
||||||
|
|
@ -633,6 +673,9 @@ public class ItemModel(string id, string imageUrl, string description, string ti
|
||||||
public string TitleFull{ get; set; } = season + episode + " - " + title;
|
public string TitleFull{ get; set; } = season + episode + " - " + title;
|
||||||
|
|
||||||
public List<string> AvailableAudios{ get; set; } = availableAudios;
|
public List<string> AvailableAudios{ get; set; } = availableAudios;
|
||||||
|
public EpisodeType EpisodeType{ get; set; } = epType;
|
||||||
|
|
||||||
|
public bool HasDubs{ get; set; } = availableAudios.Count != 0;
|
||||||
|
|
||||||
public event PropertyChangedEventHandler? PropertyChanged;
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,15 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using CRD.Downloader;
|
using CRD.Downloader;
|
||||||
using CRD.Downloader.Crunchyroll;
|
using CRD.Downloader.Crunchyroll;
|
||||||
using CRD.Utils;
|
using CRD.Utils;
|
||||||
|
using CRD.Utils.Files;
|
||||||
using CRD.Utils.Structs;
|
using CRD.Utils.Structs;
|
||||||
using CRD.Utils.Structs.History;
|
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
using DynamicData.Kernel;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace CRD.ViewModels;
|
namespace CRD.ViewModels;
|
||||||
|
|
||||||
|
|
@ -70,9 +64,10 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
||||||
|
|
||||||
private CalendarWeek? currentWeek;
|
private CalendarWeek? currentWeek;
|
||||||
|
|
||||||
private bool loading = true;
|
private bool loading;
|
||||||
|
|
||||||
public CalendarPageViewModel(){
|
public CalendarPageViewModel(){
|
||||||
|
loading = true;
|
||||||
CalendarDays = new ObservableCollection<CalendarDay>();
|
CalendarDays = new ObservableCollection<CalendarDay>();
|
||||||
|
|
||||||
foreach (var languageItem in Languages.languages){
|
foreach (var languageItem in Languages.languages){
|
||||||
|
|
@ -138,7 +133,7 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
||||||
|
|
||||||
currentWeek = week;
|
currentWeek = week;
|
||||||
CalendarDays.Clear();
|
CalendarDays.Clear();
|
||||||
CalendarDays.AddRange(week.CalendarDays);
|
if (week.CalendarDays != null) CalendarDays.AddRange(week.CalendarDays);
|
||||||
RaisePropertyChanged(nameof(CalendarDays));
|
RaisePropertyChanged(nameof(CalendarDays));
|
||||||
ShowLoading = false;
|
ShowLoading = false;
|
||||||
if (CustomCalendar){
|
if (CustomCalendar){
|
||||||
|
|
@ -146,9 +141,9 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
||||||
foreach (var calendarDayCalendarEpisode in calendarDay.CalendarEpisodes){
|
foreach (var calendarDayCalendarEpisode in calendarDay.CalendarEpisodes){
|
||||||
if (calendarDayCalendarEpisode.ImageBitmap == null){
|
if (calendarDayCalendarEpisode.ImageBitmap == null){
|
||||||
if (calendarDayCalendarEpisode.AnilistEpisode){
|
if (calendarDayCalendarEpisode.AnilistEpisode){
|
||||||
calendarDayCalendarEpisode.LoadImage(100,150);
|
_ = calendarDayCalendarEpisode.LoadImage(100,150);
|
||||||
} else{
|
} else{
|
||||||
calendarDayCalendarEpisode.LoadImage();
|
_ = calendarDayCalendarEpisode.LoadImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -165,9 +160,9 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
||||||
|
|
||||||
if (calendarDayCalendarEpisode.ImageBitmap == null){
|
if (calendarDayCalendarEpisode.ImageBitmap == null){
|
||||||
if (calendarDayCalendarEpisode.AnilistEpisode){
|
if (calendarDayCalendarEpisode.AnilistEpisode){
|
||||||
calendarDayCalendarEpisode.LoadImage(100,150);
|
_ = calendarDayCalendarEpisode.LoadImage(100,150);
|
||||||
} else{
|
} else{
|
||||||
calendarDayCalendarEpisode.LoadImage();
|
_ = calendarDayCalendarEpisode.LoadImage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -263,7 +258,7 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
||||||
if (value?.Content != null){
|
if (value?.Content != null){
|
||||||
CrunchyrollManager.Instance.CrunOptions.SelectedCalendarLanguage = value.Content.ToString();
|
CrunchyrollManager.Instance.CrunOptions.SelectedCalendarLanguage = value.Content.ToString();
|
||||||
Refresh();
|
Refresh();
|
||||||
CfgManager.WriteSettingsToFile();
|
CfgManager.WriteCrSettings();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -276,7 +271,7 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
||||||
|
|
||||||
LoadCalendar(GetThisWeeksMondayDate(),DateTime.Now, true);
|
LoadCalendar(GetThisWeeksMondayDate(),DateTime.Now, true);
|
||||||
|
|
||||||
CfgManager.WriteSettingsToFile();
|
CfgManager.WriteCrSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnHideDubsChanged(bool value){
|
partial void OnHideDubsChanged(bool value){
|
||||||
|
|
@ -285,7 +280,7 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
||||||
}
|
}
|
||||||
|
|
||||||
CrunchyrollManager.Instance.CrunOptions.CalendarHideDubs = value;
|
CrunchyrollManager.Instance.CrunOptions.CalendarHideDubs = value;
|
||||||
CfgManager.WriteSettingsToFile();
|
CfgManager.WriteCrSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnFilterByAirDateChanged(bool value){
|
partial void OnFilterByAirDateChanged(bool value){
|
||||||
|
|
@ -294,7 +289,7 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
||||||
}
|
}
|
||||||
|
|
||||||
CrunchyrollManager.Instance.CrunOptions.CalendarFilterByAirDate = value;
|
CrunchyrollManager.Instance.CrunOptions.CalendarFilterByAirDate = value;
|
||||||
CfgManager.WriteSettingsToFile();
|
CfgManager.WriteCrSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnShowUpcomingEpisodesChanged(bool value){
|
partial void OnShowUpcomingEpisodesChanged(bool value){
|
||||||
|
|
@ -303,7 +298,7 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
||||||
}
|
}
|
||||||
|
|
||||||
CrunchyrollManager.Instance.CrunOptions.CalendarShowUpcomingEpisodes = value;
|
CrunchyrollManager.Instance.CrunOptions.CalendarShowUpcomingEpisodes = value;
|
||||||
CfgManager.WriteSettingsToFile();
|
CfgManager.WriteCrSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnCurrentCalendarDubFilterChanged(ComboBoxItem? value){
|
partial void OnCurrentCalendarDubFilterChanged(ComboBoxItem? value){
|
||||||
|
|
@ -313,7 +308,7 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(value?.Content + "")){
|
if (!string.IsNullOrEmpty(value?.Content + "")){
|
||||||
CrunchyrollManager.Instance.CrunOptions.CalendarDubFilter = value?.Content + "";
|
CrunchyrollManager.Instance.CrunOptions.CalendarDubFilter = value?.Content + "";
|
||||||
CfgManager.WriteSettingsToFile();
|
CfgManager.WriteCrSettings();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@ using CommunityToolkit.Mvvm.Input;
|
||||||
using CRD.Downloader;
|
using CRD.Downloader;
|
||||||
using CRD.Downloader.Crunchyroll;
|
using CRD.Downloader.Crunchyroll;
|
||||||
using CRD.Utils;
|
using CRD.Utils;
|
||||||
|
using CRD.Utils.Files;
|
||||||
using CRD.Utils.Structs;
|
using CRD.Utils.Structs;
|
||||||
|
using CRD.Utils.Structs.Crunchyroll;
|
||||||
|
|
||||||
namespace CRD.ViewModels;
|
namespace CRD.ViewModels;
|
||||||
|
|
||||||
|
|
@ -36,12 +38,12 @@ public partial class DownloadsPageViewModel : ViewModelBase{
|
||||||
QueueManager.Instance.UpdateDownloadListItems();
|
QueueManager.Instance.UpdateDownloadListItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
CfgManager.WriteSettingsToFile();
|
CfgManager.WriteCrSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnRemoveFinishedChanged(bool value){
|
partial void OnRemoveFinishedChanged(bool value){
|
||||||
CrunchyrollManager.Instance.CrunOptions.RemoveFinishedDownload = value;
|
CrunchyrollManager.Instance.CrunOptions.RemoveFinishedDownload = value;
|
||||||
CfgManager.WriteSettingsToFile();
|
CfgManager.WriteCrSettings();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,9 +69,10 @@ public partial class DownloadItemModel : INotifyPropertyChanged{
|
||||||
public DownloadItemModel(CrunchyEpMeta epMetaF){
|
public DownloadItemModel(CrunchyEpMeta epMetaF){
|
||||||
epMeta = epMetaF;
|
epMeta = epMetaF;
|
||||||
|
|
||||||
ImageUrl = epMeta.Image;
|
ImageUrl = epMeta.Image ?? string.Empty;
|
||||||
Title = epMeta.SeriesTitle + (!string.IsNullOrEmpty(epMeta.Season) ? " - S" + epMeta.Season + "E" + (epMeta.EpisodeNumber != string.Empty ? epMeta.EpisodeNumber : epMeta.AbsolutEpisodeNumberE) : "") + " - " +
|
Title = epMeta.SeriesTitle + (!string.IsNullOrEmpty(epMeta.Season) ? " - S" + epMeta.Season + "E" + (epMeta.EpisodeNumber != string.Empty ? epMeta.EpisodeNumber : epMeta.AbsolutEpisodeNumberE) : "") + " - " +
|
||||||
epMeta.EpisodeTitle;
|
epMeta.EpisodeTitle;
|
||||||
|
|
||||||
isDownloading = epMeta.DownloadProgress.IsDownloading || Done;
|
isDownloading = epMeta.DownloadProgress.IsDownloading || Done;
|
||||||
|
|
||||||
Done = epMeta.DownloadProgress.Done;
|
Done = epMeta.DownloadProgress.Done;
|
||||||
|
|
@ -81,11 +84,19 @@ public partial class DownloadItemModel : INotifyPropertyChanged{
|
||||||
Done ? (epMeta.DownloadProgress.Doing != string.Empty ? epMeta.DownloadProgress.Doing : "Done") :
|
Done ? (epMeta.DownloadProgress.Doing != string.Empty ? epMeta.DownloadProgress.Doing : "Done") :
|
||||||
epMeta.DownloadProgress.Doing != string.Empty ? epMeta.DownloadProgress.Doing : "Waiting";
|
epMeta.DownloadProgress.Doing != string.Empty ? epMeta.DownloadProgress.Doing : "Waiting";
|
||||||
|
|
||||||
if (epMeta.Data != null) InfoText = GetDubString() + " - " + GetSubtitleString() + (!string.IsNullOrEmpty(epMeta.Resolution) ? "- " + epMeta.Resolution : "");
|
InfoText = JoinWithSeparator(
|
||||||
|
GetDubString(),
|
||||||
|
GetSubtitleString(),
|
||||||
|
epMeta.Resolution
|
||||||
|
);
|
||||||
|
|
||||||
Error = epMeta.DownloadProgress.Error;
|
Error = epMeta.DownloadProgress.Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string JoinWithSeparator(params string[] parts){
|
||||||
|
return string.Join(" - ", parts.Where(part => !string.IsNullOrEmpty(part)));
|
||||||
|
}
|
||||||
|
|
||||||
private string GetDubString(){
|
private string GetDubString(){
|
||||||
if (epMeta.SelectedDubs == null || epMeta.SelectedDubs.Count < 1){
|
if (epMeta.SelectedDubs == null || epMeta.SelectedDubs.Count < 1){
|
||||||
return "";
|
return "";
|
||||||
|
|
@ -138,10 +149,15 @@ public partial class DownloadItemModel : INotifyPropertyChanged{
|
||||||
Done ? (epMeta.DownloadProgress.Doing != string.Empty ? epMeta.DownloadProgress.Doing : "Done") :
|
Done ? (epMeta.DownloadProgress.Doing != string.Empty ? epMeta.DownloadProgress.Doing : "Done") :
|
||||||
epMeta.DownloadProgress.Doing != string.Empty ? epMeta.DownloadProgress.Doing : "Waiting";
|
epMeta.DownloadProgress.Doing != string.Empty ? epMeta.DownloadProgress.Doing : "Waiting";
|
||||||
|
|
||||||
if (epMeta.Data != null) InfoText = GetDubString() + " - " + GetSubtitleString() + (!string.IsNullOrEmpty(epMeta.Resolution) ? "- " + epMeta.Resolution : "");
|
InfoText = JoinWithSeparator(
|
||||||
|
GetDubString(),
|
||||||
|
GetSubtitleString(),
|
||||||
|
epMeta.Resolution
|
||||||
|
);
|
||||||
|
|
||||||
Error = epMeta.DownloadProgress.Error;
|
Error = epMeta.DownloadProgress.Error;
|
||||||
|
|
||||||
|
|
||||||
if (PropertyChanged != null){
|
if (PropertyChanged != null){
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(isDownloading)));
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(isDownloading)));
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Percent)));
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Percent)));
|
||||||
|
|
@ -186,15 +202,15 @@ public partial class DownloadItemModel : INotifyPropertyChanged{
|
||||||
epMeta.DownloadProgress.IsDownloading = true;
|
epMeta.DownloadProgress.IsDownloading = true;
|
||||||
Paused = !epMeta.Paused && !isDownloading || epMeta.Paused;
|
Paused = !epMeta.Paused && !isDownloading || epMeta.Paused;
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Paused)));
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Paused)));
|
||||||
|
|
||||||
CrDownloadOptions newOptions = Helpers.DeepCopy(CrunchyrollManager.Instance.CrunOptions);
|
CrDownloadOptions newOptions = Helpers.DeepCopy(CrunchyrollManager.Instance.CrunOptions);
|
||||||
|
|
||||||
if (epMeta.OnlySubs){
|
if (epMeta.OnlySubs){
|
||||||
newOptions.Novids = true;
|
newOptions.Novids = true;
|
||||||
newOptions.Noaudio = true;
|
newOptions.Noaudio = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
await CrunchyrollManager.Instance.DownloadEpisode(epMeta,newOptions );
|
await CrunchyrollManager.Instance.DownloadEpisode(epMeta, newOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -209,7 +225,8 @@ public partial class DownloadItemModel : INotifyPropertyChanged{
|
||||||
if (File.Exists(downloadItemDownloadedFile)){
|
if (File.Exists(downloadItemDownloadedFile)){
|
||||||
File.Delete(downloadItemDownloadedFile);
|
File.Delete(downloadItemDownloadedFile);
|
||||||
}
|
}
|
||||||
} catch (Exception e){
|
} catch (Exception){
|
||||||
|
// ignored
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ using CommunityToolkit.Mvvm.Input;
|
||||||
using CRD.Downloader;
|
using CRD.Downloader;
|
||||||
using CRD.Downloader.Crunchyroll;
|
using CRD.Downloader.Crunchyroll;
|
||||||
using CRD.Utils;
|
using CRD.Utils;
|
||||||
|
using CRD.Utils.Files;
|
||||||
using CRD.Utils.Sonarr;
|
using CRD.Utils.Sonarr;
|
||||||
using CRD.Utils.Structs;
|
using CRD.Utils.Structs;
|
||||||
using CRD.Utils.Structs.History;
|
using CRD.Utils.Structs.History;
|
||||||
|
|
@ -29,7 +30,7 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
||||||
private ProgramManager _programManager;
|
private ProgramManager _programManager;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private HistorySeries _selectedSeries;
|
private HistorySeries? _selectedSeries;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private static bool _editMode;
|
private static bool _editMode;
|
||||||
|
|
@ -72,10 +73,16 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
||||||
|
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _isPosterViewSelected = false;
|
private bool _isPosterViewSelected;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _isTableViewSelected = false;
|
private bool _isTableViewSelected;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _showSeries = true;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _showArtists;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private static bool _viewSelectionOpen;
|
private static bool _viewSelectionOpen;
|
||||||
|
|
@ -98,7 +105,7 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
||||||
private FilterType currentFilterType;
|
private FilterType currentFilterType;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private static bool _sortDir = false;
|
private static bool _sortDir;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private static bool _sonarrAvailable;
|
private static bool _sonarrAvailable;
|
||||||
|
|
@ -127,6 +134,8 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
||||||
currentFilterType = properties?.SelectedFilter ?? FilterType.All;
|
currentFilterType = properties?.SelectedFilter ?? FilterType.All;
|
||||||
ScaleValue = properties?.ScaleValue ?? 0.73;
|
ScaleValue = properties?.ScaleValue ?? 0.73;
|
||||||
SortDir = properties?.Ascending ?? false;
|
SortDir = properties?.Ascending ?? false;
|
||||||
|
ShowSeries = properties?.ShowSeries ?? true;
|
||||||
|
ShowArtists = properties?.ShowArtists ?? false;
|
||||||
|
|
||||||
foreach (HistoryViewType viewType in Enum.GetValues(typeof(HistoryViewType))){
|
foreach (HistoryViewType viewType in Enum.GetValues(typeof(HistoryViewType))){
|
||||||
var combobox = new ComboBoxItem{ Content = viewType };
|
var combobox = new ComboBoxItem{ Content = viewType };
|
||||||
|
|
@ -137,7 +146,7 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (SortingType sortingType in Enum.GetValues(typeof(SortingType))){
|
foreach (SortingType sortingType in Enum.GetValues(typeof(SortingType))){
|
||||||
var combobox = new SortingListElement(){ SortingTitle = sortingType.GetEnumMemberValue(), SelectedSorting = sortingType };
|
var combobox = new SortingListElement{ SortingTitle = sortingType.GetEnumMemberValue(), SelectedSorting = sortingType };
|
||||||
SortingList.Add(combobox);
|
SortingList.Add(combobox);
|
||||||
if (sortingType == currentSortingType){
|
if (sortingType == currentSortingType){
|
||||||
SelectedSorting = combobox;
|
SelectedSorting = combobox;
|
||||||
|
|
@ -149,7 +158,7 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var item = new FilterListElement(){ FilterTitle = filterType.GetEnumMemberValue(), SelectedType = filterType };
|
var item = new FilterListElement{ FilterTitle = filterType.GetEnumMemberValue(), SelectedType = filterType };
|
||||||
FilterList.Add(item);
|
FilterList.Add(item);
|
||||||
if (filterType == currentFilterType){
|
if (filterType == currentFilterType){
|
||||||
SelectedFilter = item;
|
SelectedFilter = item;
|
||||||
|
|
@ -162,7 +171,7 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
||||||
|
|
||||||
foreach (var historySeries in Items){
|
foreach (var historySeries in Items){
|
||||||
if (historySeries.ThumbnailImage == null){
|
if (historySeries.ThumbnailImage == null){
|
||||||
historySeries.LoadImage();
|
_ = historySeries.LoadImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
historySeries.UpdateNewEpisodes();
|
historySeries.UpdateNewEpisodes();
|
||||||
|
|
@ -179,15 +188,15 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
||||||
CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties.SelectedSorting = currentSortingType;
|
CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties.SelectedSorting = currentSortingType;
|
||||||
CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties.Ascending = SortDir;
|
CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties.Ascending = SortDir;
|
||||||
} else{
|
} else{
|
||||||
CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties = new HistoryPageProperties()
|
CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties = new HistoryPageProperties
|
||||||
{ ScaleValue = ScaleValue, SelectedView = currentViewType, SelectedSorting = currentSortingType, Ascending = SortDir };
|
{ ScaleValue = ScaleValue, SelectedView = currentViewType, SelectedSorting = currentSortingType, Ascending = SortDir };
|
||||||
}
|
}
|
||||||
|
|
||||||
CfgManager.WriteSettingsToFile();
|
CfgManager.WriteCrSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnSelectedViewChanged(ComboBoxItem value){
|
partial void OnSelectedViewChanged(ComboBoxItem? value){
|
||||||
if (Enum.TryParse(value.Content + "", out HistoryViewType viewType)){
|
if (Enum.TryParse(value?.Content + "", out HistoryViewType viewType)){
|
||||||
currentViewType = viewType;
|
currentViewType = viewType;
|
||||||
IsPosterViewSelected = currentViewType == HistoryViewType.Posters;
|
IsPosterViewSelected = currentViewType == HistoryViewType.Posters;
|
||||||
IsTableViewSelected = currentViewType == HistoryViewType.Table;
|
IsTableViewSelected = currentViewType == HistoryViewType.Table;
|
||||||
|
|
@ -214,21 +223,33 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newValue.SelectedSorting != null){
|
currentSortingType = newValue.SelectedSorting;
|
||||||
currentSortingType = newValue.SelectedSorting;
|
if (CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties != null) CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties.SelectedSorting = currentSortingType;
|
||||||
if (CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties != null) CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties.SelectedSorting = currentSortingType;
|
CrunchyrollManager.Instance.History.SortItems();
|
||||||
CrunchyrollManager.Instance.History.SortItems();
|
if (SelectedFilter != null){
|
||||||
if (SelectedFilter != null){
|
OnSelectedFilterChanged(SelectedFilter);
|
||||||
OnSelectedFilterChanged(SelectedFilter);
|
|
||||||
}
|
|
||||||
} else{
|
|
||||||
Console.Error.WriteLine("Invalid viewtype selected");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SortingSelectionOpen = false;
|
SortingSelectionOpen = false;
|
||||||
UpdateSettings();
|
UpdateSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
partial void OnShowArtistsChanged(bool value){
|
||||||
|
if (CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties != null) CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties.ShowArtists = ShowArtists;
|
||||||
|
|
||||||
|
CfgManager.WriteCrSettings();
|
||||||
|
|
||||||
|
ApplyFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnShowSeriesChanged(bool value){
|
||||||
|
if (CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties != null) CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties.ShowSeries = ShowSeries;
|
||||||
|
|
||||||
|
CfgManager.WriteCrSettings();
|
||||||
|
|
||||||
|
ApplyFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
partial void OnSelectedFilterChanged(FilterListElement? value){
|
partial void OnSelectedFilterChanged(FilterListElement? value){
|
||||||
if (value == null){
|
if (value == null){
|
||||||
|
|
@ -237,37 +258,52 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
||||||
|
|
||||||
currentFilterType = value.SelectedType;
|
currentFilterType = value.SelectedType;
|
||||||
if (CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties != null) CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties.SelectedFilter = currentFilterType;
|
if (CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties != null) CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties.SelectedFilter = currentFilterType;
|
||||||
|
CfgManager.WriteCrSettings();
|
||||||
|
|
||||||
|
|
||||||
|
ApplyFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyFilter(){
|
||||||
|
List<HistorySeries> filteredItems;
|
||||||
|
|
||||||
switch (currentFilterType){
|
switch (currentFilterType){
|
||||||
case FilterType.All:
|
case FilterType.All:
|
||||||
FilteredItems.Clear();
|
filteredItems = Items.ToList();
|
||||||
FilteredItems.AddRange(Items);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FilterType.MissingEpisodes:
|
case FilterType.MissingEpisodes:
|
||||||
List<HistorySeries> filteredItems = Items.Where(item => item.NewEpisodes > 0).ToList();
|
filteredItems = Items.Where(item => item.NewEpisodes > 0).ToList();
|
||||||
FilteredItems.Clear();
|
|
||||||
FilteredItems.AddRange(filteredItems);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FilterType.MissingEpisodesSonarr:
|
case FilterType.MissingEpisodesSonarr:
|
||||||
|
filteredItems = Items.Where(historySeries =>
|
||||||
var missingSonarrFiltered = Items.Where(historySeries =>
|
!string.IsNullOrEmpty(historySeries.SonarrSeriesId) &&
|
||||||
!string.IsNullOrEmpty(historySeries.SonarrSeriesId) && // Check series ID
|
historySeries.Seasons.Any(season =>
|
||||||
historySeries.Seasons.Any(season => // Check each season
|
season.EpisodesList.Any(historyEpisode =>
|
||||||
season.EpisodesList.Any(historyEpisode => // Check each episode
|
!string.IsNullOrEmpty(historyEpisode.SonarrEpisodeId) && !historyEpisode.SonarrHasFile)))
|
||||||
!string.IsNullOrEmpty(historyEpisode.SonarrEpisodeId) && !historyEpisode.SonarrHasFile))) // Filter condition
|
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
FilteredItems.Clear();
|
|
||||||
FilteredItems.AddRange(missingSonarrFiltered);
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FilterType.ContinuingOnly:
|
case FilterType.ContinuingOnly:
|
||||||
List<HistorySeries> continuingFiltered = Items.Where(item => !string.IsNullOrEmpty(item.SonarrNextAirDate)).ToList();
|
filteredItems = Items.Where(item => !string.IsNullOrEmpty(item.SonarrNextAirDate)).ToList();
|
||||||
FilteredItems.Clear();
|
break;
|
||||||
FilteredItems.AddRange(continuingFiltered);
|
|
||||||
|
default:
|
||||||
|
filteredItems = new List<HistorySeries>();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!ShowArtists){
|
||||||
|
filteredItems.RemoveAll(item => item.SeriesType == SeriesType.Artist);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ShowSeries){
|
||||||
|
filteredItems.RemoveAll(item => item.SeriesType == SeriesType.Series);
|
||||||
|
}
|
||||||
|
|
||||||
|
FilteredItems.Clear();
|
||||||
|
FilteredItems.AddRange(filteredItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -298,12 +334,12 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
||||||
NavToSeries();
|
NavToSeries();
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(value.SonarrSeriesId) && CrunchyrollManager.Instance.CrunOptions.SonarrProperties is{ SonarrEnabled: true }){
|
if (!string.IsNullOrEmpty(value.SonarrSeriesId) && CrunchyrollManager.Instance.CrunOptions.SonarrProperties is{ SonarrEnabled: true }){
|
||||||
CrunchyrollManager.Instance.History.MatchHistoryEpisodesWithSonarr(true, SelectedSeries);
|
if (SelectedSeries != null) _ = CrunchyrollManager.Instance.History.MatchHistoryEpisodesWithSonarr(true, SelectedSeries);
|
||||||
CfgManager.UpdateHistoryFile();
|
CfgManager.UpdateHistoryFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SelectedSeries = null;
|
||||||
|
|
||||||
_selectedSeries = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
|
|
@ -348,7 +384,7 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
public async void AddMissingToQueue(){
|
public async Task AddMissingToQueue(){
|
||||||
var tasks = FilteredItems
|
var tasks = FilteredItems
|
||||||
.Select(item => item.AddNewMissingToDownloads());
|
.Select(item => item.AddNewMissingToDownloads());
|
||||||
|
|
||||||
|
|
@ -408,7 +444,7 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
||||||
ProgressText = $"{count + 1}/{totalSeries}";
|
ProgressText = $"{count + 1}/{totalSeries}";
|
||||||
|
|
||||||
// Await the CRUpdateSeries task for each seriesId
|
// Await the CRUpdateSeries task for each seriesId
|
||||||
await crInstance.History.CRUpdateSeries(seriesIds[count], "");
|
await crInstance.History.CrUpdateSeries(seriesIds[count], "");
|
||||||
RaisePropertyChanged(nameof(ProgressText));
|
RaisePropertyChanged(nameof(ProgressText));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -500,27 +536,30 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class HistoryPageProperties(){
|
public class HistoryPageProperties{
|
||||||
public SortingType? SelectedSorting{ get; set; }
|
public SortingType? SelectedSorting{ get; set; }
|
||||||
public HistoryViewType SelectedView{ get; set; }
|
public HistoryViewType SelectedView{ get; set; }
|
||||||
public FilterType SelectedFilter{ get; set; }
|
public FilterType SelectedFilter{ get; set; }
|
||||||
public double? ScaleValue{ get; set; }
|
public double? ScaleValue{ get; set; }
|
||||||
|
|
||||||
public bool Ascending{ get; set; }
|
public bool Ascending{ get; set; }
|
||||||
|
|
||||||
|
public bool ShowSeries{ get; set; } = true;
|
||||||
|
public bool ShowArtists{ get; set; } = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SeasonsPageProperties(){
|
public class SeasonsPageProperties{
|
||||||
public SortingType? SelectedSorting{ get; set; }
|
public SortingType? SelectedSorting{ get; set; }
|
||||||
|
|
||||||
public bool Ascending{ get; set; }
|
public bool Ascending{ get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SortingListElement(){
|
public class SortingListElement{
|
||||||
public SortingType SelectedSorting{ get; set; }
|
public SortingType SelectedSorting{ get; set; }
|
||||||
public string? SortingTitle{ get; set; }
|
public string? SortingTitle{ get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FilterListElement(){
|
public class FilterListElement{
|
||||||
public FilterType SelectedType{ get; set; }
|
public FilterType SelectedType{ get; set; }
|
||||||
public string? FilterTitle{ get; set; }
|
public string? FilterTitle{ get; set; }
|
||||||
}
|
}
|
||||||
|
|
@ -29,6 +29,9 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public static bool _sonarrAvailable;
|
public static bool _sonarrAvailable;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public static bool _showMonitoredBookmark;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public static bool _sonarrConnected;
|
public static bool _sonarrConnected;
|
||||||
|
|
@ -53,7 +56,7 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
||||||
_selectedSeries = CrunchyrollManager.Instance.SelectedSeries;
|
_selectedSeries = CrunchyrollManager.Instance.SelectedSeries;
|
||||||
|
|
||||||
if (_selectedSeries.ThumbnailImage == null){
|
if (_selectedSeries.ThumbnailImage == null){
|
||||||
_selectedSeries.LoadImage();
|
_ = _selectedSeries.LoadImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CrunchyrollManager.Instance.CrunOptions.SonarrProperties != null){
|
if (CrunchyrollManager.Instance.CrunOptions.SonarrProperties != null){
|
||||||
|
|
@ -61,13 +64,18 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(SelectedSeries.SonarrSeriesId)){
|
if (!string.IsNullOrEmpty(SelectedSeries.SonarrSeriesId)){
|
||||||
SonarrAvailable = SelectedSeries.SonarrSeriesId.Length > 0 && SonarrConnected;
|
SonarrAvailable = SelectedSeries.SonarrSeriesId.Length > 0 && SonarrConnected;
|
||||||
|
|
||||||
|
if (SonarrAvailable){
|
||||||
|
ShowMonitoredBookmark = CrunchyrollManager.Instance.CrunOptions.HistorySkipUnmonitored;
|
||||||
|
}
|
||||||
|
|
||||||
} else{
|
} else{
|
||||||
SonarrAvailable = false;
|
SonarrAvailable = false;
|
||||||
}
|
}
|
||||||
} else{
|
} else{
|
||||||
SonarrConnected = SonarrAvailable = false;
|
SonarrConnected = SonarrAvailable = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
AvailableDubs = "Available Dubs: " + string.Join(", ", SelectedSeries.HistorySeriesAvailableDubLang);
|
AvailableDubs = "Available Dubs: " + string.Join(", ", SelectedSeries.HistorySeriesAvailableDubLang);
|
||||||
AvailableSubs = "Available Subs: " + string.Join(", ", SelectedSeries.HistorySeriesAvailableSoftSubs);
|
AvailableSubs = "Available Subs: " + string.Join(", ", SelectedSeries.HistorySeriesAvailableSoftSubs);
|
||||||
|
|
||||||
|
|
@ -87,17 +95,19 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
||||||
var seasonPath = season.SeasonDownloadPath;
|
var seasonPath = season.SeasonDownloadPath;
|
||||||
var directoryInfo = new DirectoryInfo(seasonPath);
|
var directoryInfo = new DirectoryInfo(seasonPath);
|
||||||
|
|
||||||
string parentFolderPath = directoryInfo.Parent?.FullName;
|
if (!string.IsNullOrEmpty(directoryInfo.Parent?.FullName)){
|
||||||
|
string parentFolderPath = directoryInfo.Parent?.FullName ?? string.Empty;
|
||||||
|
|
||||||
if (Directory.Exists(parentFolderPath)){
|
if (Directory.Exists(parentFolderPath)){
|
||||||
SeriesFolderPath = parentFolderPath;
|
SeriesFolderPath = parentFolderPath;
|
||||||
SeriesFolderPathExists = true;
|
SeriesFolderPathExists = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e){
|
} catch (Exception e){
|
||||||
Console.Error.WriteLine($"An error occurred while opening the folder: {e.Message}");
|
Console.Error.WriteLine($"An error occurred while opening the folder: {e.Message}");
|
||||||
}
|
}
|
||||||
} else{
|
} else{
|
||||||
var customPath = string.Empty;
|
string customPath;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(SelectedSeries.SeriesTitle))
|
if (string.IsNullOrEmpty(SelectedSeries.SeriesTitle))
|
||||||
return;
|
return;
|
||||||
|
|
@ -110,10 +120,10 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
||||||
// Check Crunchyroll download directory
|
// Check Crunchyroll download directory
|
||||||
var downloadDirPath = CrunchyrollManager.Instance.CrunOptions.DownloadDirPath;
|
var downloadDirPath = CrunchyrollManager.Instance.CrunOptions.DownloadDirPath;
|
||||||
if (!string.IsNullOrEmpty(downloadDirPath)){
|
if (!string.IsNullOrEmpty(downloadDirPath)){
|
||||||
customPath = System.IO.Path.Combine(downloadDirPath, seriesTitle);
|
customPath = Path.Combine(downloadDirPath, seriesTitle);
|
||||||
} else{
|
} else{
|
||||||
// Fallback to configured VIDEOS_DIR path
|
// Fallback to configured VIDEOS_DIR path
|
||||||
customPath = System.IO.Path.Combine(CfgManager.PathVIDEOS_DIR, seriesTitle);
|
customPath = Path.Combine(CfgManager.PathVIDEOS_DIR, seriesTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if custom path exists
|
// Check if custom path exists
|
||||||
|
|
@ -186,7 +196,7 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
||||||
SonarrConnected = SonarrAvailable = false;
|
SonarrConnected = SonarrAvailable = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateData("");
|
_ = UpdateData("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ using System;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Layout;
|
using Avalonia.Layout;
|
||||||
using Avalonia.Media;
|
|
||||||
using Avalonia.Media.Imaging;
|
using Avalonia.Media.Imaging;
|
||||||
using CRD.Downloader.Crunchyroll.ViewModels;
|
using CRD.Downloader.Crunchyroll.ViewModels;
|
||||||
using CRD.Downloader.Crunchyroll.Views;
|
using CRD.Downloader.Crunchyroll.Views;
|
||||||
|
|
@ -15,14 +14,14 @@ using Image = Avalonia.Controls.Image;
|
||||||
|
|
||||||
namespace CRD.ViewModels;
|
namespace CRD.ViewModels;
|
||||||
|
|
||||||
public partial class SettingsPageViewModel : ViewModelBase{
|
public class SettingsPageViewModel : ViewModelBase{
|
||||||
|
|
||||||
public ObservableCollection<TabViewItem> Tabs{ get; } = new();
|
public ObservableCollection<TabViewItem> Tabs{ get; } = new();
|
||||||
|
|
||||||
private TabViewItem CreateTab(string header, string iconPath, UserControl content, object viewModel){
|
private TabViewItem CreateTab(string header, string iconPath, UserControl content, object viewModel){
|
||||||
content.DataContext = viewModel;
|
content.DataContext = viewModel;
|
||||||
|
|
||||||
Bitmap bitmap = null;
|
Bitmap? bitmap = null;
|
||||||
try{
|
try{
|
||||||
// Load the image using AssetLoader.Open
|
// Load the image using AssetLoader.Open
|
||||||
bitmap = new Bitmap(Avalonia.Platform.AssetLoader.Open(new Uri(iconPath)));
|
bitmap = new Bitmap(Avalonia.Platform.AssetLoader.Open(new Uri(iconPath)));
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,24 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Reactive.Linq;
|
|
||||||
using System.Reactive.Threading.Tasks;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Avalonia.Controls;
|
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using CRD.Downloader;
|
using CRD.Downloader;
|
||||||
using CRD.Downloader.Crunchyroll;
|
using CRD.Downloader.Crunchyroll;
|
||||||
using CRD.Utils;
|
using CRD.Utils;
|
||||||
|
using CRD.Utils.Files;
|
||||||
using CRD.Utils.Structs;
|
using CRD.Utils.Structs;
|
||||||
using CRD.Utils.Structs.History;
|
using CRD.Utils.Structs.History;
|
||||||
using CRD.Views;
|
using CRD.Views;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using JsonSerializer = Newtonsoft.Json.JsonSerializer;
|
|
||||||
|
|
||||||
namespace CRD.ViewModels;
|
namespace CRD.ViewModels;
|
||||||
|
|
||||||
|
|
@ -147,7 +141,7 @@ public partial class UpcomingPageViewModel : ViewModelBase{
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private AnilistSeries _selectedSeries;
|
private AnilistSeries? _selectedSeries;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private int _selectedIndex;
|
private int _selectedIndex;
|
||||||
|
|
@ -164,7 +158,7 @@ public partial class UpcomingPageViewModel : ViewModelBase{
|
||||||
private SortingType currentSortingType;
|
private SortingType currentSortingType;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private static bool _sortDir = false;
|
private static bool _sortDir;
|
||||||
|
|
||||||
public ObservableCollection<SortingListElement> SortingList{ get; } =[];
|
public ObservableCollection<SortingListElement> SortingList{ get; } =[];
|
||||||
|
|
||||||
|
|
@ -236,12 +230,12 @@ public partial class UpcomingPageViewModel : ViewModelBase{
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
public async void AddToHistory(AnilistSeries series){
|
public async Task AddToHistory(AnilistSeries series){
|
||||||
if (!string.IsNullOrEmpty(series.CrunchyrollID)){
|
if (!string.IsNullOrEmpty(series.CrunchyrollID)){
|
||||||
if (CrunchyrollManager.Instance.CrunOptions.History){
|
if (CrunchyrollManager.Instance.CrunOptions.History){
|
||||||
series.IsInHistory = true;
|
series.IsInHistory = true;
|
||||||
RaisePropertyChanged(nameof(series.IsInHistory));
|
RaisePropertyChanged(nameof(series.IsInHistory));
|
||||||
var sucess = await CrunchyrollManager.Instance.History.CRUpdateSeries(series.CrunchyrollID, "");
|
var sucess = await CrunchyrollManager.Instance.History.CrUpdateSeries(series.CrunchyrollID, "");
|
||||||
series.IsInHistory = sucess;
|
series.IsInHistory = sucess;
|
||||||
RaisePropertyChanged(nameof(series.IsInHistory));
|
RaisePropertyChanged(nameof(series.IsInHistory));
|
||||||
|
|
||||||
|
|
@ -435,7 +429,7 @@ public partial class UpcomingPageViewModel : ViewModelBase{
|
||||||
CrunchyrollManager.Instance.CrunOptions.SeasonsPageProperties = new SeasonsPageProperties(){ SelectedSorting = currentSortingType, Ascending = SortDir };
|
CrunchyrollManager.Instance.CrunOptions.SeasonsPageProperties = new SeasonsPageProperties(){ SelectedSorting = currentSortingType, Ascending = SortDir };
|
||||||
}
|
}
|
||||||
|
|
||||||
CfgManager.WriteSettingsToFile();
|
CfgManager.WriteCrSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnSelectedSortingChanged(SortingListElement? oldValue, SortingListElement? newValue){
|
partial void OnSelectedSortingChanged(SortingListElement? oldValue, SortingListElement? newValue){
|
||||||
|
|
@ -452,11 +446,9 @@ public partial class UpcomingPageViewModel : ViewModelBase{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newValue.SelectedSorting != null){
|
currentSortingType = newValue.SelectedSorting;
|
||||||
currentSortingType = newValue.SelectedSorting;
|
if (CrunchyrollManager.Instance.CrunOptions.SeasonsPageProperties != null) CrunchyrollManager.Instance.CrunOptions.SeasonsPageProperties.SelectedSorting = currentSortingType;
|
||||||
if (CrunchyrollManager.Instance.CrunOptions.SeasonsPageProperties != null) CrunchyrollManager.Instance.CrunOptions.SeasonsPageProperties.SelectedSorting = currentSortingType;
|
SortItems();
|
||||||
SortItems();
|
|
||||||
}
|
|
||||||
|
|
||||||
SortingSelectionOpen = false;
|
SortingSelectionOpen = false;
|
||||||
UpdateSettings();
|
UpdateSettings();
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using CRD.Utils;
|
using CRD.Utils;
|
||||||
using CRD.Utils.Ffmpeg_Encoding;
|
using CRD.Utils.Ffmpeg_Encoding;
|
||||||
|
using CRD.Utils.Files;
|
||||||
using CRD.Utils.Structs;
|
using CRD.Utils.Structs;
|
||||||
using CRD.Views;
|
using CRD.Views;
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,9 @@ using CommunityToolkit.Mvvm.Input;
|
||||||
using CRD.Downloader;
|
using CRD.Downloader;
|
||||||
using CRD.Downloader.Crunchyroll;
|
using CRD.Downloader.Crunchyroll;
|
||||||
using CRD.Utils;
|
using CRD.Utils;
|
||||||
|
using CRD.Utils.Files;
|
||||||
using CRD.Utils.Sonarr;
|
using CRD.Utils.Sonarr;
|
||||||
using CRD.Utils.Structs;
|
using CRD.Utils.Structs.Crunchyroll;
|
||||||
using CRD.Utils.Structs.History;
|
using CRD.Utils.Structs.History;
|
||||||
using FluentAvalonia.Styling;
|
using FluentAvalonia.Styling;
|
||||||
|
|
||||||
|
|
@ -35,8 +36,14 @@ public partial class GeneralSettingsViewModel : ViewModelBase{
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _history;
|
private bool _history;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _historyIncludeCrArtists;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _historyAddSpecials;
|
private bool _historyAddSpecials;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _historySkipUnmonitored;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _historyCountSonarr;
|
private bool _historyCountSonarr;
|
||||||
|
|
@ -236,7 +243,9 @@ public partial class GeneralSettingsViewModel : ViewModelBase{
|
||||||
ProxyUsername = options.ProxyUsername ?? "";
|
ProxyUsername = options.ProxyUsername ?? "";
|
||||||
ProxyPassword = options.ProxyPassword ?? "";
|
ProxyPassword = options.ProxyPassword ?? "";
|
||||||
ProxyPort = options.ProxyPort;
|
ProxyPort = options.ProxyPort;
|
||||||
|
HistoryIncludeCrArtists = options.HistoryIncludeCrArtists;
|
||||||
HistoryAddSpecials = options.HistoryAddSpecials;
|
HistoryAddSpecials = options.HistoryAddSpecials;
|
||||||
|
HistorySkipUnmonitored = options.HistorySkipUnmonitored;
|
||||||
HistoryCountSonarr = options.HistoryCountSonarr;
|
HistoryCountSonarr = options.HistoryCountSonarr;
|
||||||
DownloadSpeed = options.DownloadSpeedLimit;
|
DownloadSpeed = options.DownloadSpeedLimit;
|
||||||
DownloadToTempFolder = options.DownloadToTempFolder;
|
DownloadToTempFolder = options.DownloadToTempFolder;
|
||||||
|
|
@ -265,6 +274,8 @@ public partial class GeneralSettingsViewModel : ViewModelBase{
|
||||||
|
|
||||||
CrunchyrollManager.Instance.CrunOptions.DownloadToTempFolder = DownloadToTempFolder;
|
CrunchyrollManager.Instance.CrunOptions.DownloadToTempFolder = DownloadToTempFolder;
|
||||||
CrunchyrollManager.Instance.CrunOptions.HistoryAddSpecials = HistoryAddSpecials;
|
CrunchyrollManager.Instance.CrunOptions.HistoryAddSpecials = HistoryAddSpecials;
|
||||||
|
CrunchyrollManager.Instance.CrunOptions.HistoryIncludeCrArtists = HistoryIncludeCrArtists;
|
||||||
|
CrunchyrollManager.Instance.CrunOptions.HistorySkipUnmonitored = HistorySkipUnmonitored;
|
||||||
CrunchyrollManager.Instance.CrunOptions.HistoryCountSonarr = HistoryCountSonarr;
|
CrunchyrollManager.Instance.CrunOptions.HistoryCountSonarr = HistoryCountSonarr;
|
||||||
CrunchyrollManager.Instance.CrunOptions.DownloadSpeedLimit = Math.Clamp((int)(DownloadSpeed ?? 0), 0, 1000000000);
|
CrunchyrollManager.Instance.CrunOptions.DownloadSpeedLimit = Math.Clamp((int)(DownloadSpeed ?? 0), 0, 1000000000);
|
||||||
CrunchyrollManager.Instance.CrunOptions.SimultaneousDownloads = Math.Clamp((int)(SimultaneousDownloads ?? 0), 1, 10);
|
CrunchyrollManager.Instance.CrunOptions.SimultaneousDownloads = Math.Clamp((int)(SimultaneousDownloads ?? 0), 1, 10);
|
||||||
|
|
@ -309,7 +320,7 @@ public partial class GeneralSettingsViewModel : ViewModelBase{
|
||||||
|
|
||||||
CrunchyrollManager.Instance.CrunOptions.LogMode = LogMode;
|
CrunchyrollManager.Instance.CrunOptions.LogMode = LogMode;
|
||||||
|
|
||||||
CfgManager.WriteSettingsToFile();
|
CfgManager.WriteCrSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
|
|
@ -364,7 +375,7 @@ public partial class GeneralSettingsViewModel : ViewModelBase{
|
||||||
pathSetter(selectedFolder.Path.LocalPath);
|
pathSetter(selectedFolder.Path.LocalPath);
|
||||||
var finalPath = string.IsNullOrEmpty(pathGetter()) ? defaultPath : pathGetter();
|
var finalPath = string.IsNullOrEmpty(pathGetter()) ? defaultPath : pathGetter();
|
||||||
pathSetter(finalPath);
|
pathSetter(finalPath);
|
||||||
CfgManager.WriteSettingsToFile();
|
CfgManager.WriteCrSettings();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -411,7 +422,7 @@ public partial class GeneralSettingsViewModel : ViewModelBase{
|
||||||
pathSetter(selectedFile.Path.LocalPath);
|
pathSetter(selectedFile.Path.LocalPath);
|
||||||
var finalPath = string.IsNullOrEmpty(pathGetter()) ? defaultPath : pathGetter();
|
var finalPath = string.IsNullOrEmpty(pathGetter()) ? defaultPath : pathGetter();
|
||||||
pathSetter(finalPath);
|
pathSetter(finalPath);
|
||||||
CfgManager.WriteSettingsToFile();
|
CfgManager.WriteCrSettings();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -490,9 +501,9 @@ public partial class GeneralSettingsViewModel : ViewModelBase{
|
||||||
decompressedJson,
|
decompressedJson,
|
||||||
CrunchyrollManager.Instance.SettingsJsonSerializerSettings
|
CrunchyrollManager.Instance.SettingsJsonSerializerSettings
|
||||||
) ?? new ObservableCollection<HistorySeries>();
|
) ?? new ObservableCollection<HistorySeries>();
|
||||||
|
|
||||||
CrunchyrollManager.Instance.HistoryList = historyList;
|
CrunchyrollManager.Instance.HistoryList = historyList;
|
||||||
|
|
||||||
Parallel.ForEach(historyList, historySeries => {
|
Parallel.ForEach(historyList, historySeries => {
|
||||||
historySeries.Init();
|
historySeries.Init();
|
||||||
|
|
||||||
|
|
@ -500,14 +511,13 @@ public partial class GeneralSettingsViewModel : ViewModelBase{
|
||||||
historySeriesSeason.Init();
|
historySeriesSeason.Init();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
} else{
|
} else{
|
||||||
CrunchyrollManager.Instance.HistoryList = new ObservableCollection<HistorySeries>();
|
CrunchyrollManager.Instance.HistoryList = new ObservableCollection<HistorySeries>();
|
||||||
}
|
}
|
||||||
} else{
|
} else{
|
||||||
CrunchyrollManager.Instance.HistoryList = new ObservableCollection<HistorySeries>();
|
CrunchyrollManager.Instance.HistoryList = new ObservableCollection<HistorySeries>();
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = Task.Run(() => SonarrClient.Instance.RefreshSonarrLite());
|
_ = Task.Run(() => SonarrClient.Instance.RefreshSonarrLite());
|
||||||
} else{
|
} else{
|
||||||
CrunchyrollManager.Instance.HistoryList = new ObservableCollection<HistorySeries>();
|
CrunchyrollManager.Instance.HistoryList = new ObservableCollection<HistorySeries>();
|
||||||
|
|
|
||||||
|
|
@ -212,6 +212,7 @@
|
||||||
</Grid>
|
</Grid>
|
||||||
<StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center" Margin="10">
|
<StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center" Margin="10">
|
||||||
<TextBlock FontStyle="Italic"
|
<TextBlock FontStyle="Italic"
|
||||||
|
IsVisible="{Binding HasDubs}"
|
||||||
Opacity="0.8" Text="Dubs: ">
|
Opacity="0.8" Text="Dubs: ">
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
<TextBlock Text="{Binding AvailableAudios, Converter={StaticResource UiListToStringConverter}}"
|
<TextBlock Text="{Binding AvailableAudios, Converter={StaticResource UiListToStringConverter}}"
|
||||||
|
|
|
||||||
|
|
@ -186,15 +186,44 @@
|
||||||
Placement="BottomEdgeAlignedRight"
|
Placement="BottomEdgeAlignedRight"
|
||||||
PlacementTarget="{Binding ElementName=DropdownButtonFilter}">
|
PlacementTarget="{Binding ElementName=DropdownButtonFilter}">
|
||||||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||||
<ListBox SelectionMode="Single" Width="210"
|
<StackPanel Orientation="Vertical">
|
||||||
MaxHeight="400"
|
|
||||||
ItemsSource="{Binding FilterList}" SelectedItem="{Binding SelectedFilter}">
|
<Grid Margin="8 0 5 0">
|
||||||
<ListBox.ItemTemplate>
|
<Grid.ColumnDefinitions>
|
||||||
<DataTemplate>
|
<ColumnDefinition Width="*" /> <!-- TextBlock takes remaining space -->
|
||||||
<TextBlock Text="{Binding FilterTitle}"></TextBlock>
|
<ColumnDefinition Width="Auto" /> <!-- ToggleSwitch takes only needed space -->
|
||||||
</DataTemplate>
|
</Grid.ColumnDefinitions>
|
||||||
</ListBox.ItemTemplate>
|
|
||||||
</ListBox>
|
<TextBlock Text="Series" Grid.Column="0" VerticalAlignment="Center" />
|
||||||
|
<ToggleSwitch OffContent="" OnContent="" Grid.Column="1" IsChecked="{Binding ShowSeries}" VerticalAlignment="Center" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid Margin="8 0 5 0">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<TextBlock Text="Artists" Grid.Column="0" VerticalAlignment="Center" />
|
||||||
|
<ToggleSwitch OffContent="" OnContent="" Grid.Column="1" IsChecked="{Binding ShowArtists}" VerticalAlignment="Center" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
|
||||||
|
<Rectangle Height="1" Fill="Gray" Margin="0,8,0,8" />
|
||||||
|
|
||||||
|
<ListBox SelectionMode="Single" Width="210"
|
||||||
|
MaxHeight="400"
|
||||||
|
ItemsSource="{Binding FilterList}" SelectedItem="{Binding SelectedFilter}">
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBlock Text="{Binding FilterTitle}"></TextBlock>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
</Border>
|
</Border>
|
||||||
</Popup>
|
</Popup>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<ui:UiListToStringConverter x:Key="UiListToStringConverter" />
|
<ui:UiListToStringConverter x:Key="UiListToStringConverter" />
|
||||||
|
<ui:UiListHasElementsConverter x:Key="UiListHasElementsConverter" />
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
|
|
||||||
<Grid>
|
<Grid>
|
||||||
|
|
@ -57,8 +58,8 @@
|
||||||
|
|
||||||
<TextBlock Grid.Row="0" FontSize="45" Text="{Binding SelectedSeries.SeriesTitle}" TextTrimming="CharacterEllipsis"></TextBlock>
|
<TextBlock Grid.Row="0" FontSize="45" Text="{Binding SelectedSeries.SeriesTitle}" TextTrimming="CharacterEllipsis"></TextBlock>
|
||||||
<TextBlock Grid.Row="1" FontSize="20" TextWrapping="Wrap" Text="{Binding SelectedSeries.SeriesDescription}"></TextBlock>
|
<TextBlock Grid.Row="1" FontSize="20" TextWrapping="Wrap" Text="{Binding SelectedSeries.SeriesDescription}"></TextBlock>
|
||||||
<TextBlock Grid.Row="3" FontSize="15" Opacity="0.8" TextWrapping="Wrap" Text="{Binding AvailableDubs}"></TextBlock>
|
<TextBlock Grid.Row="3" IsVisible="{Binding SelectedSeries.HistorySeriesAvailableDubLang, Converter={StaticResource UiListHasElementsConverter}}" FontSize="15" Opacity="0.8" TextWrapping="Wrap" Text="{Binding AvailableDubs}"></TextBlock>
|
||||||
<TextBlock Grid.Row="4" FontSize="15" Opacity="0.8" TextWrapping="Wrap" Text="{Binding AvailableSubs}"></TextBlock>
|
<TextBlock Grid.Row="4" IsVisible="{Binding SelectedSeries.HistorySeriesAvailableSoftSubs, Converter={StaticResource UiListHasElementsConverter}}" FontSize="15" Opacity="0.8" TextWrapping="Wrap" Text="{Binding AvailableSubs}"></TextBlock>
|
||||||
<StackPanel Grid.Row="5" Orientation="Vertical">
|
<StackPanel Grid.Row="5" Orientation="Vertical">
|
||||||
|
|
||||||
<StackPanel Orientation="Horizontal" Margin="0 10 10 10">
|
<StackPanel Orientation="Horizontal" Margin="0 10 10 10">
|
||||||
|
|
@ -183,7 +184,7 @@
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0 0 0 10">
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0 0 0 10" >
|
||||||
<TextBlock Text="Dub" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0 0 10 0"></TextBlock>
|
<TextBlock Text="Dub" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0 0 10 0"></TextBlock>
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<ToggleButton x:Name="DropdownButtonDub" Width="210" HorizontalContentAlignment="Stretch">
|
<ToggleButton x:Name="DropdownButtonDub" Width="210" HorizontalContentAlignment="Stretch">
|
||||||
|
|
@ -313,14 +314,15 @@
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<StackPanel>
|
<StackPanel VerticalAlignment="Center">
|
||||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
<TextBlock Text="E"></TextBlock>
|
<TextBlock Text="E"></TextBlock>
|
||||||
<TextBlock Text="{Binding Episode}"></TextBlock>
|
<TextBlock Text="{Binding Episode}"></TextBlock>
|
||||||
<TextBlock Text=" - "></TextBlock>
|
<TextBlock Text=" - "></TextBlock>
|
||||||
<TextBlock Text="{Binding EpisodeTitle}"></TextBlock>
|
<TextBlock Text="{Binding EpisodeTitle}"></TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"
|
||||||
|
IsVisible="{Binding HistoryEpisodeAvailableDubLang, Converter={StaticResource UiListHasElementsConverter}}">
|
||||||
<TextBlock FontStyle="Italic"
|
<TextBlock FontStyle="Italic"
|
||||||
FontSize="12"
|
FontSize="12"
|
||||||
Opacity="0.8" Text="Dubs: ">
|
Opacity="0.8" Text="Dubs: ">
|
||||||
|
|
@ -343,10 +345,30 @@
|
||||||
|
|
||||||
<StackPanel Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center">
|
<StackPanel Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
|
|
||||||
<StackPanel VerticalAlignment="Center" Margin="0 0 5 0"
|
<StackPanel VerticalAlignment="Center" Margin="0 0 5 0" Orientation="Horizontal"
|
||||||
IsVisible="{Binding $parent[UserControl].((vm:SeriesPageViewModel)DataContext).SonarrAvailable}">
|
IsVisible="{Binding $parent[UserControl].((vm:SeriesPageViewModel)DataContext).SonarrAvailable}">
|
||||||
|
|
||||||
|
|
||||||
|
<StackPanel IsVisible="{Binding $parent[UserControl].((vm:SeriesPageViewModel)DataContext).ShowMonitoredBookmark}"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
<controls:SymbolIcon IsVisible="{Binding !SonarrIsMonitored}" Margin="0 0 5 0 " Symbol="Bookmark" FontSize="18" HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
<ToolTip.Tip>
|
||||||
|
<TextBlock Text="Unmonitored" FontSize="15" />
|
||||||
|
</ToolTip.Tip>
|
||||||
|
|
||||||
|
</controls:SymbolIcon>
|
||||||
|
|
||||||
|
<controls:SymbolIcon IsVisible="{Binding SonarrIsMonitored}" Margin="0 0 5 0 " Symbol="BookmarkFilled" FontSize="18" HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
<ToolTip.Tip>
|
||||||
|
<TextBlock Text="Monitored" FontSize="15" />
|
||||||
|
</ToolTip.Tip>
|
||||||
|
|
||||||
|
</controls:SymbolIcon>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<controls:ImageIcon IsVisible="{Binding SonarrHasFile}"
|
<controls:ImageIcon IsVisible="{Binding SonarrHasFile}"
|
||||||
Source="../Assets/sonarr.png" Width="25"
|
Source="../Assets/sonarr.png" Width="25"
|
||||||
Height="25" />
|
Height="25" />
|
||||||
|
|
@ -382,7 +404,7 @@
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button Margin="0 0 5 0" FontStyle="Italic" HorizontalAlignment="Right"
|
<Button Margin="0 0 5 0" FontStyle="Italic" HorizontalAlignment="Right"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Command="{Binding DownloadEpisode}"
|
Command="{Binding DownloadEpisode}"
|
||||||
CommandParameter="false">
|
CommandParameter="false">
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
|
|
@ -392,9 +414,10 @@
|
||||||
<TextBlock Text="Download Episode" FontSize="15" />
|
<TextBlock Text="Download Episode" FontSize="15" />
|
||||||
</ToolTip.Tip>
|
</ToolTip.Tip>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button Margin="0 0 5 0" FontStyle="Italic" HorizontalAlignment="Right"
|
<Button Margin="0 0 5 0" FontStyle="Italic" HorizontalAlignment="Right"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
|
IsEnabled="{Binding HistoryEpisodeAvailableSoftSubs, Converter={StaticResource UiListHasElementsConverter}}"
|
||||||
Command="{Binding DownloadEpisode}"
|
Command="{Binding DownloadEpisode}"
|
||||||
CommandParameter="true">
|
CommandParameter="true">
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
|
|
@ -404,8 +427,6 @@
|
||||||
<TextBlock Text="Download Subs" FontSize="15" />
|
<TextBlock Text="Download Subs" FontSize="15" />
|
||||||
</ToolTip.Tip>
|
</ToolTip.Tip>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Interactivity;
|
|
||||||
|
|
||||||
namespace CRD.Views;
|
namespace CRD.Views;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,4 @@
|
||||||
using System;
|
using Avalonia.Controls;
|
||||||
using Avalonia;
|
|
||||||
using Avalonia.Controls;
|
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
using CRD.Utils.Sonarr;
|
|
||||||
using CRD.Utils.Structs;
|
using CRD.Utils.Structs;
|
||||||
using CRD.ViewModels;
|
using CRD.ViewModels;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,17 +33,29 @@
|
||||||
</controls:SettingsExpanderItem.Footer>
|
</controls:SettingsExpanderItem.Footer>
|
||||||
</controls:SettingsExpanderItem>
|
</controls:SettingsExpanderItem>
|
||||||
|
|
||||||
|
<controls:SettingsExpanderItem Content="History Include CR Artists" Description="Add Crunchyroll artists (music) to the history">
|
||||||
|
<controls:SettingsExpanderItem.Footer>
|
||||||
|
<CheckBox IsChecked="{Binding HistoryIncludeCrArtists}"> </CheckBox>
|
||||||
|
</controls:SettingsExpanderItem.Footer>
|
||||||
|
</controls:SettingsExpanderItem>
|
||||||
|
|
||||||
<controls:SettingsExpanderItem Content="History Add Specials" Description="Add specials to the queue if they weren't downloaded before">
|
<controls:SettingsExpanderItem Content="History Add Specials" Description="Add specials to the queue if they weren't downloaded before">
|
||||||
<controls:SettingsExpanderItem.Footer>
|
<controls:SettingsExpanderItem.Footer>
|
||||||
<CheckBox IsChecked="{Binding HistoryAddSpecials}"> </CheckBox>
|
<CheckBox IsChecked="{Binding HistoryAddSpecials}"> </CheckBox>
|
||||||
</controls:SettingsExpanderItem.Footer>
|
</controls:SettingsExpanderItem.Footer>
|
||||||
</controls:SettingsExpanderItem>
|
</controls:SettingsExpanderItem>
|
||||||
|
|
||||||
<controls:SettingsExpanderItem Content="History Missing/New Count from Sonarr" Description="The missing count (number in the orange corner) will count the episodes missing from sonarr">
|
<controls:SettingsExpanderItem Content="History Missing/New Count from Sonarr" Description="The missing count (number in the orange corner) will count the episodes missing from sonarr">
|
||||||
<controls:SettingsExpanderItem.Footer>
|
<controls:SettingsExpanderItem.Footer>
|
||||||
<CheckBox IsChecked="{Binding HistoryCountSonarr}"> </CheckBox>
|
<CheckBox IsChecked="{Binding HistoryCountSonarr}"> </CheckBox>
|
||||||
</controls:SettingsExpanderItem.Footer>
|
</controls:SettingsExpanderItem.Footer>
|
||||||
</controls:SettingsExpanderItem>
|
</controls:SettingsExpanderItem>
|
||||||
|
|
||||||
|
<controls:SettingsExpanderItem Content="History Skip Sonarr Unmonitored" Description="Skips unmonitored sonarr episodes when counting Missing/New">
|
||||||
|
<controls:SettingsExpanderItem.Footer>
|
||||||
|
<CheckBox IsChecked="{Binding HistorySkipUnmonitored}"> </CheckBox>
|
||||||
|
</controls:SettingsExpanderItem.Footer>
|
||||||
|
</controls:SettingsExpanderItem>
|
||||||
|
|
||||||
</controls:SettingsExpander>
|
</controls:SettingsExpander>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue