mirror of
https://github.com/Crunchy-DL/Crunchy-Downloader.git
synced 2026-01-11 20:10:26 +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.Structs;
|
||||
using CRD.Utils.Structs.History;
|
||||
using DynamicData;
|
||||
using HtmlAgilityPack;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
|
|
@ -90,56 +89,60 @@ public class CalendarManager{
|
|||
foreach (var day in dayNodes){
|
||||
// Extract the date and day name
|
||||
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){
|
||||
week.FirstDayOfWeek = dayDateTime;
|
||||
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);
|
||||
if (week.FirstDayOfWeek == DateTime.MinValue){
|
||||
week.FirstDayOfWeek = dayDateTime;
|
||||
week.FirstDayOfWeekString = dayDateTime.ToString("yyyy-MM-dd");
|
||||
}
|
||||
}
|
||||
|
||||
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{
|
||||
Console.Error.WriteLine("No days found in the HTML document.");
|
||||
|
|
@ -260,7 +263,7 @@ public class CalendarManager{
|
|||
calEpisode.EpisodeName = crBrowseEpisode.Title;
|
||||
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.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.IsPremiere = crBrowseEpisode.EpisodeMetadata.Episode == "1";
|
||||
calEpisode.SeasonName = crBrowseEpisode.EpisodeMetadata.SeasonTitle;
|
||||
|
|
@ -268,10 +271,10 @@ public class CalendarManager{
|
|||
calEpisode.CrSeriesID = crBrowseEpisode.EpisodeMetadata.SeriesId;
|
||||
|
||||
var existingEpisode = calendarDay.CalendarEpisodes
|
||||
?.FirstOrDefault(e => e.SeasonName == calEpisode.SeasonName);
|
||||
.FirstOrDefault(e => e.SeasonName == calEpisode.SeasonName);
|
||||
|
||||
if (existingEpisode != null){
|
||||
if (!int.TryParse(existingEpisode.EpisodeNumber, out var num)){
|
||||
if (!int.TryParse(existingEpisode.EpisodeNumber, out _)){
|
||||
existingEpisode.EpisodeNumber = "...";
|
||||
} else{
|
||||
var existingNumbers = existingEpisode.EpisodeNumber
|
||||
|
|
@ -300,7 +303,7 @@ public class CalendarManager{
|
|||
|
||||
existingEpisode.CalendarEpisodes.Add(calEpisode);
|
||||
} else{
|
||||
calendarDay.CalendarEpisodes?.Add(calEpisode);
|
||||
calendarDay.CalendarEpisodes.Add(calEpisode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -429,8 +432,6 @@ public class CalendarManager{
|
|||
calEp.EpisodeNumber = anilistEle.Episode.ToString();
|
||||
calEp.AnilistEpisode = true;
|
||||
|
||||
var crunchyrollID = "";
|
||||
|
||||
if (anilistEle.Media?.ExternalLinks != null){
|
||||
var url = anilistEle.Media.ExternalLinks.First(external =>
|
||||
string.Equals(external.Site, "Crunchyroll", StringComparison.OrdinalIgnoreCase)).Url;
|
||||
|
|
@ -438,10 +439,11 @@ public class CalendarManager{
|
|||
string pattern = @"series\/([^\/]+)";
|
||||
|
||||
Match match = Regex.Match(url, pattern);
|
||||
string crunchyrollId;
|
||||
if (match.Success){
|
||||
crunchyrollID = match.Groups[1].Value;
|
||||
crunchyrollId = match.Groups[1].Value;
|
||||
|
||||
AdjustReleaseTimeToHistory(calEp, crunchyrollID);
|
||||
AdjustReleaseTimeToHistory(calEp, crunchyrollId);
|
||||
} else{
|
||||
Uri uri = new Uri(url);
|
||||
|
||||
|
|
@ -462,9 +464,9 @@ public class CalendarManager{
|
|||
|
||||
Match match2 = Regex.Match(finalUrl ?? string.Empty, pattern);
|
||||
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.Threading.Tasks;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.Files;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.Crunchyroll;
|
||||
using CRD.Views;
|
||||
|
|
@ -15,6 +16,11 @@ namespace CRD.Downloader.Crunchyroll;
|
|||
public class CrAuth{
|
||||
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(){
|
||||
string uuid = Guid.NewGuid().ToString();
|
||||
|
||||
|
|
@ -22,17 +28,18 @@ public class CrAuth{
|
|||
{ "grant_type", "client_id" },
|
||||
{ "scope", "offline_access" },
|
||||
{ "device_id", uuid },
|
||||
{ "device_type", "Chrome on Windows" }
|
||||
{ "device_name", DeviceName },
|
||||
{ "device_type", DeviceType },
|
||||
};
|
||||
|
||||
var requestContent = new FormUrlEncodedContent(formData);
|
||||
|
||||
var crunchyAuthHeaders = new Dictionary<string, string>{
|
||||
{ "Authorization", ApiUrls.authBasicSwitch },
|
||||
{ "User-Agent", ApiUrls.ChromeUserAgent }
|
||||
{ "Authorization", authorization },
|
||||
{ "User-Agent", userAgent }
|
||||
};
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, ApiUrls.BetaAuth){
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, ApiUrls.Auth){
|
||||
Content = requestContent
|
||||
};
|
||||
|
||||
|
|
@ -63,7 +70,7 @@ public class CrAuth{
|
|||
crunInstance.Token.device_id = deviceId;
|
||||
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" },
|
||||
{ "scope", "offline_access" },
|
||||
{ "device_id", uuid },
|
||||
{ "device_type", "Chrome on Windows" }
|
||||
{ "device_name", DeviceName },
|
||||
{ "device_type", DeviceType },
|
||||
};
|
||||
|
||||
var requestContent = new FormUrlEncodedContent(formData);
|
||||
|
||||
var crunchyAuthHeaders = new Dictionary<string, string>{
|
||||
{ "Authorization", ApiUrls.authBasicSwitch },
|
||||
{ "User-Agent", ApiUrls.ChromeUserAgent }
|
||||
{ "Authorization", authorization },
|
||||
{ "User-Agent", userAgent }
|
||||
};
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, ApiUrls.BetaAuth){
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, ApiUrls.Auth){
|
||||
Content = requestContent
|
||||
};
|
||||
|
||||
|
|
@ -120,7 +128,7 @@ public class CrAuth{
|
|||
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);
|
||||
|
||||
|
|
@ -183,18 +191,19 @@ public class CrAuth{
|
|||
{ "refresh_token", crunInstance.Token.refresh_token },
|
||||
{ "scope", "offline_access" },
|
||||
{ "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 crunchyAuthHeaders = new Dictionary<string, string>{
|
||||
{ "Authorization", ApiUrls.authBasicSwitch },
|
||||
{ "User-Agent", ApiUrls.ChromeUserAgent }
|
||||
{ "Authorization", authorization },
|
||||
{ "User-Agent", userAgent }
|
||||
};
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, ApiUrls.BetaAuth){
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, ApiUrls.Auth){
|
||||
Content = requestContent
|
||||
};
|
||||
|
||||
|
|
@ -237,21 +246,22 @@ public class CrAuth{
|
|||
string uuid = Guid.NewGuid().ToString();
|
||||
|
||||
var formData = new Dictionary<string, string>{
|
||||
{ "refresh_token", crunInstance.Token.refresh_token },
|
||||
{ "refresh_token", crunInstance.Token?.refresh_token ?? "" },
|
||||
{ "grant_type", "refresh_token" },
|
||||
{ "scope", "offline_access" },
|
||||
{ "device_id", uuid },
|
||||
{ "device_type", "Chrome on Windows" }
|
||||
{ "device_name", DeviceName },
|
||||
{ "device_type", DeviceType },
|
||||
};
|
||||
|
||||
var requestContent = new FormUrlEncodedContent(formData);
|
||||
|
||||
var crunchyAuthHeaders = new Dictionary<string, string>{
|
||||
{ "Authorization", ApiUrls.authBasicSwitch },
|
||||
{ "User-Agent", ApiUrls.ChromeUserAgent }
|
||||
{ "Authorization", authorization },
|
||||
{ "User-Agent", userAgent }
|
||||
};
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, ApiUrls.BetaAuth){
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, ApiUrls.Auth){
|
||||
Content = requestContent
|
||||
};
|
||||
|
||||
|
|
@ -259,7 +269,7 @@ public class CrAuth{
|
|||
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);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Text.RegularExpressions;
|
|||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.Files;
|
||||
using CRD.Utils.Structs;
|
||||
|
||||
namespace CRD.Downloader.Crunchyroll;
|
||||
|
|
@ -35,9 +36,9 @@ public class CrEpisode(){
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
@ -59,7 +60,7 @@ public class CrEpisode(){
|
|||
CrunchyRollEpisodeData episode = new CrunchyRollEpisodeData();
|
||||
|
||||
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);
|
||||
if (historySeries != null){
|
||||
CrunchyrollManager.Instance.History.MatchHistorySeriesWithSonarr(false);
|
||||
|
|
@ -81,7 +82,13 @@ public class CrEpisode(){
|
|||
if (episode.EpisodeAndLanguages.Langs.All(a => a.CrLocale != version.AudioLocale)){
|
||||
// Push to arrays if there are no duplicates of the same language
|
||||
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{
|
||||
|
|
@ -91,7 +98,13 @@ public class CrEpisode(){
|
|||
if (episode.EpisodeAndLanguages.Langs.All(a => a.CrLocale != dlEpisode.AudioLocale)){
|
||||
// Push to arrays if there are no duplicates of the same language
|
||||
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;
|
||||
|
||||
|
||||
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;
|
||||
if (isSpecial && !string.IsNullOrEmpty(episode.EpisodeAndLanguages.Items[0].Episode)){
|
||||
newKey = episode.EpisodeAndLanguages.Items[0].Episode ?? "SP" + (specialIndex + " " + episode.EpisodeAndLanguages.Items[0].Id);
|
||||
|
|
@ -110,14 +123,14 @@ public class CrEpisode(){
|
|||
|
||||
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();
|
||||
|
||||
var title = episode.EpisodeAndLanguages.Items[0].Title;
|
||||
var seasonNumber = Helpers.ExtractNumberAfterS(episode.EpisodeAndLanguages.Items[0].Identifier) ?? episode.EpisodeAndLanguages.Items[0].SeasonNumber.ToString();
|
||||
|
||||
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)}]");
|
||||
|
||||
|
|
@ -125,7 +138,7 @@ public class CrEpisode(){
|
|||
if (!serieshasversions){
|
||||
Console.WriteLine("Couldn\'t find versions on episode, added languages with language array.");
|
||||
}
|
||||
|
||||
|
||||
return episode;
|
||||
}
|
||||
|
||||
|
|
@ -160,17 +173,17 @@ public class CrEpisode(){
|
|||
|
||||
var epMeta = new CrunchyEpMeta();
|
||||
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();
|
||||
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();
|
||||
epMeta.EpisodeNumber = item.Episode;
|
||||
epMeta.EpisodeTitle = item.Title;
|
||||
epMeta.SeasonId = item.SeasonId;
|
||||
epMeta.Season = Helpers.ExtractNumberAfterS(item.Identifier) ?? item.SeasonNumber + "";
|
||||
epMeta.ShowId = item.SeriesId;
|
||||
epMeta.SeriesId = item.SeriesId;
|
||||
epMeta.AbsolutEpisodeNumberE = epNum;
|
||||
epMeta.Image = images[images.Count / 2].FirstOrDefault().Source;
|
||||
epMeta.Image = images[images.Count / 2].FirstOrDefault()?.Source;
|
||||
epMeta.DownloadProgress = new DownloadProgress(){
|
||||
IsDownloading = 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];
|
||||
retMeta.Data.Add(epMetaData);
|
||||
} else{
|
||||
|
|
@ -215,7 +228,7 @@ public class CrEpisode(){
|
|||
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);
|
||||
CrBrowseEpisodeBase? complete = new CrBrowseEpisodeBase();
|
||||
complete.Data =[];
|
||||
|
|
@ -257,7 +270,6 @@ public class CrEpisode(){
|
|||
requestAmount += 50;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} else{
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ public class CrMovies{
|
|||
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){
|
||||
return null;
|
||||
|
|
@ -67,9 +67,9 @@ public class CrMovies{
|
|||
epMeta.EpisodeTitle = episodeP.Title;
|
||||
epMeta.SeasonId = "";
|
||||
epMeta.Season = "";
|
||||
epMeta.ShowId = "";
|
||||
epMeta.SeriesId = "";
|
||||
epMeta.AbsolutEpisodeNumberE = "";
|
||||
epMeta.Image = images[images.Count / 2].FirstOrDefault().Source;
|
||||
epMeta.Image = images[images.Count / 2].FirstOrDefault()?.Source;
|
||||
epMeta.DownloadProgress = new DownloadProgress(){
|
||||
IsDownloading = false,
|
||||
Done = false,
|
||||
|
|
|
|||
|
|
@ -14,44 +14,119 @@ namespace CRD.Downloader.Crunchyroll;
|
|||
public class CrMusic{
|
||||
private readonly CrunchyrollManager crunInstance = CrunchyrollManager.Instance;
|
||||
|
||||
public async Task<CrunchyMusicVideo?> ParseMusicVideoByIdAsync(string id, string crLocale, bool forcedLang = false){
|
||||
return await ParseMediaByIdAsync(id, crLocale, forcedLang, "music/music_videos");
|
||||
public async Task<CrunchyMusicVideoList?> ParseFeaturedMusicVideoByIdAsync(string seriesId, string crLocale, bool forcedLang = false, bool updateHistory = false){
|
||||
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){
|
||||
return await ParseMediaByIdAsync(id, crLocale, forcedLang, "music/concerts");
|
||||
public async Task<CrunchyMusicVideo?> ParseConcertByIdAsync(string id, string crLocale, bool forcedLang = false, bool updateHistory = false){
|
||||
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){
|
||||
var musicVideosTask = FetchMediaListAsync($"{ApiUrls.Content}/music/artists/{id}/music_videos", crLocale, forcedLang);
|
||||
var concertsTask = FetchMediaListAsync($"{ApiUrls.Content}/music/artists/{id}/concerts", crLocale, forcedLang);
|
||||
public async Task<CrunchyMusicVideoList?> ParseArtistMusicVideosByIdAsync(string artistId, string crLocale, bool forcedLang = false, bool updateHistory = false){
|
||||
var musicVideos = await FetchMediaListAsync($"{ApiUrls.Content}/music/artists/{artistId}/music_videos", 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);
|
||||
|
||||
var musicVideos = await musicVideosTask;
|
||||
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);
|
||||
}
|
||||
|
||||
if (updateHistory){
|
||||
await crunInstance.History.UpdateWithMusicEpisodeList(musicVideos.Data);
|
||||
}
|
||||
|
||||
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){
|
||||
var mediaList = await FetchMediaListAsync($"{ApiUrls.Content}/{endpoint}/{id}", crLocale, forcedLang);
|
||||
|
||||
switch (mediaList.Total){
|
||||
case < 1:
|
||||
return null;
|
||||
case 1 when mediaList.Data != null:
|
||||
case 1 when mediaList.Data.Count > 0:
|
||||
return mediaList.Data.First();
|
||||
default:
|
||||
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 Helpers.Deserialize<CrunchyMusicVideoList>(response.ResponseContent, crunInstance.SettingsJsonSerializerSettings);
|
||||
return Helpers.Deserialize<CrunchyMusicVideoList>(response.ResponseContent, crunInstance.SettingsJsonSerializerSettings) ?? new CrunchyMusicVideoList();
|
||||
}
|
||||
|
||||
private NameValueCollection CreateQueryParameters(string crLocale, bool forcedLang){
|
||||
|
|
@ -88,15 +163,16 @@ public class CrMusic{
|
|||
public CrunchyEpMeta EpisodeMeta(CrunchyMusicVideo episodeP){
|
||||
var images = (episodeP.Images?.Thumbnail ?? new List<Image>{ new Image{ Source = "/notFound.png" } });
|
||||
|
||||
|
||||
var epMeta = new CrunchyEpMeta();
|
||||
epMeta.Data = new List<CrunchyEpMetaData>{ new(){ MediaId = episodeP.Id, Versions = null } };
|
||||
epMeta.SeriesTitle = "Music";
|
||||
epMeta.SeasonTitle = episodeP.DisplayArtistName;
|
||||
epMeta.SeriesTitle = episodeP.GetSeriesTitle();
|
||||
epMeta.SeasonTitle = episodeP.GetSeasonTitle();
|
||||
epMeta.EpisodeNumber = episodeP.SequenceNumber + "";
|
||||
epMeta.EpisodeTitle = episodeP.Title;
|
||||
epMeta.SeasonId = "";
|
||||
epMeta.EpisodeTitle = episodeP.GetEpisodeTitle();
|
||||
epMeta.SeasonId = episodeP.GetSeasonId();
|
||||
epMeta.Season = "";
|
||||
epMeta.ShowId = "";
|
||||
epMeta.SeriesId = episodeP.GetSeriesId();
|
||||
epMeta.AbsolutEpisodeNumberE = "";
|
||||
epMeta.Image = images[images.Count / 2].Source;
|
||||
epMeta.DownloadProgress = new DownloadProgress(){
|
||||
|
|
|
|||
|
|
@ -7,13 +7,14 @@ using System.Text.RegularExpressions;
|
|||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.Files;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Views;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace CRD.Downloader.Crunchyroll;
|
||||
|
||||
public class CrSeries(){
|
||||
public class CrSeries{
|
||||
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){
|
||||
|
|
@ -61,15 +62,15 @@ public class CrSeries(){
|
|||
|
||||
var epMeta = new CrunchyEpMeta();
|
||||
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.SeasonTitle = episode.Items.FirstOrDefault(a => !dubPattern.IsMatch(a.SeasonTitle)).SeasonTitle ?? Regex.Replace(episode.Items[0].SeasonTitle, @"\(\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.EpisodeNumber = item.Episode;
|
||||
epMeta.EpisodeTitle = item.Title;
|
||||
epMeta.SeasonId = item.SeasonId;
|
||||
epMeta.Season = Helpers.ExtractNumberAfterS(item.Identifier) ?? item.SeasonNumber + "";
|
||||
epMeta.ShowId = item.SeriesId;
|
||||
epMeta.SeriesId = item.SeriesId;
|
||||
epMeta.AbsolutEpisodeNumberE = epNum;
|
||||
epMeta.Image = images[images.Count / 2].FirstOrDefault().Source;
|
||||
epMeta.Image = images[images.Count / 2].FirstOrDefault()?.Source ?? "";
|
||||
epMeta.DownloadProgress = new DownloadProgress(){
|
||||
IsDownloading = false,
|
||||
Done = false,
|
||||
|
|
@ -98,7 +99,7 @@ public class CrSeries(){
|
|||
if (all is true || e != null && e.Contains(epNum)){
|
||||
if (ret.TryGetValue(key, out var epMe)){
|
||||
epMetaData.Lang = episode.Langs[index];
|
||||
epMe.Data?.Add(epMetaData);
|
||||
epMe.Data.Add(epMetaData);
|
||||
} else{
|
||||
epMetaData.Lang = episode.Langs[index];
|
||||
epMeta.Data[0] = epMetaData;
|
||||
|
|
@ -122,7 +123,7 @@ public class CrSeries(){
|
|||
|
||||
bool serieshasversions = true;
|
||||
|
||||
CrSeriesSearch? parsedSeries = await ParseSeriesById(id, crLocale,forcedLocale);
|
||||
CrSeriesSearch? parsedSeries = await ParseSeriesById(id, crLocale, forcedLocale);
|
||||
|
||||
if (parsedSeries == null || parsedSeries.Data == null){
|
||||
Console.Error.WriteLine("Parse Data Invalid");
|
||||
|
|
@ -131,66 +132,59 @@ public class CrSeries(){
|
|||
|
||||
// var result = ParseSeriesResult(parsedSeries);
|
||||
Dictionary<string, EpisodeAndLanguage> episodes = new Dictionary<string, EpisodeAndLanguage>();
|
||||
|
||||
|
||||
if (crunInstance.CrunOptions.History){
|
||||
crunInstance.History.CRUpdateSeries(id,"");
|
||||
_ = crunInstance.History.CrUpdateSeries(id, "");
|
||||
}
|
||||
|
||||
var cachedSeasonID = "";
|
||||
var cachedSeasonId = "";
|
||||
var seasonData = new CrunchyEpisodeList();
|
||||
|
||||
|
||||
foreach (var s in parsedSeries.Data){
|
||||
if (data?.S != null && s.Id != data.Value.S) continue;
|
||||
int fallbackIndex = 0;
|
||||
if (cachedSeasonID != s.Id){
|
||||
seasonData = await GetSeasonDataById(s.Id, forcedLocale ? crLocale : "");
|
||||
cachedSeasonID = s.Id;
|
||||
}
|
||||
|
||||
if (seasonData.Data != null){
|
||||
|
||||
if (data?.S != null && s.Id != data.S) continue;
|
||||
int fallbackIndex = 0;
|
||||
if (cachedSeasonId != s.Id){
|
||||
seasonData = await GetSeasonDataById(s.Id, forcedLocale ? crLocale : "");
|
||||
cachedSeasonId = s.Id;
|
||||
}
|
||||
|
||||
foreach (var episode in seasonData.Data){
|
||||
// Prepare the episode array
|
||||
EpisodeAndLanguage item;
|
||||
if (seasonData.Data != null){
|
||||
foreach (var episode in seasonData.Data){
|
||||
// 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 episodeKey = $"{seasonIdentifier}E{episodeNum}";
|
||||
var seasonIdentifier = !string.IsNullOrEmpty(s.Identifier) ? s.Identifier.Split('|')[1] : $"S{episode.SeasonNumber}";
|
||||
var episodeKey = $"{seasonIdentifier}E{episodeNum}";
|
||||
|
||||
if (!episodes.ContainsKey(episodeKey)){
|
||||
item = new EpisodeAndLanguage{
|
||||
Items = new List<CrunchyEpisode>(),
|
||||
Langs = new List<LanguageItem>()
|
||||
};
|
||||
episodes[episodeKey] = item;
|
||||
} else{
|
||||
item = episodes[episodeKey];
|
||||
}
|
||||
if (!episodes.ContainsKey(episodeKey)){
|
||||
item = new EpisodeAndLanguage{
|
||||
Items = new List<CrunchyEpisode>(),
|
||||
Langs = new List<LanguageItem>()
|
||||
};
|
||||
episodes[episodeKey] = item;
|
||||
} else{
|
||||
item = episodes[episodeKey];
|
||||
}
|
||||
|
||||
if (episode.Versions != null){
|
||||
foreach (var version in episode.Versions){
|
||||
// Ensure there is only one of the same language
|
||||
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
|
||||
if (episode.Versions != null){
|
||||
foreach (var version in episode.Versions){
|
||||
if (item.Langs.All(a => a.CrLocale != version.AudioLocale)){
|
||||
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){
|
||||
|
|
@ -215,21 +209,21 @@ public class CrSeries(){
|
|||
|
||||
string newKey;
|
||||
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{
|
||||
newKey = $"{(isSpecial ? "SP" : 'E')}{(isSpecial ? (specialIndex + " " + item.Items[0].Id) : epIndex + "")}";
|
||||
}
|
||||
|
||||
|
||||
|
||||
episodes.Remove(key);
|
||||
|
||||
|
||||
int counter = 1;
|
||||
string originalKey = newKey;
|
||||
while (episodes.ContainsKey(newKey)){
|
||||
newKey = originalKey + "_" + counter;
|
||||
counter++;
|
||||
}
|
||||
|
||||
|
||||
episodes.Add(newKey, item);
|
||||
|
||||
if (isSpecial){
|
||||
|
|
@ -249,14 +243,14 @@ public class CrSeries(){
|
|||
var key = kvp.Key;
|
||||
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();
|
||||
|
||||
var title = item.Items[0].Title;
|
||||
var seasonNumber = Helpers.ExtractNumberAfterS(item.Items[0].Identifier) ?? item.Items[0].SeasonNumber.ToString();
|
||||
|
||||
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)}]");
|
||||
}
|
||||
|
|
@ -275,7 +269,7 @@ public class CrSeries(){
|
|||
var seconds = (int)Math.Floor(value.Items[0].DurationMs / 1000.0);
|
||||
var langList = value.Langs.Select(a => a.CrLocale).ToList();
|
||||
Languages.SortListByLangList(langList);
|
||||
|
||||
|
||||
return new Episode{
|
||||
E = key.StartsWith("E") ? key.Substring(1) : key,
|
||||
Lang = langList,
|
||||
|
|
@ -285,8 +279,9 @@ public class CrSeries(){
|
|||
SeasonTitle = Regex.Replace(value.Items[0].SeasonTitle, @"\(\w+ Dub\)", "").TrimEnd(),
|
||||
EpisodeNum = key.StartsWith("SP") ? key : value.Items[0].EpisodeNumber?.ToString() ?? value.Items[0].Episode ?? "?",
|
||||
Id = value.Items[0].SeasonId,
|
||||
Img = images[images.Count / 2].FirstOrDefault().Source,
|
||||
Img = images[images.Count / 2].FirstOrDefault()?.Source ?? "",
|
||||
Description = value.Items[0].Description,
|
||||
EpisodeType = EpisodeType.Episode,
|
||||
Time = $"{seconds / 60}:{seconds % 60:D2}" // Ensures two digits for seconds.
|
||||
};
|
||||
}).ToList();
|
||||
|
|
@ -294,7 +289,7 @@ public class CrSeries(){
|
|||
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() };
|
||||
|
||||
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);
|
||||
|
||||
|
|
@ -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);
|
||||
|
||||
if (!episodeRequestResponse.IsOk){
|
||||
Console.Error.WriteLine($"Episode List Request FAILED! uri: {episodeRequest.RequestUri}");
|
||||
} 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){
|
||||
|
|
@ -351,6 +347,8 @@ public class CrSeries(){
|
|||
var ret = new Dictionary<int, Dictionary<string, SeriesSearchItem>>();
|
||||
int i = 0;
|
||||
|
||||
if (seasonsList.Data == null) return ret;
|
||||
|
||||
foreach (var item in seasonsList.Data){
|
||||
i++;
|
||||
foreach (var lang in Languages.languages){
|
||||
|
|
@ -482,12 +480,12 @@ public class CrSeries(){
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return series;
|
||||
}
|
||||
|
||||
public async Task<CrBrowseSeriesBase?> GetAllSeries(string? crLocale){
|
||||
CrBrowseSeriesBase? complete = new CrBrowseSeriesBase();
|
||||
CrBrowseSeriesBase complete = new CrBrowseSeriesBase();
|
||||
complete.Data =[];
|
||||
|
||||
var i = 0;
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ using CRD.Utils.Sonarr;
|
|||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.Crunchyroll;
|
||||
using CRD.Utils.Structs.History;
|
||||
using CRD.ViewModels;
|
||||
using CRD.ViewModels.Utils;
|
||||
using CRD.Views;
|
||||
using CRD.Views.Utils;
|
||||
|
|
@ -130,9 +131,75 @@ public class CrunchyrollManager{
|
|||
options.BackgroundImageOpacity = 0.5;
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
@ -147,7 +214,6 @@ public class CrunchyrollManager{
|
|||
CrMusic = new CrMusic();
|
||||
History = new History();
|
||||
|
||||
|
||||
Profile = new CrProfile{
|
||||
Username = "???",
|
||||
Avatar = "crbrand_avatars_logo_marks_mangagirl_taupe.png",
|
||||
|
|
@ -155,6 +221,11 @@ public class CrunchyrollManager{
|
|||
PreferredContentSubtitleLanguage = DefaultLocale,
|
||||
HasPremium = false,
|
||||
};
|
||||
|
||||
if (Path.Exists(CfgManager.PathCrDownloadOptionsOld)){
|
||||
CfgManager.WriteCrSettings();
|
||||
Helpers.DeleteFile(CfgManager.PathCrDownloadOptionsOld);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Init(){
|
||||
|
|
@ -183,8 +254,11 @@ public class CrunchyrollManager{
|
|||
}
|
||||
|
||||
if (CfgManager.CheckIfFileExists(CfgManager.PathCrToken)){
|
||||
Token = CfgManager.DeserializeFromFile<CrToken>(CfgManager.PathCrToken);
|
||||
Token = CfgManager.ReadJsonFromFile<CrToken>(CfgManager.PathCrToken);
|
||||
await CrAuth.LoginWithToken();
|
||||
if (Path.Exists(CfgManager.PathCrTokenOld)){
|
||||
Helpers.DeleteFile(CfgManager.PathCrTokenOld);
|
||||
}
|
||||
} else{
|
||||
await CrAuth.AuthAnonymous();
|
||||
}
|
||||
|
|
@ -200,12 +274,11 @@ public class CrunchyrollManager{
|
|||
);
|
||||
|
||||
if (historyList != null){
|
||||
|
||||
HistoryList = historyList;
|
||||
|
||||
|
||||
Parallel.ForEach(historyList, historySeries => {
|
||||
historySeries.Init();
|
||||
|
||||
|
||||
foreach (var historySeriesSeason in historySeries.Seasons){
|
||||
historySeriesSeason.Init();
|
||||
}
|
||||
|
|
@ -318,11 +391,13 @@ public class CrunchyrollManager{
|
|||
|
||||
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){
|
||||
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{
|
||||
|
|
@ -364,11 +439,12 @@ public class CrunchyrollManager{
|
|||
|
||||
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){
|
||||
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.Queue.Refresh();
|
||||
|
||||
if (options.History && data.Data != null && data.Data.Count > 0){
|
||||
History.SetAsDownloaded(data.ShowId, data.SeasonId, data.Data.First().MediaId);
|
||||
if (options.History && data.Data is{ Count: > 0 } && (options.HistoryIncludeCrArtists && data.Music || !data.Music)){
|
||||
History.SetAsDownloaded(data.SeriesId, data.SeasonId, data.Data.First().MediaId);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -473,9 +549,9 @@ public class CrunchyrollManager{
|
|||
? options.DownloadDirPath
|
||||
: 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)){
|
||||
Console.WriteLine("Invalid destination directory path.");
|
||||
return;
|
||||
|
|
@ -519,7 +595,7 @@ public class CrunchyrollManager{
|
|||
foreach (var downloadedMedia in subs){
|
||||
var subt = new SubtitleFonts();
|
||||
subt.Language = downloadedMedia.Language;
|
||||
subt.Fonts = downloadedMedia.Fonts;
|
||||
subt.Fonts = downloadedMedia.Fonts ??[];
|
||||
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){
|
||||
|
|
@ -725,7 +803,15 @@ public class CrunchyrollManager{
|
|||
bool dlVideoOnce = false;
|
||||
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){
|
||||
Console.WriteLine($"Requesting: [{epMeta.MediaId}] {mediaName}");
|
||||
|
||||
|
|
@ -753,10 +839,10 @@ public class CrunchyrollManager{
|
|||
string mediaGuid = currentMediaId;
|
||||
if (epMeta.Versions != 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 }){
|
||||
LanguageItem lang = Array.Find(Languages.languages, a => a.CrLocale == data.SelectedDubs[0]);
|
||||
currentVersion = epMeta.Versions.Find(a => a.AudioLocale == lang.CrLocale);
|
||||
LanguageItem? lang = Array.Find(Languages.languages, a => a.CrLocale == data.SelectedDubs[0]);
|
||||
currentVersion = epMeta.Versions.Find(a => a.AudioLocale == lang?.CrLocale) ?? currentVersion;
|
||||
} else if (epMeta.Versions.Count == 1){
|
||||
currentVersion = epMeta.Versions[0];
|
||||
}
|
||||
|
|
@ -772,7 +858,7 @@ public class CrunchyrollManager{
|
|||
mediaGuid = currentVersion.Guid;
|
||||
|
||||
if (!isPrimary){
|
||||
primaryVersion = epMeta.Versions.Find(a => a.Original);
|
||||
primaryVersion = epMeta.Versions.Find(a => a.Original) ?? currentVersion;
|
||||
} else{
|
||||
primaryVersion = currentVersion;
|
||||
}
|
||||
|
|
@ -816,12 +902,12 @@ public class CrunchyrollManager{
|
|||
Data = new List<DownloadedMedia>(),
|
||||
Error = true,
|
||||
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{
|
||||
Data = new List<DownloadedMedia>(),
|
||||
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 = 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());
|
||||
string tempTsFile = Path.IsPathRooted(tempFile) ? tempFile : Path.Combine(fileDir, tempFile);
|
||||
|
||||
|
|
@ -1207,7 +1294,7 @@ public class CrunchyrollManager{
|
|||
} else if (options.Novids){
|
||||
Console.WriteLine("Skipping video download...");
|
||||
} 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;
|
||||
|
||||
|
|
@ -1226,7 +1313,7 @@ public class CrunchyrollManager{
|
|||
|
||||
|
||||
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;
|
||||
|
||||
|
|
@ -1403,7 +1490,7 @@ public class CrunchyrollManager{
|
|||
videoDownloadMedia = new DownloadedMedia{
|
||||
Type = syncTimingDownload ? DownloadMediaType.SyncVideo : DownloadMediaType.Video,
|
||||
Path = $"{tsFile}.video.m4s",
|
||||
Lang = lang.Value,
|
||||
Lang = lang,
|
||||
IsPrimary = isPrimary
|
||||
};
|
||||
files.Add(videoDownloadMedia);
|
||||
|
|
@ -1471,7 +1558,7 @@ public class CrunchyrollManager{
|
|||
files.Add(new DownloadedMedia{
|
||||
Type = DownloadMediaType.Audio,
|
||||
Path = $"{tsFile}.audio.m4s",
|
||||
Lang = lang.Value,
|
||||
Lang = lang,
|
||||
IsPrimary = isPrimary
|
||||
});
|
||||
data.downloadedFiles.Add($"{tsFile}.audio.m4s");
|
||||
|
|
@ -1487,7 +1574,7 @@ public class CrunchyrollManager{
|
|||
videoDownloadMedia = new DownloadedMedia{
|
||||
Type = syncTimingDownload ? DownloadMediaType.SyncVideo : DownloadMediaType.Video,
|
||||
Path = $"{tsFile}.video.m4s",
|
||||
Lang = lang.Value,
|
||||
Lang = lang,
|
||||
IsPrimary = isPrimary
|
||||
};
|
||||
files.Add(videoDownloadMedia);
|
||||
|
|
@ -1498,7 +1585,7 @@ public class CrunchyrollManager{
|
|||
files.Add(new DownloadedMedia{
|
||||
Type = DownloadMediaType.Audio,
|
||||
Path = $"{tsFile}.audio.m4s",
|
||||
Lang = lang.Value,
|
||||
Lang = lang,
|
||||
IsPrimary = isPrimary
|
||||
});
|
||||
data.downloadedFiles.Add($"{tsFile}.audio.m4s");
|
||||
|
|
@ -1547,7 +1634,13 @@ public class CrunchyrollManager{
|
|||
}
|
||||
|
||||
// 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"){
|
||||
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){
|
||||
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{
|
||||
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){
|
||||
if (pbData.Meta != null && (pbData.Meta.Subtitles is{ Count: > 0 } || pbData.Meta.Captions is{ Count: > 0 })){
|
||||
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){
|
||||
// Prepare for video download
|
||||
int totalParts = chosenVideoSegments.segments.Count;
|
||||
int mathParts = (int)Math.Ceiling((double)totalParts / options.Partsize);
|
||||
string mathMsg = $"({mathParts}*{options.Partsize})";
|
||||
string tsFile;
|
||||
Console.WriteLine($"Total parts in video stream: {totalParts} {mathMsg}");
|
||||
|
||||
if (Path.IsPathRooted(outFile)){
|
||||
|
|
@ -1843,9 +1937,10 @@ public class CrunchyrollManager{
|
|||
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){
|
||||
// Prepare for audio download
|
||||
string tsFile;
|
||||
int totalParts = chosenAudioSegments.segments.Count;
|
||||
int mathParts = (int)Math.Ceiling((double)totalParts / options.Partsize);
|
||||
string mathMsg = $"({mathParts}*{options.Partsize})";
|
||||
|
|
@ -1911,7 +2006,7 @@ public class CrunchyrollManager{
|
|||
var playbackRequestResponse = await SendPlaybackRequestAsync(playbackEndpoint);
|
||||
|
||||
if (!playbackRequestResponse.IsOk){
|
||||
playbackRequestResponse = await HandleStreamErrorsAsync(playbackRequestResponse, mediaGuidId, playbackEndpoint);
|
||||
playbackRequestResponse = await HandleStreamErrorsAsync(playbackRequestResponse, playbackEndpoint);
|
||||
}
|
||||
|
||||
if (playbackRequestResponse.IsOk){
|
||||
|
|
@ -1922,7 +2017,7 @@ public class CrunchyrollManager{
|
|||
playbackRequestResponse = await SendPlaybackRequestAsync(playbackEndpoint);
|
||||
|
||||
if (!playbackRequestResponse.IsOk){
|
||||
playbackRequestResponse = await HandleStreamErrorsAsync(playbackRequestResponse, mediaGuidId, playbackEndpoint);
|
||||
playbackRequestResponse = await HandleStreamErrorsAsync(playbackRequestResponse, playbackEndpoint);
|
||||
}
|
||||
|
||||
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){
|
||||
|
|
@ -1940,7 +2035,7 @@ public class CrunchyrollManager{
|
|||
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;
|
||||
|
||||
var error = StreamError.FromJson(response.ResponseContent);
|
||||
|
|
@ -1990,7 +2085,7 @@ public class CrunchyrollManager{
|
|||
temppbData.Meta = new PlaybackMeta{
|
||||
AudioLocale = playStream.AudioLocale,
|
||||
Versions = playStream.Versions,
|
||||
Bifs = new List<string>{ playStream.Bifs },
|
||||
Bifs = new List<string>{ playStream.Bifs ?? "" },
|
||||
MediaId = mediaId,
|
||||
Captions = playStream.Captions,
|
||||
Subtitles = new Subtitles()
|
||||
|
|
@ -2012,7 +2107,7 @@ public class CrunchyrollManager{
|
|||
#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 showRequestResponse = await HttpClientReq.Instance.SendHttpRequest(showRequest, true);
|
||||
|
|
@ -2024,11 +2119,11 @@ public class CrunchyrollManager{
|
|||
try{
|
||||
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>();
|
||||
}
|
||||
|
||||
if (jObject.TryGetValue("mediaId", out JToken mediaIdToken)){
|
||||
if (jObject.TryGetValue("mediaId", out JToken? mediaIdToken)){
|
||||
chapterData.mediaId = mediaIdToken.ToObject<string>();
|
||||
}
|
||||
|
||||
|
|
@ -2037,7 +2132,7 @@ public class CrunchyrollManager{
|
|||
foreach (var property in jObject.Properties()){
|
||||
if (property.Value.Type == JTokenType.Object && property.Name != "lastUpdate" && property.Name != "mediaId"){
|
||||
try{
|
||||
CrunchyChapter chapter = property.Value.ToObject<CrunchyChapter>();
|
||||
CrunchyChapter chapter = property.Value.ToObject<CrunchyChapter>() ?? new CrunchyChapter();
|
||||
chapterData.Chapters.Add(chapter);
|
||||
} catch (Exception ex){
|
||||
Console.Error.WriteLine($"Error parsing chapter: {ex.Message}");
|
||||
|
|
@ -2046,7 +2141,7 @@ public class CrunchyrollManager{
|
|||
}
|
||||
} catch (Exception ex){
|
||||
Console.Error.WriteLine($"Error parsing JSON response: {ex.Message}");
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (chapterData.Chapters.Count > 0){
|
||||
|
|
@ -2098,8 +2193,6 @@ public class CrunchyrollManager{
|
|||
compiledChapters.Add($"CHAPTER{chapterNumber}NAME={formattedChapterType} End");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} else{
|
||||
Console.WriteLine("Chapter request failed, attempting old API ");
|
||||
|
|
@ -2109,7 +2202,7 @@ public class CrunchyrollManager{
|
|||
showRequestResponse = await HttpClientReq.Instance.SendHttpRequest(showRequest, true);
|
||||
|
||||
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);
|
||||
|
||||
|
|
@ -2137,13 +2230,10 @@ public class CrunchyrollManager{
|
|||
chapterNumber = (compiledChapters.Count / 2) + 1;
|
||||
compiledChapters.Add($"CHAPTER{chapterNumber}={endFormatted}");
|
||||
compiledChapters.Add($"CHAPTER{chapterNumber}NAME=Episode");
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine("Chapter request failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -12,8 +12,10 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
|||
using CommunityToolkit.Mvvm.Input;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.Ffmpeg_Encoding;
|
||||
using CRD.Utils.Files;
|
||||
using CRD.Utils.Sonarr;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.Crunchyroll;
|
||||
using CRD.Utils.Structs.History;
|
||||
using CRD.ViewModels;
|
||||
using CRD.ViewModels.Utils;
|
||||
|
|
@ -42,7 +44,7 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
|
||||
[ObservableProperty]
|
||||
private bool _includeCcSubs;
|
||||
|
||||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem _selectedScaledBorderAndShadow;
|
||||
|
||||
|
|
@ -77,10 +79,10 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
|
||||
[ObservableProperty]
|
||||
private bool _skipSubMux;
|
||||
|
||||
|
||||
[ObservableProperty]
|
||||
private double? _leadingNumbers;
|
||||
|
||||
|
||||
[ObservableProperty]
|
||||
private double? _partSize;
|
||||
|
||||
|
|
@ -107,7 +109,7 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem _selectedHSLang;
|
||||
|
||||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem _selectedDescriptionLang;
|
||||
|
||||
|
|
@ -131,81 +133,78 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem? _selectedAudioQuality;
|
||||
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<ListBoxItem> _selectedSubLang = new();
|
||||
|
||||
private ObservableCollection<ListBoxItem> _selectedSubLang =[];
|
||||
|
||||
[ObservableProperty]
|
||||
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(){
|
||||
new ComboBoxItem(){ Content = "best" },
|
||||
new ComboBoxItem(){ Content = "128kB/s" },
|
||||
new ComboBoxItem(){ Content = "96kB/s" },
|
||||
new ComboBoxItem(){ Content = "64kB/s" },
|
||||
new ComboBoxItem(){ Content = "worst" },
|
||||
};
|
||||
public ObservableCollection<ComboBoxItem> VideoQualityList{ get; } =[
|
||||
new(){ Content = "best" },
|
||||
new(){ Content = "1080" },
|
||||
new(){ Content = "720" },
|
||||
new(){ Content = "480" },
|
||||
new(){ Content = "360" },
|
||||
new(){ Content = "240" },
|
||||
new(){ Content = "worst" }
|
||||
];
|
||||
|
||||
public ObservableCollection<ComboBoxItem> HardSubLangList{ get; } = new(){
|
||||
new ComboBoxItem(){ Content = "none" },
|
||||
};
|
||||
|
||||
public ObservableCollection<ComboBoxItem> DescriptionLangList{ get; } = new(){
|
||||
new ComboBoxItem(){ Content = "default" },
|
||||
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<ComboBoxItem> AudioQualityList{ get; } =[
|
||||
new(){ Content = "best" },
|
||||
new(){ Content = "128kB/s" },
|
||||
new(){ Content = "96kB/s" },
|
||||
new(){ Content = "64kB/s" },
|
||||
new(){ Content = "worst" }
|
||||
];
|
||||
|
||||
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(){
|
||||
new ListBoxItem(){ Content = "all" },
|
||||
new ListBoxItem(){ Content = "none" },
|
||||
};
|
||||
public ObservableCollection<ListBoxItem> SubLangList{ get; } =[
|
||||
new(){ Content = "all" },
|
||||
new(){ Content = "none" }
|
||||
];
|
||||
|
||||
public ObservableCollection<ComboBoxItem> StreamEndpoints{ get; } = new(){
|
||||
new ComboBoxItem(){ Content = "web/firefox" },
|
||||
new ComboBoxItem(){ Content = "console/switch" },
|
||||
new ComboBoxItem(){ Content = "console/ps4" },
|
||||
new ComboBoxItem(){ Content = "console/ps5" },
|
||||
new ComboBoxItem(){ Content = "console/xbox_one" },
|
||||
new ComboBoxItem(){ Content = "web/edge" },
|
||||
// new ComboBoxItem(){ Content = "web/safari" },
|
||||
new ComboBoxItem(){ Content = "web/chrome" },
|
||||
new ComboBoxItem(){ Content = "web/fallback" },
|
||||
// new ComboBoxItem(){ Content = "ios/iphone" },
|
||||
// new ComboBoxItem(){ Content = "ios/ipad" },
|
||||
new ComboBoxItem(){ Content = "android/phone" },
|
||||
new ComboBoxItem(){ Content = "tv/samsung" },
|
||||
};
|
||||
public ObservableCollection<ComboBoxItem> StreamEndpoints{ get; } =[
|
||||
new(){ Content = "web/firefox" },
|
||||
new(){ Content = "console/switch" },
|
||||
new(){ Content = "console/ps4" },
|
||||
new(){ Content = "console/ps5" },
|
||||
new(){ Content = "console/xbox_one" },
|
||||
new(){ Content = "web/edge" },
|
||||
// new (){ Content = "web/safari" },
|
||||
new(){ Content = "web/chrome" },
|
||||
new(){ Content = "web/fallback" },
|
||||
// new (){ Content = "ios/iphone" },
|
||||
// new (){ Content = "ios/ipad" },
|
||||
new(){ Content = "android/phone" },
|
||||
new(){ Content = "tv/samsung" }
|
||||
];
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isEncodeEnabled;
|
||||
|
|
@ -214,8 +213,8 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
private StringItem _selectedEncodingPreset;
|
||||
|
||||
public ObservableCollection<StringItem> EncodingPresetsList{ get; } = new();
|
||||
|
||||
|
||||
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _cCSubsMuxingFlag;
|
||||
|
||||
|
|
@ -224,11 +223,13 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
|
||||
[ObservableProperty]
|
||||
private bool _signsSubsAsForced;
|
||||
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _searchFetchFeaturedMusic;
|
||||
|
||||
private bool settingsLoaded;
|
||||
|
||||
|
||||
public CrunchyrollSettingsViewModel(){
|
||||
|
||||
foreach (var languageItem in Languages.languages){
|
||||
HardSubLangList.Add(new ComboBoxItem{ Content = languageItem.CrLocale });
|
||||
SubLangList.Add(new ListBoxItem{ Content = languageItem.CrLocale });
|
||||
|
|
@ -244,7 +245,7 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
|
||||
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];
|
||||
|
||||
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){
|
||||
SelectedDubLang.Add(listBoxItem);
|
||||
}
|
||||
|
||||
|
||||
AddScaledBorderAndShadow = options.SubsAddScaledBorder is ScaledBorderAndShadowSelection.ScaledBorderAndShadowNo or ScaledBorderAndShadowSelection.ScaledBorderAndShadowYes;
|
||||
SelectedScaledBorderAndShadow = GetScaledBorderAndShadowFromOptions(options);
|
||||
|
||||
|
|
@ -301,22 +302,23 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
SkipSubMux = options.SkipSubsMux;
|
||||
LeadingNumbers = options.Numbers;
|
||||
FileName = options.FileName;
|
||||
SearchFetchFeaturedMusic = options.SearchFetchFeaturedMusic;
|
||||
|
||||
ComboBoxItem? qualityAudio = AudioQualityList.FirstOrDefault(a => a.Content != null && (string)a.Content == options.QualityAudio) ?? null;
|
||||
SelectedAudioQuality = qualityAudio ?? AudioQualityList[0];
|
||||
|
||||
ComboBoxItem? qualityVideo = VideoQualityList.FirstOrDefault(a => a.Content != null && (string)a.Content == options.QualityVideo) ?? null;
|
||||
SelectedVideoQuality = qualityVideo ?? VideoQualityList[0];
|
||||
|
||||
|
||||
MkvMergeOptions.Clear();
|
||||
if (options.MkvmergeOptions != null){
|
||||
if (options.MkvmergeOptions is{ Count: > 0 }){
|
||||
foreach (var mkvmergeParam in options.MkvmergeOptions){
|
||||
MkvMergeOptions.Add(new StringItem(){ stringValue = mkvmergeParam });
|
||||
}
|
||||
}
|
||||
|
||||
FfmpegOptions.Clear();
|
||||
if (options.FfmpegOptions != null){
|
||||
if (options.FfmpegOptions is{ Count: > 0 }){
|
||||
foreach (var ffmpegParam in options.FfmpegOptions){
|
||||
FfmpegOptions.Add(new StringItem(){ stringValue = ffmpegParam });
|
||||
}
|
||||
|
|
@ -341,7 +343,7 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
if (!settingsLoaded){
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.SignsSubsAsForced = SignsSubsAsForced;
|
||||
CrunchyrollManager.Instance.CrunOptions.CcSubsMuxingFlag = CCSubsMuxingFlag;
|
||||
CrunchyrollManager.Instance.CrunOptions.CcSubsFont = CCSubsFont;
|
||||
|
|
@ -364,8 +366,9 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
CrunchyrollManager.Instance.CrunOptions.FileName = FileName;
|
||||
CrunchyrollManager.Instance.CrunOptions.IncludeSignsSubs = IncludeSignSubs;
|
||||
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();
|
||||
|
||||
List<string> softSubs = new List<string>();
|
||||
|
|
@ -378,7 +381,7 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
string descLang = SelectedDescriptionLang.Content + "";
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.DescriptionLang = descLang != "default" ? descLang : CrunchyrollManager.Instance.DefaultLocale;
|
||||
|
||||
|
||||
string hslang = SelectedHSLang.Content + "";
|
||||
|
||||
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.QualityVideo = SelectedVideoQuality?.Content + "";
|
||||
|
||||
|
||||
List<string> mkvmergeParams = new List<string>();
|
||||
foreach (var mkvmergeParam in MkvMergeOptions){
|
||||
mkvmergeParams.Add(mkvmergeParam.stringValue);
|
||||
|
|
@ -413,7 +416,7 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
|
||||
CrunchyrollManager.Instance.CrunOptions.FfmpegOptions = ffmpegParams;
|
||||
|
||||
CfgManager.WriteSettingsToFile();
|
||||
CfgManager.WriteCrSettings();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -469,7 +472,7 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
FfmpegOptions.Remove(param);
|
||||
RaisePropertyChanged(nameof(FfmpegOptions));
|
||||
}
|
||||
|
||||
|
||||
private void Changes(object? sender, NotifyCollectionChangedEventArgs e){
|
||||
UpdateSettings();
|
||||
|
||||
|
|
@ -515,7 +518,6 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
CrunchyrollManager.Instance.HistoryList =[];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
|
@ -542,9 +544,8 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
}
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -191,6 +191,22 @@
|
|||
</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"
|
||||
IconSource="Download"
|
||||
Description="Adjust download settings"
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.Files;
|
||||
using CRD.Utils.Sonarr;
|
||||
using CRD.Utils.Sonarr.Models;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.Crunchyroll.Music;
|
||||
using CRD.Utils.Structs.History;
|
||||
using CRD.Views;
|
||||
using DynamicData;
|
||||
|
|
@ -16,10 +17,10 @@ using ReactiveUI;
|
|||
|
||||
namespace CRD.Downloader;
|
||||
|
||||
public class History(){
|
||||
public class History{
|
||||
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);
|
||||
|
||||
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);
|
||||
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){
|
||||
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){
|
||||
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 SeriesDataCache? cachedSeries;
|
||||
|
||||
private async Task RefreshSeriesData(string seriesId, HistorySeries historySeries){
|
||||
if (cachedSeries == null || (cachedSeries.Data != null && cachedSeries.Data.First().Id != seriesId)){
|
||||
cachedSeries = await crunInstance.CrSeries.SeriesById(seriesId, string.IsNullOrEmpty(crunInstance.CrunOptions.HistoryLang) ? crunInstance.DefaultLocale : crunInstance.CrunOptions.HistoryLang, true);
|
||||
} else{
|
||||
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);
|
||||
if (cachedSeries == null || (!string.IsNullOrEmpty(cachedSeries.SeriesId) && cachedSeries.SeriesId != seriesId)){
|
||||
if (historySeries.SeriesType == SeriesType.Series){
|
||||
var seriesData = await crunInstance.CrSeries.SeriesById(seriesId, string.IsNullOrEmpty(crunInstance.CrunOptions.HistoryLang) ? crunInstance.DefaultLocale : crunInstance.CrunOptions.HistoryLang, true);
|
||||
if (seriesData is{ Data: not null }){
|
||||
var firstEpisode = seriesData.Data.First();
|
||||
cachedSeries = new SeriesDataCache{
|
||||
SeriesDescription = firstEpisode.Description,
|
||||
SeriesId = seriesId,
|
||||
SeriesTitle = firstEpisode.Title,
|
||||
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
|
||||
? CrunchyrollManager.Instance.HistoryList
|
||||
.OrderByDescending(s => {
|
||||
var date = ParseDate(s.SonarrNextAirDate, today);
|
||||
return date.HasValue ? date.Value : DateTime.MinValue;
|
||||
var date = ParseDate(s.SonarrNextAirDate ?? string.Empty, today);
|
||||
return date ?? DateTime.MinValue;
|
||||
})
|
||||
.ThenByDescending(s => s.SonarrNextAirDate == "Today" ? 1 : 0)
|
||||
.ThenBy(s => string.IsNullOrEmpty(s.SonarrNextAirDate) ? 1 : 0)
|
||||
|
|
@ -452,8 +471,8 @@ public class History(){
|
|||
.OrderByDescending(s => s.SonarrNextAirDate == "Today")
|
||||
.ThenBy(s => s.SonarrNextAirDate == "Today" ? s.SeriesTitle : null)
|
||||
.ThenBy(s => {
|
||||
var date = ParseDate(s.SonarrNextAirDate, today);
|
||||
return date.HasValue ? date.Value : DateTime.MaxValue;
|
||||
var date = ParseDate(s.SonarrNextAirDate ?? string.Empty, today);
|
||||
return date ?? DateTime.MaxValue;
|
||||
})
|
||||
.ThenBy(s => s.SeriesTitle)
|
||||
.ToList();
|
||||
|
|
@ -499,55 +518,40 @@ public class History(){
|
|||
private string GetSeriesThumbnail(CrSeriesBase series){
|
||||
// var series = await crunInstance.CrSeries.SeriesById(seriesId);
|
||||
|
||||
if ((series.Data ?? Array.Empty<SeriesBaseItem>()).First().Images.PosterTall?.Count > 0){
|
||||
return series.Data.First().Images.PosterTall.First().First(e => e.Height == 360).Source;
|
||||
if (series.Data != null && series.Data.First().Images.PosterTall?.Count > 0){
|
||||
var imagesPosterTall = series.Data.First().Images.PosterTall;
|
||||
if (imagesPosterTall != null) return imagesPosterTall.First().First(e => e.Height == 360).Source;
|
||||
}
|
||||
|
||||
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
|
||||
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){
|
||||
private HistorySeason NewHistorySeason(List<IHistorySource> episodeList, IHistorySource firstEpisode){
|
||||
var newSeason = new HistorySeason{
|
||||
SeasonTitle = firstEpisode.SeasonTitle,
|
||||
SeasonId = firstEpisode.SeasonId,
|
||||
SeasonNum = Helpers.ExtractNumberAfterS(firstEpisode.Identifier) ?? firstEpisode.SeasonNumber + "",
|
||||
SeasonTitle = firstEpisode.GetSeasonTitle(),
|
||||
SeasonId = firstEpisode.GetSeasonId(),
|
||||
SeasonNum = firstEpisode.GetSeasonNum(),
|
||||
EpisodesList =[],
|
||||
SpecialSeason = CheckStringForSpecial(firstEpisode.Identifier)
|
||||
SpecialSeason = firstEpisode.IsSpecialSeason()
|
||||
};
|
||||
|
||||
foreach (var crunchyEpisode in seasonData){
|
||||
var langList = new List<string>();
|
||||
|
||||
if (crunchyEpisode.Versions != null){
|
||||
langList.AddRange(crunchyEpisode.Versions.Select(version => version.AudioLocale));
|
||||
} else{
|
||||
langList.Add(crunchyEpisode.AudioLocale);
|
||||
foreach (var historySource in episodeList){
|
||||
if (historySource.GetSeasonId() != newSeason.SeasonId){
|
||||
continue;
|
||||
}
|
||||
|
||||
Languages.SortListByLangList(langList);
|
||||
|
||||
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 = langList,
|
||||
HistoryEpisodeAvailableSoftSubs = crunchyEpisode.SubtitleLocales,
|
||||
EpisodeCrPremiumAirDate = crunchyEpisode.PremiumAvailableDate
|
||||
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()
|
||||
};
|
||||
|
||||
newSeason.EpisodesList.Add(newHistoryEpisode);
|
||||
|
|
@ -563,7 +567,7 @@ public class History(){
|
|||
|
||||
foreach (var historySeries in crunInstance.HistoryList){
|
||||
if (updateAll || string.IsNullOrEmpty(historySeries.SonarrSeriesId)){
|
||||
var sonarrSeries = FindClosestMatch(historySeries.SeriesTitle);
|
||||
var sonarrSeries = FindClosestMatch(historySeries.SeriesTitle ?? string.Empty);
|
||||
if (sonarrSeries != null){
|
||||
historySeries.SonarrSeriesId = sonarrSeries.Id + "";
|
||||
historySeries.SonarrTvDbId = sonarrSeries.TvdbId + "";
|
||||
|
|
@ -581,7 +585,7 @@ public class History(){
|
|||
}
|
||||
|
||||
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);
|
||||
|
||||
|
|
@ -603,8 +607,10 @@ public class History(){
|
|||
historyEpisode.SonarrEpisodeId = episode.Id + "";
|
||||
historyEpisode.SonarrEpisodeNumber = episode.EpisodeNumber + "";
|
||||
historyEpisode.SonarrHasFile = episode.HasFile;
|
||||
historyEpisode.SonarrIsMonitored = episode.Monitored;
|
||||
historyEpisode.SonarrAbsolutNumber = episode.AbsoluteEpisodeNumber + "";
|
||||
historyEpisode.SonarrSeasonNumber = episode.SeasonNumber + "";
|
||||
|
||||
lock (_lock){
|
||||
episodes.Remove(episode);
|
||||
}
|
||||
|
|
@ -622,8 +628,8 @@ public class History(){
|
|||
return false;
|
||||
}
|
||||
|
||||
var episodeNumberStr = ele.EpisodeNumber.ToString() ?? string.Empty;
|
||||
var seasonNumberStr = ele.SeasonNumber.ToString() ?? string.Empty;
|
||||
var episodeNumberStr = ele.EpisodeNumber.ToString();
|
||||
var seasonNumberStr = ele.SeasonNumber.ToString();
|
||||
|
||||
return episodeNumberStr == historyEpisode.Episode && seasonNumberStr == historyEpisode.EpisodeSeasonNum;
|
||||
});
|
||||
|
|
@ -631,6 +637,7 @@ public class History(){
|
|||
historyEpisode.SonarrEpisodeId = episode.Id + "";
|
||||
historyEpisode.SonarrEpisodeNumber = episode.EpisodeNumber + "";
|
||||
historyEpisode.SonarrHasFile = episode.HasFile;
|
||||
historyEpisode.SonarrIsMonitored = episode.Monitored;
|
||||
historyEpisode.SonarrAbsolutNumber = episode.AbsoluteEpisodeNumber + "";
|
||||
historyEpisode.SonarrSeasonNumber = episode.SeasonNumber + "";
|
||||
lock (_lock){
|
||||
|
|
@ -649,6 +656,7 @@ public class History(){
|
|||
historyEpisode.SonarrEpisodeId = episode1.Id + "";
|
||||
historyEpisode.SonarrEpisodeNumber = episode1.EpisodeNumber + "";
|
||||
historyEpisode.SonarrHasFile = episode1.HasFile;
|
||||
historyEpisode.SonarrIsMonitored = episode1.Monitored;
|
||||
historyEpisode.SonarrAbsolutNumber = episode1.AbsoluteEpisodeNumber + "";
|
||||
historyEpisode.SonarrSeasonNumber = episode1.SeasonNumber + "";
|
||||
lock (_lock){
|
||||
|
|
@ -666,6 +674,7 @@ public class History(){
|
|||
historyEpisode.SonarrEpisodeId = episode2.Id + "";
|
||||
historyEpisode.SonarrEpisodeNumber = episode2.EpisodeNumber + "";
|
||||
historyEpisode.SonarrHasFile = episode2.HasFile;
|
||||
historyEpisode.SonarrIsMonitored = episode2.Monitored;
|
||||
historyEpisode.SonarrAbsolutNumber = episode2.AbsoluteEpisodeNumber + "";
|
||||
historyEpisode.SonarrSeasonNumber = episode2.SeasonNumber + "";
|
||||
lock (_lock){
|
||||
|
|
@ -677,8 +686,6 @@ public class History(){
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -706,6 +713,10 @@ public class History(){
|
|||
}
|
||||
|
||||
private SonarrSeries? FindClosestMatch(string title){
|
||||
if (string.IsNullOrEmpty(title)){
|
||||
return null;
|
||||
}
|
||||
|
||||
SonarrSeries? closestMatch = null;
|
||||
double highestSimilarity = 0.0;
|
||||
|
||||
|
|
@ -748,7 +759,7 @@ public class History(){
|
|||
|
||||
Parallel.ForEach(episodeList, episode => {
|
||||
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
|
||||
{
|
||||
if (similarity > highestSimilarity){
|
||||
|
|
@ -810,13 +821,13 @@ public class History(){
|
|||
}
|
||||
|
||||
public class NumericStringPropertyComparer : IComparer<HistoryEpisode>{
|
||||
public int Compare(HistoryEpisode x, HistoryEpisode y){
|
||||
if (double.TryParse(x.Episode, NumberStyles.Any, CultureInfo.InvariantCulture, out double xDouble) &&
|
||||
double.TryParse(y.Episode, NumberStyles.Any, CultureInfo.InvariantCulture, out double yDouble)){
|
||||
public int Compare(HistoryEpisode? x, HistoryEpisode? y){
|
||||
if (double.TryParse(x?.Episode, NumberStyles.Any, CultureInfo.InvariantCulture, out double xDouble) &&
|
||||
double.TryParse(y?.Episode, NumberStyles.Any, CultureInfo.InvariantCulture, out double yDouble)){
|
||||
return xDouble.CompareTo(yDouble);
|
||||
}
|
||||
|
||||
// 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.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -13,7 +12,6 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
|||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.History;
|
||||
using CRD.Utils.Updater;
|
||||
using FluentAvalonia.Styling;
|
||||
|
||||
|
|
@ -51,10 +49,10 @@ public partial class ProgramManager : ObservableObject{
|
|||
private bool _updateAvailable = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _finishedLoading = false;
|
||||
private bool _finishedLoading;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _navigationLock = false;
|
||||
private bool _navigationLock;
|
||||
|
||||
#endregion
|
||||
|
||||
|
|
@ -66,7 +64,7 @@ public partial class ProgramManager : ObservableObject{
|
|||
|
||||
private Queue<Func<Task>> taskQueue = new Queue<Func<Task>>();
|
||||
|
||||
private bool exitOnTaskFinish = false;
|
||||
private bool exitOnTaskFinish;
|
||||
|
||||
public IStorageProvider StorageProvider;
|
||||
|
||||
|
|
@ -111,7 +109,7 @@ public partial class ProgramManager : ObservableObject{
|
|||
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...");
|
||||
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.OldItems != null)
|
||||
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){
|
||||
DownloadItemModels.Remove(downloadItem);
|
||||
} 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){
|
||||
if (string.IsNullOrEmpty(epId)){
|
||||
return;
|
||||
}
|
||||
|
||||
await CrunchyrollManager.Instance.CrAuth.RefreshToken(true);
|
||||
|
||||
var episodeL = await CrunchyrollManager.Instance.CrEpisode.ParseEpisodeById(epId, crLocale);
|
||||
|
||||
|
||||
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));
|
||||
return;
|
||||
}
|
||||
|
|
@ -206,6 +210,15 @@ public class QueueManager{
|
|||
|
||||
if (musicVideo != null){
|
||||
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);
|
||||
MessageBus.Current.SendMessage(new ToastMessage($"Added music video to the queue", ToastType.Information, 1));
|
||||
}
|
||||
|
|
@ -218,6 +231,14 @@ public class QueueManager{
|
|||
|
||||
if (concert != null){
|
||||
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);
|
||||
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()){
|
||||
if (crunchyEpMeta.Data?.First() != null){
|
||||
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 (historyEpisode.historyEpisode != null){
|
||||
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.DownloadSubs = subLangList.sublist.Count > 0 ? subLangList.sublist : CrunchyrollManager.Instance.CrunOptions.DlSubs;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ using ProtoBuf;
|
|||
|
||||
namespace CRD.Utils.DRM;
|
||||
|
||||
public struct ContentDecryptionModule{
|
||||
public class ContentDecryptionModule{
|
||||
public byte[] privateKey{ get; set; }
|
||||
public byte[] identifierBlob{ get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.IO;
|
|||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using CRD.Utils.Files;
|
||||
|
||||
namespace CRD.Utils.DRM;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,24 @@ using Newtonsoft.Json;
|
|||
|
||||
namespace CRD.Utils;
|
||||
|
||||
public enum StreamingService{
|
||||
Crunchyroll,
|
||||
Unknown
|
||||
}
|
||||
|
||||
public enum EpisodeType{
|
||||
MusicVideo,
|
||||
Concert,
|
||||
Episode,
|
||||
Unknown
|
||||
}
|
||||
|
||||
public enum SeriesType{
|
||||
Artist,
|
||||
Series,
|
||||
Unknown
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
[JsonConverter(typeof(LocaleConverter))]
|
||||
public enum Locale{
|
||||
|
|
|
|||
|
|
@ -4,21 +4,25 @@ using System.IO;
|
|||
using System.IO.Compression;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.Crunchyroll;
|
||||
using Newtonsoft.Json;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
|
||||
namespace CRD.Utils;
|
||||
namespace CRD.Utils.Files;
|
||||
|
||||
public class CfgManager{
|
||||
private static string WorkingDirectory = AppContext.BaseDirectory;
|
||||
|
||||
public static readonly string PathCrToken = Path.Combine(WorkingDirectory, "config", "cr_token.yml");
|
||||
public static readonly string PathCrDownloadOptions = Path.Combine(WorkingDirectory, "config", "settings.yml");
|
||||
public static readonly string PathCrTokenOld = Path.Combine(WorkingDirectory, "config", "cr_token.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 PathWindowSettings = Path.Combine(WorkingDirectory, "config", "windowSettings.json");
|
||||
|
||||
|
|
@ -83,18 +87,36 @@ public class CfgManager{
|
|||
}
|
||||
}
|
||||
|
||||
public static void WriteJsonResponseToYamlFile(string jsonResponse, string filePath){
|
||||
// Convert JSON to an object
|
||||
var deserializer = new DeserializerBuilder()
|
||||
.WithNamingConvention(UnderscoredNamingConvention.Instance) // Adjust this as needed
|
||||
.Build();
|
||||
var jsonObject = deserializer.Deserialize<object>(jsonResponse);
|
||||
public static void WriteCrSettings(){
|
||||
WriteJsonToFile(PathCrDownloadOptions, CrunchyrollManager.Instance.CrunOptions);
|
||||
}
|
||||
|
||||
// Convert the object to YAML
|
||||
var serializer = new SerializerBuilder()
|
||||
.WithNamingConvention(UnderscoredNamingConvention.Instance) // Ensure consistent naming convention
|
||||
.Build();
|
||||
var yaml = serializer.Serialize(jsonObject);
|
||||
// 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 UpdateSettingsFromFile<T>(T options, string filePath) where T : class{
|
||||
if (options == null){
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
string dirPath = Path.GetDirectoryName(filePath) ?? string.Empty;
|
||||
|
||||
|
|
@ -103,74 +125,81 @@ public class CfgManager{
|
|||
}
|
||||
|
||||
if (!File.Exists(filePath)){
|
||||
// Create the file if it doesn't exist
|
||||
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;
|
||||
}
|
||||
|
||||
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){
|
||||
return;
|
||||
|
|
@ -180,17 +209,18 @@ public class CfgManager{
|
|||
.WithNamingConvention(UnderscoredNamingConvention.Instance)
|
||||
.IgnoreUnmatchedProperties()
|
||||
.Build();
|
||||
|
||||
|
||||
var propertiesPresentInYaml = GetTopLevelPropertiesInYaml(input);
|
||||
var loadedOptions = deserializer.Deserialize<CrDownloadOptions>(new StringReader(input));
|
||||
var loadedOptions = deserializer.Deserialize<CrDownloadOptionsYaml>(new StringReader(input));
|
||||
var instanceOptions = options;
|
||||
|
||||
foreach (PropertyInfo property in typeof(CrDownloadOptions).GetProperties()){
|
||||
foreach (PropertyInfo property in typeof(CrDownloadOptionsYaml).GetProperties()){
|
||||
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)){
|
||||
PropertyInfo instanceProperty = instanceOptions.GetType().GetProperty(property.Name);
|
||||
PropertyInfo? instanceProperty = instanceOptions.GetType().GetProperty(property.Name);
|
||||
if (instanceProperty != null && instanceProperty.CanWrite){
|
||||
instanceProperty.SetValue(instanceOptions, property.GetValue(loadedOptions));
|
||||
}
|
||||
|
|
@ -216,6 +246,9 @@ public class CfgManager{
|
|||
return properties;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
public static void UpdateHistoryFile(){
|
||||
if (!CrunchyrollManager.Instance.CrunOptions.History){
|
||||
return;
|
||||
|
|
@ -308,12 +341,32 @@ public class CfgManager{
|
|||
return Directory.Exists(dirPath) && File.Exists(filePath);
|
||||
}
|
||||
|
||||
public static T DeserializeFromFile<T>(string filePath){
|
||||
var deserializer = new DeserializerBuilder()
|
||||
.Build();
|
||||
// public static T DeserializeFromFile<T>(string filePath){
|
||||
// var deserializer = new DeserializerBuilder()
|
||||
// .Build();
|
||||
//
|
||||
// using (var reader = new StreamReader(filePath)){
|
||||
// return deserializer.Deserialize<T>(reader);
|
||||
// }
|
||||
// }
|
||||
|
||||
using (var reader = new StreamReader(filePath)){
|
||||
return deserializer.Deserialize<T>(reader);
|
||||
public static T? ReadJsonFromFile<T>(string pathToFile) where T : class{
|
||||
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
|
||||
if (File.Exists(fn) && !_data.IsResume){
|
||||
string rwts = _data.Override ?? "Y";
|
||||
string rwts = !string.IsNullOrEmpty(_data.Override) ? _data.Override : "Y";
|
||||
rwts = rwts.ToUpper(); // ?? "N"
|
||||
|
||||
if (rwts.StartsWith("Y")){
|
||||
|
|
@ -304,7 +304,7 @@ public class HlsDownloader{
|
|||
double downloadSpeed = downloadedBytes / (dateElapsed / 1000);
|
||||
|
||||
int partsLeft = partsTotal - partsDownloaded;
|
||||
double remainingTime = (partsLeft * (totalDownloadedBytes / partsDownloaded)) / downloadSpeed;
|
||||
double remainingTime = (partsLeft * ((double)totalDownloadedBytes / partsDownloaded)) / downloadSpeed;
|
||||
|
||||
return new Info{
|
||||
Percent = percent,
|
||||
|
|
|
|||
|
|
@ -15,8 +15,10 @@ using Avalonia.Media;
|
|||
using Avalonia.Media.Imaging;
|
||||
using CRD.Downloader;
|
||||
using CRD.Utils.Ffmpeg_Encoding;
|
||||
using CRD.Utils.Files;
|
||||
using CRD.Utils.JsonConv;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.Crunchyroll;
|
||||
using Microsoft.Win32;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
|
|
@ -747,4 +749,95 @@ public class Helpers{
|
|||
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 ApiN = "https://www.crunchyroll.com";
|
||||
public static readonly string Anilist = "https://graphql.anilist.co";
|
||||
|
||||
|
||||
public static readonly string Auth = ApiN + "/auth/v1/token";
|
||||
public static readonly string BetaAuth = ApiBeta + "/auth/v1/token";
|
||||
public static readonly string BetaProfile = ApiBeta + "/accounts/v1/me/profile";
|
||||
public static readonly string BetaCmsToken = ApiBeta + "/index/v2";
|
||||
public static readonly string Search = ApiBeta + "/content/v2/discover/search";
|
||||
public static readonly string Browse = ApiBeta + "/content/v2/discover/browse";
|
||||
public static readonly string Cms = ApiBeta + "/content/v2/cms";
|
||||
public static readonly string Content = ApiBeta + "/content/v2";
|
||||
public static readonly string Profile = ApiN + "/accounts/v1/me/profile";
|
||||
public static readonly string CmsToken = ApiN + "/index/v2";
|
||||
public static readonly string Search = ApiN + "/content/v2/discover/search";
|
||||
public static readonly string Browse = ApiN + "/content/v2/discover/browse";
|
||||
public static readonly string Cms = ApiN + "/content/v2/cms";
|
||||
public static readonly string Content = ApiN + "/content/v2";
|
||||
|
||||
public static readonly string BetaBrowse = ApiBeta + "/content/v1/browse";
|
||||
public static readonly string BetaCms = ApiBeta + "/cms/v2";
|
||||
public static readonly string DRM = ApiBeta + "/drm/v1/auth";
|
||||
|
||||
public static readonly string Subscription = ApiBeta + "/subs/v3/subscriptions/";
|
||||
public static readonly string CmsN = ApiN + "/content/v2/cms";
|
||||
public static readonly string Subscription = ApiN + "/subs/v3/subscriptions/";
|
||||
|
||||
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 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.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using CRD.Utils.Files;
|
||||
using CRD.Utils.Structs;
|
||||
|
||||
namespace CRD.Utils.Muxing;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Text.RegularExpressions;
|
|||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils.Files;
|
||||
using CRD.Utils.Structs;
|
||||
|
||||
namespace CRD.Utils.Muxing;
|
||||
|
|
@ -332,18 +333,25 @@ public class Merger{
|
|||
Time = GetTimeFromFileName(fp, extractFramesCompareEnd.frameRate)
|
||||
}).ToList();
|
||||
|
||||
|
||||
|
||||
// Calculate offsets
|
||||
var startOffset = SyncingHelper.CalculateOffset(baseFramesStart, compareFramesStart);
|
||||
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;
|
||||
|
||||
Console.WriteLine($"Start offset: {startOffset} seconds");
|
||||
Console.WriteLine($"End offset: {endOffset} seconds");
|
||||
|
||||
CleanupDirectory(cleanupDir);
|
||||
|
||||
baseFramesStart.Clear();
|
||||
baseFramesEnd.Clear();
|
||||
compareFramesStart.Clear();
|
||||
compareFramesEnd.Clear();
|
||||
|
||||
var difference = Math.Abs(startOffset - endOffset);
|
||||
|
||||
|
|
@ -370,7 +378,7 @@ public class Merger{
|
|||
private static double GetTimeFromFileName(string fileName, double frameRate){
|
||||
var match = Regex.Match(Path.GetFileName(fileName), @"frame(\d+)");
|
||||
if (match.Success){
|
||||
return int.Parse(match.Groups[1].Value) / frameRate; // Assuming 30 fps
|
||||
return int.Parse(match.Groups[1].Value) / frameRate;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using CRD.Utils.Files;
|
||||
using CRD.Utils.Structs;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
|
@ -16,7 +17,7 @@ namespace CRD.Utils.Muxing;
|
|||
public class SyncingHelper{
|
||||
public static async Task<(bool IsOk, int ErrorCode, double frameRate)> ExtractFrames(string videoPath, string outputDir, double offset, double duration){
|
||||
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 = "";
|
||||
|
||||
|
|
@ -68,7 +69,7 @@ public class SyncingHelper{
|
|||
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 mean2 = pixels2.Average();
|
||||
|
||||
|
|
@ -110,7 +111,7 @@ public class SyncingHelper{
|
|||
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 image2 = Image.Load<Rgba32>(imagePath2)){
|
||||
// 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
|
||||
if (IsBlackFrame(pixels1) || IsBlackFrame(pixels2)){
|
||||
// Return a negative value or zero to indicate no SSIM comparison for black frames.
|
||||
return -1.0;
|
||||
return (-1.0,99);
|
||||
}
|
||||
|
||||
// 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){
|
||||
// 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){
|
||||
double ssim = ComputeSSIM(imagePath1, imagePath2, 256, 256);
|
||||
var (ssim, pixelDiff) = ComputeSSIM(imagePath1, imagePath2, 256, 256);
|
||||
// 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){
|
||||
|
||||
|
|
@ -160,7 +175,9 @@ public class SyncingHelper{
|
|||
foreach (var baseFrame in baseFrames){
|
||||
var matchingFrame = compareFrames.FirstOrDefault(f => AreFramesSimilar(baseFrame.FilePath, f.FilePath, ssimThreshold));
|
||||
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;
|
||||
} else{
|
||||
// Console.WriteLine($"No Match Found for Base Frame Time: {baseFrame.Time}");
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ public static class MPDParser{
|
|||
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);
|
||||
|
||||
var pItem = new AudioPlaylist{
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ using Newtonsoft.Json;
|
|||
|
||||
namespace CRD.Utils.Structs;
|
||||
|
||||
public struct CrunchyChapters{
|
||||
public class CrunchyChapters{
|
||||
public List<CrunchyChapter> Chapters { get; set; }
|
||||
public DateTime lastUpdate { get; set; }
|
||||
public string? mediaId { get; set; }
|
||||
}
|
||||
|
||||
public struct CrunchyChapter{
|
||||
public class CrunchyChapter{
|
||||
public string approverId { get; set; }
|
||||
public string distributionNumber { get; set; }
|
||||
public double? end { get; set; }
|
||||
|
|
@ -22,7 +22,7 @@ public struct CrunchyChapter{
|
|||
public string type { get; set; }
|
||||
}
|
||||
|
||||
public struct CrunchyOldChapter{
|
||||
public class CrunchyOldChapter{
|
||||
public string media_id { get; set; }
|
||||
public double startTime { get; set; }
|
||||
public double endTime { get; set; }
|
||||
|
|
|
|||
|
|
@ -1,13 +1,261 @@
|
|||
using System.Collections.Generic;
|
||||
using CRD.Utils.Sonarr;
|
||||
using CRD.ViewModels;
|
||||
using Newtonsoft.Json;
|
||||
using YamlDotNet.Serialization;
|
||||
|
||||
namespace CRD.Utils.Structs;
|
||||
namespace CRD.Utils.Structs.Crunchyroll;
|
||||
|
||||
public class CrDownloadOptions{
|
||||
#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)]
|
||||
public bool AutoDownload{ get; set; }
|
||||
|
||||
|
|
@ -21,13 +269,13 @@ public class CrDownloadOptions{
|
|||
public int FsRetryTime{ get; set; }
|
||||
|
||||
[YamlIgnore]
|
||||
public string Force{ get; set; }
|
||||
public string Force{ get; set; } = "";
|
||||
|
||||
[YamlMember(Alias = "simultaneous_downloads", ApplyNamingConventions = false)]
|
||||
public int SimultaneousDownloads{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "theme", ApplyNamingConventions = false)]
|
||||
public string Theme{ get; set; }
|
||||
public string Theme{ get; set; } = "";
|
||||
|
||||
[YamlMember(Alias = "accent_color", ApplyNamingConventions = false)]
|
||||
public string? AccentColor{ get; set; }
|
||||
|
|
@ -43,23 +291,29 @@ public class CrDownloadOptions{
|
|||
|
||||
|
||||
[YamlIgnore]
|
||||
public List<string> Override{ get; set; }
|
||||
public List<string> Override{ get; } =[];
|
||||
|
||||
[YamlIgnore]
|
||||
public string CcTag{ get; set; }
|
||||
public string CcTag{ get; set; } = "";
|
||||
|
||||
[YamlIgnore]
|
||||
public bool Nocleanup{ get; set; }
|
||||
public bool Nocleanup{ get; } = false;
|
||||
|
||||
[YamlMember(Alias = "history", ApplyNamingConventions = false)]
|
||||
public bool History{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "history_include_cr_artists", ApplyNamingConventions = false)]
|
||||
public bool HistoryIncludeCrArtists{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "history_lang", ApplyNamingConventions = false)]
|
||||
public string? HistoryLang{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "history_add_specials", ApplyNamingConventions = false)]
|
||||
public bool HistoryAddSpecials{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "history_skip_unmonitored", ApplyNamingConventions = false)]
|
||||
public bool HistorySkipUnmonitored{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "history_count_sonarr", ApplyNamingConventions = false)]
|
||||
public bool HistoryCountSonarr{ get; set; }
|
||||
|
||||
|
|
@ -80,7 +334,7 @@ public class CrDownloadOptions{
|
|||
|
||||
[YamlMember(Alias = "history_page_properties", ApplyNamingConventions = false)]
|
||||
public HistoryPageProperties? HistoryPageProperties{ get; set; }
|
||||
|
||||
|
||||
[YamlMember(Alias = "seasons_page_properties", ApplyNamingConventions = false)]
|
||||
public SeasonsPageProperties? SeasonsPageProperties{ get; set; }
|
||||
|
||||
|
|
@ -89,7 +343,7 @@ public class CrDownloadOptions{
|
|||
|
||||
[YamlMember(Alias = "proxy_enabled", ApplyNamingConventions = false)]
|
||||
public bool ProxyEnabled{ get; set; }
|
||||
|
||||
|
||||
[YamlMember(Alias = "proxy_socks", ApplyNamingConventions = false)]
|
||||
public bool ProxySocks{ get; set; }
|
||||
|
||||
|
|
@ -98,20 +352,20 @@ public class CrDownloadOptions{
|
|||
|
||||
[YamlMember(Alias = "proxy_port", ApplyNamingConventions = false)]
|
||||
public int ProxyPort{ get; set; }
|
||||
|
||||
|
||||
[YamlMember(Alias = "proxy_username", ApplyNamingConventions = false)]
|
||||
public string? ProxyUsername{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "proxy_password", ApplyNamingConventions = false)]
|
||||
public string? ProxyPassword{ get; set; }
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Crunchyroll Settings
|
||||
|
||||
[YamlMember(Alias = "hard_sub_lang", ApplyNamingConventions = false)]
|
||||
public string Hslang{ get; set; }
|
||||
public string Hslang{ get; set; } = "";
|
||||
|
||||
[YamlIgnore]
|
||||
public int Kstream{ get; set; }
|
||||
|
|
@ -126,23 +380,23 @@ public class CrDownloadOptions{
|
|||
public int StreamServer{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "quality_video", ApplyNamingConventions = false)]
|
||||
public string QualityVideo{ get; set; }
|
||||
public string QualityVideo{ get; set; } = "";
|
||||
|
||||
[YamlMember(Alias = "quality_audio", ApplyNamingConventions = false)]
|
||||
public string QualityAudio{ get; set; }
|
||||
public string QualityAudio{ get; set; } = "";
|
||||
|
||||
[YamlMember(Alias = "file_name", ApplyNamingConventions = false)]
|
||||
public string FileName{ get; set; }
|
||||
public string FileName{ get; set; } = "";
|
||||
|
||||
[YamlMember(Alias = "leading_numbers", ApplyNamingConventions = false)]
|
||||
public int Numbers{ get; set; }
|
||||
|
||||
[YamlIgnore]
|
||||
[YamlMember(Alias = "download_part_size", ApplyNamingConventions = false)]
|
||||
public int Partsize{ get; set; }
|
||||
|
||||
|
||||
[YamlMember(Alias = "soft_subs", ApplyNamingConventions = false)]
|
||||
public List<string> DlSubs{ get; set; }
|
||||
public List<string> DlSubs{ get; set; } =[];
|
||||
|
||||
[YamlIgnore]
|
||||
public bool SkipSubs{ get; set; }
|
||||
|
|
@ -181,13 +435,13 @@ public class CrDownloadOptions{
|
|||
public string? DescriptionLang{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "mux_ffmpeg", ApplyNamingConventions = false)]
|
||||
public List<string> FfmpegOptions{ get; set; }
|
||||
public List<string> FfmpegOptions{ get; set; } =[];
|
||||
|
||||
[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)]
|
||||
public string DefaultSub{ get; set; }
|
||||
public string DefaultSub{ get; set; } = "";
|
||||
|
||||
[YamlMember(Alias = "mux_default_sub_signs", ApplyNamingConventions = false)]
|
||||
public bool DefaultSubSigns{ get; set; }
|
||||
|
|
@ -196,7 +450,7 @@ public class CrDownloadOptions{
|
|||
public bool DefaultSubForcedDisplay{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "mux_default_dub", ApplyNamingConventions = false)]
|
||||
public string DefaultAudio{ get; set; }
|
||||
public string DefaultAudio{ get; set; } = "";
|
||||
|
||||
[YamlMember(Alias = "dl_video_once", ApplyNamingConventions = false)]
|
||||
public bool DlVideoOnce{ get; set; }
|
||||
|
|
@ -220,8 +474,7 @@ public class CrDownloadOptions{
|
|||
public bool Chapters{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "dub_lang", ApplyNamingConventions = false)]
|
||||
public List<string> DubLang{ get; set; }
|
||||
|
||||
public List<string> DubLang{ get; set; } =[];
|
||||
|
||||
[YamlMember(Alias = "calendar_language", ApplyNamingConventions = false)]
|
||||
public string? SelectedCalendarLanguage{ get; set; }
|
||||
|
|
@ -237,12 +490,15 @@ public class CrDownloadOptions{
|
|||
|
||||
[YamlMember(Alias = "calendar_filter_by_air_date", ApplyNamingConventions = false)]
|
||||
public bool CalendarFilterByAirDate{ get; set; }
|
||||
|
||||
|
||||
[YamlMember(Alias = "calendar_show_upcoming_episodes", ApplyNamingConventions = false)]
|
||||
public bool CalendarShowUpcomingEpisodes{ get; set; }
|
||||
|
||||
|
||||
[YamlMember(Alias = "stream_endpoint", ApplyNamingConventions = false)]
|
||||
public string? StreamEndpoint{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "search_fetch_featured_music", ApplyNamingConventions = false)]
|
||||
public bool SearchFetchFeaturedMusic{ get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ using Newtonsoft.Json;
|
|||
|
||||
namespace CRD.Utils.Structs;
|
||||
|
||||
public struct CrunchyMovieList{
|
||||
public class CrunchyMovieList{
|
||||
public int Total{ get; set; }
|
||||
public List<CrunchyMovie>? Data{ get; set; }
|
||||
public Meta Meta{ get; set; }
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ public class CrBrowseEpisodeMetaData{
|
|||
|
||||
}
|
||||
|
||||
public struct CrBrowseEpisodeVersion{
|
||||
public class CrBrowseEpisodeVersion{
|
||||
[JsonProperty("audio_locale")]
|
||||
public Locale? AudioLocale{ get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using CRD.Utils.Structs.History;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CRD.Utils.Structs;
|
||||
|
||||
public struct CrunchyEpisodeList{
|
||||
public class CrunchyEpisodeList{
|
||||
public int Total{ get; set; }
|
||||
public List<CrunchyEpisode>? Data{ get; set; }
|
||||
public Meta Meta{ get; set; }
|
||||
}
|
||||
|
||||
public struct CrunchyEpisode{
|
||||
public class CrunchyEpisode : IHistorySource{
|
||||
[JsonProperty("next_episode_id")]
|
||||
public string NextEpisodeId{ get; set; }
|
||||
|
||||
|
|
@ -81,7 +84,7 @@ public struct CrunchyEpisode{
|
|||
[JsonProperty("audio_locale")]
|
||||
public string AudioLocale{ get; set; }
|
||||
|
||||
public string Id{ get; set; }
|
||||
public required string Id{ get; set; }
|
||||
|
||||
[JsonProperty("media_type")]
|
||||
public string? MediaType{ get; set; }
|
||||
|
|
@ -173,9 +176,123 @@ public struct CrunchyEpisode{
|
|||
|
||||
[JsonProperty("__links__")]
|
||||
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")]
|
||||
public List<List<Image>>? PosterTall{ get; set; }
|
||||
|
||||
|
|
@ -188,14 +305,14 @@ public struct Images{
|
|||
public List<List<Image>>? Thumbnail{ get; set; }
|
||||
}
|
||||
|
||||
public struct Image{
|
||||
public class Image{
|
||||
public int Height{ get; set; }
|
||||
public string Source{ get; set; }
|
||||
public ImageType Type{ get; set; }
|
||||
public int Width{ get; set; }
|
||||
}
|
||||
|
||||
public struct EpisodeVersion{
|
||||
public class EpisodeVersion{
|
||||
[JsonProperty("audio_locale")]
|
||||
public string AudioLocale{ get; set; }
|
||||
|
||||
|
|
@ -215,11 +332,11 @@ public struct EpisodeVersion{
|
|||
public string Variant{ get; set; }
|
||||
}
|
||||
|
||||
public struct Link{
|
||||
public class Link{
|
||||
public string Href{ get; set; }
|
||||
}
|
||||
|
||||
public struct Links(){
|
||||
public class Links(){
|
||||
public Dictionary<string, Link> LinkMappings{ get; set; } = new(){
|
||||
{ "episode/channel", default },
|
||||
{ "episode/next_episode", default },
|
||||
|
|
@ -230,7 +347,7 @@ public struct Links(){
|
|||
}
|
||||
|
||||
public class CrunchyEpMeta{
|
||||
public List<CrunchyEpMetaData>? Data{ get; set; }
|
||||
public List<CrunchyEpMetaData> Data{ get; set; } =[];
|
||||
|
||||
public string? SeriesTitle{ get; set; }
|
||||
public string? SeasonTitle{ get; set; }
|
||||
|
|
@ -239,11 +356,11 @@ public class CrunchyEpMeta{
|
|||
public string? Description{ get; set; }
|
||||
public string? SeasonId{ get; set; }
|
||||
public string? Season{ get; set; }
|
||||
public string? ShowId{ get; set; }
|
||||
public string? SeriesId{ get; set; }
|
||||
public string? AbsolutEpisodeNumberE{ get; set; }
|
||||
public string? Image{ get; set; }
|
||||
public bool Paused{ get; set; }
|
||||
public DownloadProgress? DownloadProgress{ get; set; }
|
||||
public DownloadProgress DownloadProgress{ get; set; } = new();
|
||||
|
||||
public List<string>? SelectedDubs{ get; set; }
|
||||
|
||||
|
|
@ -259,7 +376,7 @@ public class CrunchyEpMeta{
|
|||
public string Resolution{ get; set; }
|
||||
|
||||
public List<string> downloadedFiles{ get; set; } =[];
|
||||
|
||||
|
||||
public bool OnlySubs{ get; set; }
|
||||
}
|
||||
|
||||
|
|
@ -274,7 +391,7 @@ public class DownloadProgress{
|
|||
public double DownloadSpeed{ get; set; }
|
||||
}
|
||||
|
||||
public struct CrunchyEpMetaData{
|
||||
public class CrunchyEpMetaData{
|
||||
public string MediaId{ get; set; }
|
||||
public LanguageItem? Lang{ get; set; }
|
||||
public string? Playback{ get; set; }
|
||||
|
|
@ -283,7 +400,7 @@ public struct CrunchyEpMetaData{
|
|||
public bool IsDubbed{ get; set; }
|
||||
}
|
||||
|
||||
public struct CrunchyRollEpisodeData{
|
||||
public class CrunchyRollEpisodeData{
|
||||
public string Key{ 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.Collections.Generic;
|
||||
using CRD.Utils.Structs.History;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CRD.Utils.Structs.Crunchyroll.Music;
|
||||
|
||||
public struct CrunchyMusicVideoList{
|
||||
public class CrunchyMusicVideoList{
|
||||
public int Total{ get; set; }
|
||||
public List<CrunchyMusicVideo>? Data{ get; set; }
|
||||
public Meta Meta{ get; set; }
|
||||
public List<CrunchyMusicVideo> Data{ get; set; } =[];
|
||||
public Meta? Meta{ get; set; }
|
||||
}
|
||||
|
||||
public class CrunchyMusicVideo{
|
||||
|
||||
public class CrunchyMusicVideo : IHistorySource{
|
||||
[JsonProperty("copyright")]
|
||||
public string? Copyright{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("hash")]
|
||||
public string? Hash{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("availability")]
|
||||
public MusicVideoAvailability? Availability{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("isMature")]
|
||||
public bool IsMature{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("maturityRatings")]
|
||||
public object? MaturityRatings{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("title")]
|
||||
public string? Title{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("artists")]
|
||||
public object? Artists{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("displayArtistNameRequired")]
|
||||
public bool DisplayArtistNameRequired{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("streams_link")]
|
||||
public string? StreamsLink{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("matureBlocked")]
|
||||
public bool MatureBlocked{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("originalRelease")]
|
||||
public DateTime OriginalRelease{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("sequenceNumber")]
|
||||
public int SequenceNumber{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("type")]
|
||||
public string? Type{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("animeIds")]
|
||||
public List<string>? AnimeIds{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("description")]
|
||||
public string? Description{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("durationMs")]
|
||||
public int DurationMs{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("licensor")]
|
||||
public string? Licensor{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("slug")]
|
||||
public string? Slug{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("artist")]
|
||||
public MusicVideoArtist? Artist{ get; set; }
|
||||
|
||||
public required MusicVideoArtist Artist{ get; set; }
|
||||
|
||||
[JsonProperty("isPremiumOnly")]
|
||||
public bool IsPremiumOnly{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("isPublic")]
|
||||
public bool IsPublic{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("publishDate")]
|
||||
public DateTime PublishDate{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("displayArtistName")]
|
||||
public string? DisplayArtistName{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("genres")]
|
||||
public object? genres{ get; set; }
|
||||
|
||||
public object? Genres{ get; set; }
|
||||
|
||||
[JsonProperty("readyToPublish")]
|
||||
public bool ReadyToPublish{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("id")]
|
||||
public string? Id{ get; set; }
|
||||
|
||||
public required string Id{ get; set; }
|
||||
|
||||
[JsonProperty("createdAt")]
|
||||
public DateTime CreatedAt{ get; set; }
|
||||
|
||||
public MusicImages? Images{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("updatedAt")]
|
||||
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")]
|
||||
public List<Image>? PosterTall{ get; set; }
|
||||
public List<Image> PosterTall{ get; set; } =[];
|
||||
|
||||
[JsonProperty("poster_wide")]
|
||||
public List<Image>? PosterWide{ get; set; }
|
||||
public List<Image> PosterWide{ get; set; } =[];
|
||||
|
||||
[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")]
|
||||
public string? Id{ get; set; }
|
||||
public required string Id{ get; set; }
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string? Name{ get; set; }
|
||||
public required string Name{ get; set; }
|
||||
|
||||
[JsonProperty("slug")]
|
||||
public string? Slug{ get; set; }
|
||||
|
||||
}
|
||||
|
||||
public struct MusicVideoAvailability{
|
||||
public class MusicVideoAvailability{
|
||||
[JsonProperty("endDate")]
|
||||
public DateTime EndDate{ get; set; }
|
||||
|
||||
[JsonProperty("startDate")]
|
||||
public DateTime StartDate{ get; set; }
|
||||
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ public class CrSeriesBase{
|
|||
public Meta Meta{ get; set; }
|
||||
}
|
||||
|
||||
public struct SeriesBaseItem{
|
||||
public class SeriesBaseItem{
|
||||
[JsonProperty("extended_maturity_rating")]
|
||||
public Dictionary<object, object>
|
||||
ExtendedMaturityRating{ get; set; }
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ public class CrSeriesSearch{
|
|||
public Meta Meta{ get; set; }
|
||||
}
|
||||
|
||||
public struct SeriesSearchItem{
|
||||
public class SeriesSearchItem{
|
||||
public string Description{ get; set; }
|
||||
|
||||
[JsonProperty("seo_description")]
|
||||
|
|
@ -91,7 +91,7 @@ public struct SeriesSearchItem{
|
|||
public string SeoTitle{ get; set; }
|
||||
}
|
||||
|
||||
public struct Version{
|
||||
public class Version{
|
||||
[JsonProperty("audio_locale")]
|
||||
public string? AudioLocale{ get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ using Newtonsoft.Json;
|
|||
|
||||
namespace CRD.Utils.Structs;
|
||||
|
||||
public struct AuthData{
|
||||
public class AuthData{
|
||||
public string Username{ get; set; }
|
||||
public string Password{ get; set; }
|
||||
}
|
||||
|
|
@ -18,12 +18,12 @@ public class DrmAuthData{
|
|||
public string? Token{ get; set; }
|
||||
}
|
||||
|
||||
public struct Meta{
|
||||
public class Meta{
|
||||
[JsonProperty("versions_considered")]
|
||||
public bool? VersionsConsidered{ get; set; }
|
||||
}
|
||||
|
||||
public struct LanguageItem{
|
||||
public class LanguageItem{
|
||||
[JsonProperty("cr_locale")]
|
||||
public string CrLocale{ get; set; }
|
||||
|
||||
|
|
@ -33,12 +33,12 @@ public struct LanguageItem{
|
|||
public string Language{ get; set; }
|
||||
}
|
||||
|
||||
public struct EpisodeAndLanguage{
|
||||
public class EpisodeAndLanguage{
|
||||
public List<CrunchyEpisode> Items{ 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 bool? AllEpisodes{ get; set; } = all; // download all 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 struct CrunchySeriesList{
|
||||
public class CrunchySeriesList{
|
||||
public List<Episode> List{ get; set; }
|
||||
public Dictionary<string, EpisodeAndLanguage> Data{ get; set; }
|
||||
}
|
||||
|
||||
public struct Episode{
|
||||
public class Episode{
|
||||
public string E{ get; set; }
|
||||
public List<string> Lang{ get; set; }
|
||||
public string Name{ get; set; }
|
||||
|
|
@ -63,9 +63,11 @@ public struct Episode{
|
|||
public string Img{ get; set; }
|
||||
public string Description{ 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 string? FileName{ get; set; }
|
||||
|
||||
|
|
@ -4,6 +4,7 @@ using System.ComponentModel;
|
|||
using System.Threading.Tasks;
|
||||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils.Files;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CRD.Utils.Structs.History;
|
||||
|
|
@ -32,12 +33,18 @@ public class HistoryEpisode : INotifyPropertyChanged{
|
|||
|
||||
[JsonProperty("episode_special_episode")]
|
||||
public bool SpecialEpisode{ get; set; }
|
||||
|
||||
[JsonProperty("episode_type")]
|
||||
public EpisodeType EpisodeType{ get; set; } = EpisodeType.Unknown;
|
||||
|
||||
[JsonProperty("sonarr_episode_id")]
|
||||
public string? SonarrEpisodeId{ get; set; }
|
||||
|
||||
[JsonProperty("sonarr_has_file")]
|
||||
public bool SonarrHasFile{ get; set; }
|
||||
|
||||
[JsonProperty("sonarr_is_monitored")]
|
||||
public bool SonarrIsMonitored{ get; set; }
|
||||
|
||||
[JsonProperty("sonarr_episode_number")]
|
||||
public string? SonarrEpisodeNumber{ get; set; }
|
||||
|
|
@ -77,8 +84,22 @@ public class HistoryEpisode : INotifyPropertyChanged{
|
|||
}
|
||||
|
||||
public async Task DownloadEpisode(bool onlySubs = false){
|
||||
await QueueManager.Instance.CrAddEpisodeToQueue(EpisodeId,
|
||||
string.IsNullOrEmpty(CrunchyrollManager.Instance.CrunOptions.HistoryLang) ? CrunchyrollManager.Instance.DefaultLocale : CrunchyrollManager.Instance.CrunOptions.HistoryLang,
|
||||
CrunchyrollManager.Instance.CrunOptions.DubLang, false, onlySubs);
|
||||
switch (EpisodeType){
|
||||
case EpisodeType.MusicVideo:
|
||||
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.ComponentModel;
|
||||
using System.Linq;
|
||||
using CRD.Utils.Files;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CRD.Utils.Structs.History;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
|
|
@ -8,11 +8,18 @@ using System.Threading.Tasks;
|
|||
using Avalonia.Media.Imaging;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils.CustomList;
|
||||
using CRD.Utils.Files;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CRD.Utils.Structs.History;
|
||||
|
||||
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")]
|
||||
public string? SeriesTitle{ get; set; }
|
||||
|
||||
|
|
@ -96,7 +103,7 @@ public class HistorySeries : INotifyPropertyChanged{
|
|||
|
||||
[JsonIgnore]
|
||||
private bool Loading = false;
|
||||
|
||||
|
||||
[JsonIgnore]
|
||||
public StringItem? _selectedVideoQualityItem;
|
||||
|
||||
|
|
@ -111,7 +118,6 @@ public class HistorySeries : INotifyPropertyChanged{
|
|||
if (!Loading){
|
||||
CfgManager.UpdateHistoryFile();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -230,16 +236,48 @@ public class HistorySeries : INotifyPropertyChanged{
|
|||
int count = 0;
|
||||
bool foundWatched = false;
|
||||
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--){
|
||||
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;
|
||||
for (int j = episodesList.Count - 1; j >= 0; 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){
|
||||
count++;
|
||||
}
|
||||
|
|
@ -253,6 +291,10 @@ public class HistorySeries : INotifyPropertyChanged{
|
|||
if (historyAddSpecials){
|
||||
var episodes = season.EpisodesList;
|
||||
for (int j = episodes.Count - 1; j >= 0; j--){
|
||||
if (sonarrEnabled && sonarrSkipUnmonitored && !episodes[j].SonarrIsMonitored){
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!episodes[j].WasDownloaded){
|
||||
count++;
|
||||
}
|
||||
|
|
@ -266,6 +308,10 @@ public class HistorySeries : INotifyPropertyChanged{
|
|||
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){
|
||||
count++;
|
||||
|
|
@ -303,47 +349,103 @@ public class HistorySeries : INotifyPropertyChanged{
|
|||
public async Task AddNewMissingToDownloads(){
|
||||
bool foundWatched = false;
|
||||
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 season = Seasons[i];
|
||||
var sonarrSkipUnmonitored = CrunchyrollManager.Instance.CrunOptions.HistorySkipUnmonitored;
|
||||
|
||||
if (season.SpecialSeason == true){
|
||||
if (historyAddSpecials){
|
||||
var episodes = season.EpisodesList;
|
||||
for (int j = episodes.Count - 1; j >= 0; j--){
|
||||
if (!episodes[j].WasDownloaded){
|
||||
await Seasons[i].EpisodesList[j].DownloadEpisode();
|
||||
if (sonarrEnabled && CrunchyrollManager.Instance.CrunOptions.HistoryCountSonarr){
|
||||
for (int i = Seasons.Count - 1; i >= 0; 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){
|
||||
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;
|
||||
}
|
||||
|
||||
if (!episode.WasDownloaded && !foundWatched){
|
||||
await Seasons[i].EpisodesList[j].DownloadEpisode();
|
||||
} else{
|
||||
foundWatched = true;
|
||||
if (!historyAddSpecials){
|
||||
break;
|
||||
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.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){
|
||||
break;
|
||||
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 (!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}");
|
||||
FetchingData = true;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FetchingData)));
|
||||
try{
|
||||
await CrunchyrollManager.Instance.History.CRUpdateSeries(SeriesId, seasonId);
|
||||
} catch (Exception e){
|
||||
Console.Error.WriteLine("Failed to update History series");
|
||||
Console.Error.WriteLine(e);
|
||||
|
||||
switch (SeriesType){
|
||||
case SeriesType.Artist:
|
||||
try{
|
||||
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(SeriesDescription)));
|
||||
UpdateNewEpisodes();
|
||||
|
|
@ -384,6 +505,15 @@ public class HistorySeries : INotifyPropertyChanged{
|
|||
}
|
||||
|
||||
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)
|
||||
return 1; // y comes before any missing value
|
||||
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;
|
||||
|
|
@ -116,8 +116,8 @@ public class Languages{
|
|||
}
|
||||
|
||||
public static LanguageItem FindLang(string crLocale){
|
||||
LanguageItem lang = languages.FirstOrDefault(l => l.CrLocale == crLocale);
|
||||
if (lang.CrLocale != null){
|
||||
LanguageItem? lang = languages.FirstOrDefault(l => l.CrLocale == crLocale);
|
||||
if (lang?.CrLocale != null){
|
||||
return lang;
|
||||
} else{
|
||||
return new LanguageItem{
|
||||
|
|
@ -159,7 +159,7 @@ public class Languages{
|
|||
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}'.");
|
||||
|
||||
var value = property.GetValue(item) as string;
|
||||
var value = property.GetValue(item) as string ?? string.Empty;
|
||||
int index = idx.ContainsKey(value) ? idx[value] : 50;
|
||||
return index;
|
||||
}).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.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using CRD.Utils.Files;
|
||||
|
||||
namespace CRD.Utils.Updater;
|
||||
|
||||
|
|
|
|||
|
|
@ -29,19 +29,19 @@ public partial class AccountPageViewModel : ViewModelBase{
|
|||
private static DispatcherTimer? _timer;
|
||||
private DateTime _targetTime;
|
||||
|
||||
private bool IsCancelled = false;
|
||||
private bool UnknownEndDate = false;
|
||||
private bool EndedButMaybeActive = false;
|
||||
private bool IsCancelled;
|
||||
private bool UnknownEndDate;
|
||||
private bool EndedButMaybeActive;
|
||||
|
||||
public AccountPageViewModel(){
|
||||
UpdatetProfile();
|
||||
}
|
||||
|
||||
private void Timer_Tick(object sender, EventArgs e){
|
||||
private void Timer_Tick(object? sender, EventArgs e){
|
||||
var remaining = _targetTime - DateTime.Now;
|
||||
if (remaining <= TimeSpan.Zero){
|
||||
RemainingTime = "No active Subscription";
|
||||
_timer.Stop();
|
||||
_timer?.Stop();
|
||||
if (UnknownEndDate){
|
||||
RemainingTime = "Unknown Subscription end date";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,28 +32,28 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
private string _buttonTextSelectSeason = "Select Season";
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _addAllEpisodes = false;
|
||||
private bool _addAllEpisodes;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _buttonEnabled = false;
|
||||
private bool _buttonEnabled;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _allButtonEnabled = false;
|
||||
private bool _allButtonEnabled;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _showLoading = false;
|
||||
private bool _showLoading;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _searchEnabled = false;
|
||||
private bool _searchEnabled;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _searchVisible = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _slectSeasonVisible = false;
|
||||
private bool _slectSeasonVisible;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _searchPopupVisible = false;
|
||||
private bool _searchPopupVisible;
|
||||
|
||||
public ObservableCollection<ItemModel> Items{ 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 List<string> selectedEpisodes = new();
|
||||
private List<ItemModel> selectedEpisodes = new();
|
||||
|
||||
private CrunchySeriesList? currentSeriesList;
|
||||
|
||||
private CrunchyMusicVideoList? currentMusicVideoList;
|
||||
|
||||
private bool CurrentSeasonFullySelected = false;
|
||||
private bool CurrentSeasonFullySelected;
|
||||
|
||||
public AddDownloadPageViewModel(){
|
||||
SelectedItems.CollectionChanged += OnSelectedItemsChanged;
|
||||
|
|
@ -98,9 +98,9 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
if (episode.ImageBitmap == null){
|
||||
if (episode.Images.PosterTall != null){
|
||||
var posterTall = episode.Images.PosterTall.First();
|
||||
var imageUrl = posterTall.Find(ele => ele.Height == 180).Source
|
||||
?? (posterTall.Count >= 2 ? posterTall[1].Source : posterTall.FirstOrDefault().Source);
|
||||
episode.LoadImage(imageUrl);
|
||||
var imageUrl = posterTall.Find(ele => ele.Height == 180)?.Source
|
||||
?? (posterTall.Count >= 2 ? posterTall[1].Source : posterTall.FirstOrDefault()?.Source);
|
||||
episode.LoadImage(imageUrl ?? string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -171,14 +171,16 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
#region OnButtonPress
|
||||
|
||||
[RelayCommand]
|
||||
public async void OnButtonPress(){
|
||||
public async Task OnButtonPress(){
|
||||
if (HasSelectedItemsOrEpisodes()){
|
||||
Console.WriteLine("Added to Queue");
|
||||
|
||||
if (currentMusicVideoList != null){
|
||||
AddSelectedMusicVideosToQueue();
|
||||
} else{
|
||||
AddSelectedEpisodesToQueue();
|
||||
}
|
||||
|
||||
if (currentSeriesList != null){
|
||||
await AddSelectedEpisodesToQueue();
|
||||
}
|
||||
|
||||
ResetState();
|
||||
|
|
@ -194,10 +196,12 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
}
|
||||
|
||||
private void AddSelectedMusicVideosToQueue(){
|
||||
if (SelectedItems.Count > 0){
|
||||
AddItemsToSelectedEpisodes();
|
||||
|
||||
if (selectedEpisodes.Count > 0){
|
||||
var musicClass = CrunchyrollManager.Instance.CrMusic;
|
||||
foreach (var selectedItem in SelectedItems){
|
||||
var music = currentMusicVideoList.Value.Data?.FirstOrDefault(ele => ele.Id == selectedItem.Id);
|
||||
foreach (var selectedItem in selectedEpisodes){
|
||||
var music = currentMusicVideoList?.Data?.FirstOrDefault(ele => ele.Id == selectedItem.Id);
|
||||
|
||||
if (music != null){
|
||||
var meta = musicClass.EpisodeMeta(music);
|
||||
|
|
@ -207,24 +211,24 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
}
|
||||
}
|
||||
|
||||
private async void AddSelectedEpisodesToQueue(){
|
||||
private async Task AddSelectedEpisodesToQueue(){
|
||||
AddItemsToSelectedEpisodes();
|
||||
|
||||
if (currentSeriesList != null){
|
||||
await QueueManager.Instance.CrAddSeriesToQueue(
|
||||
currentSeriesList.Value,
|
||||
currentSeriesList,
|
||||
new CrunchyMultiDownload(
|
||||
CrunchyrollManager.Instance.CrunOptions.DubLang,
|
||||
AddAllEpisodes,
|
||||
false,
|
||||
selectedEpisodes));
|
||||
selectedEpisodes.Select(selectedEpisode => selectedEpisode.AbsolutNum).ToList()));
|
||||
}
|
||||
}
|
||||
|
||||
private void AddItemsToSelectedEpisodes(){
|
||||
foreach (var selectedItem in SelectedItems){
|
||||
if (!selectedEpisodes.Contains(selectedItem.AbsolutNum)){
|
||||
selectedEpisodes.Add(selectedItem.AbsolutNum);
|
||||
if (!selectedEpisodes.Contains(selectedItem)){
|
||||
selectedEpisodes.Add(selectedItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -256,7 +260,7 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
|
||||
var matchResult = ExtractLocaleAndIdFromUrl();
|
||||
|
||||
if (matchResult is (string locale, string id)){
|
||||
if (matchResult is ({ } locale, { } id)){
|
||||
switch (GetUrlType()){
|
||||
case CrunchyUrlType.Artist:
|
||||
await HandleArtistUrlAsync(locale, id);
|
||||
|
|
@ -299,8 +303,8 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
private async Task HandleArtistUrlAsync(string locale, string id){
|
||||
SetLoadingState(true);
|
||||
|
||||
var list = await CrunchyrollManager.Instance.CrMusic.ParseArtistMusicVideosByIdAsync(
|
||||
id, DetermineLocale(locale), true);
|
||||
var list = await CrunchyrollManager.Instance.CrMusic.ParseArtistVideosByIdAsync(
|
||||
id, DetermineLocale(locale), true, true);
|
||||
|
||||
SetLoadingState(false);
|
||||
|
||||
|
|
@ -335,6 +339,16 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
id, DetermineLocale(locale),
|
||||
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);
|
||||
|
||||
if (list != null){
|
||||
|
|
@ -348,16 +362,37 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
|
||||
private void PopulateItemsFromMusicVideoList(){
|
||||
if (currentMusicVideoList?.Data != null){
|
||||
foreach (var episode in currentMusicVideoList.Value.Data){
|
||||
var imageUrl = episode.Images?.Thumbnail?.FirstOrDefault().Source ?? "";
|
||||
foreach (var episode in currentMusicVideoList.Data){
|
||||
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 newItem = new ItemModel(episode.Id ?? "", imageUrl, episode.Description ?? "", time, episode.Title ?? "", "",
|
||||
episode.SequenceNumber.ToString(), episode.SequenceNumber.ToString(), new List<string>());
|
||||
var newItem = new ItemModel(episode.Id, imageUrl, episode.Description ?? "", time, episode.Title ?? "", seasonKey,
|
||||
episode.SequenceNumber.ToString(), episode.Id, new List<string>(), episode.EpisodeType);
|
||||
|
||||
newItem.LoadImage(imageUrl);
|
||||
Items.Add(newItem);
|
||||
if (!episodesBySeason.ContainsKey(seasonKey)){
|
||||
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(
|
||||
episode.Id, episode.Img, episode.Description, episode.Time, episode.Name, seasonKey,
|
||||
episode.EpisodeNum.StartsWith("SP") ? episode.EpisodeNum : "E" + episode.EpisodeNum,
|
||||
episode.E, episode.Lang);
|
||||
episode.E, episode.Lang, episode.EpisodeType);
|
||||
|
||||
if (!episodesBySeason.ContainsKey(seasonKey)){
|
||||
episodesBySeason[seasonKey] = new List<ItemModel>{ itemModel };
|
||||
|
|
@ -407,7 +442,7 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
public void OnSelectSeasonPressed(){
|
||||
if (CurrentSeasonFullySelected){
|
||||
foreach (var item in Items){
|
||||
selectedEpisodes.Remove(item.AbsolutNum);
|
||||
selectedEpisodes.Remove(item);
|
||||
SelectedItems.Remove(item);
|
||||
}
|
||||
|
||||
|
|
@ -426,10 +461,9 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
}
|
||||
|
||||
partial void OnCurrentSelectedSeasonChanging(ComboBoxItem? oldValue, ComboBoxItem newValue){
|
||||
if (SelectedItems == null) return;
|
||||
foreach (var selectedItem in SelectedItems){
|
||||
if (!selectedEpisodes.Contains(selectedItem.AbsolutNum)){
|
||||
selectedEpisodes.Add(selectedItem.AbsolutNum);
|
||||
if (!selectedEpisodes.Contains(selectedItem)){
|
||||
selectedEpisodes.Add(selectedItem);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -443,7 +477,6 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
}
|
||||
|
||||
private void OnSelectedItemsChanged(object? sender, NotifyCollectionChangedEventArgs e){
|
||||
if (Items == null) return;
|
||||
|
||||
CurrentSeasonFullySelected = Items.All(item => SelectedItems.Contains(item));
|
||||
|
||||
|
|
@ -486,7 +519,7 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
|
||||
if (list != null){
|
||||
currentSeriesList = list;
|
||||
SearchPopulateEpisodesBySeason();
|
||||
await SearchPopulateEpisodesBySeason(value.Id);
|
||||
UpdateUiForEpisodeSelection();
|
||||
} else{
|
||||
ButtonEnabled = true;
|
||||
|
|
@ -514,7 +547,7 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
new CrunchyMultiDownload(CrunchyrollManager.Instance.CrunOptions.DubLang, true), true);
|
||||
}
|
||||
|
||||
private void SearchPopulateEpisodesBySeason(){
|
||||
private async Task SearchPopulateEpisodesBySeason(string seriesId){
|
||||
if (currentSeriesList?.List == null){
|
||||
return;
|
||||
}
|
||||
|
|
@ -528,8 +561,7 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
|
||||
GC.Collect();
|
||||
|
||||
|
||||
foreach (var episode in currentSeriesList.Value.List){
|
||||
foreach (var episode in currentSeriesList.List){
|
||||
var seasonKey = "S" + episode.Season;
|
||||
var episodeModel = new ItemModel(
|
||||
episode.Id,
|
||||
|
|
@ -540,7 +572,7 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
seasonKey,
|
||||
episode.EpisodeNum.StartsWith("SP") ? episode.EpisodeNum : "E" + episode.EpisodeNum,
|
||||
episode.E,
|
||||
episode.Lang);
|
||||
episode.Lang, episode.EpisodeType);
|
||||
|
||||
if (!episodesBySeason.ContainsKey(seasonKey)){
|
||||
episodesBySeason[seasonKey] = new List<ItemModel>{ episodeModel };
|
||||
|
|
@ -549,6 +581,19 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
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();
|
||||
}
|
||||
|
|
@ -575,12 +620,12 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
if (episode.ImageBitmap == null){
|
||||
episode.LoadImage(episode.ImageUrl);
|
||||
Items.Add(episode);
|
||||
if (selectedEpisodes.Contains(episode.AbsolutNum)){
|
||||
if (selectedEpisodes.Contains(episode)){
|
||||
SelectedItems.Add(episode);
|
||||
}
|
||||
} else{
|
||||
Items.Add(episode);
|
||||
if (selectedEpisodes.Contains(episode.AbsolutNum)){
|
||||
if (selectedEpisodes.Contains(episode)){
|
||||
SelectedItems.Add(episode);
|
||||
}
|
||||
}
|
||||
|
|
@ -604,21 +649,16 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
|
||||
// Clear collections and other managed resources
|
||||
Items.Clear();
|
||||
Items = null;
|
||||
SearchItems.Clear();
|
||||
SearchItems = null;
|
||||
SelectedItems.Clear();
|
||||
SelectedItems = null;
|
||||
SeasonList.Clear();
|
||||
SeasonList = null;
|
||||
episodesBySeason.Clear();
|
||||
episodesBySeason = null;
|
||||
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 ImageUrl{ get; set; } = imageUrl;
|
||||
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 List<string> AvailableAudios{ get; set; } = availableAudios;
|
||||
public EpisodeType EpisodeType{ get; set; } = epType;
|
||||
|
||||
public bool HasDubs{ get; set; } = availableAudios.Count != 0;
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,21 +2,15 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.Files;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.History;
|
||||
using DynamicData;
|
||||
using DynamicData.Kernel;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CRD.ViewModels;
|
||||
|
||||
|
|
@ -70,9 +64,10 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
|||
|
||||
private CalendarWeek? currentWeek;
|
||||
|
||||
private bool loading = true;
|
||||
private bool loading;
|
||||
|
||||
public CalendarPageViewModel(){
|
||||
loading = true;
|
||||
CalendarDays = new ObservableCollection<CalendarDay>();
|
||||
|
||||
foreach (var languageItem in Languages.languages){
|
||||
|
|
@ -138,7 +133,7 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
|||
|
||||
currentWeek = week;
|
||||
CalendarDays.Clear();
|
||||
CalendarDays.AddRange(week.CalendarDays);
|
||||
if (week.CalendarDays != null) CalendarDays.AddRange(week.CalendarDays);
|
||||
RaisePropertyChanged(nameof(CalendarDays));
|
||||
ShowLoading = false;
|
||||
if (CustomCalendar){
|
||||
|
|
@ -146,9 +141,9 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
|||
foreach (var calendarDayCalendarEpisode in calendarDay.CalendarEpisodes){
|
||||
if (calendarDayCalendarEpisode.ImageBitmap == null){
|
||||
if (calendarDayCalendarEpisode.AnilistEpisode){
|
||||
calendarDayCalendarEpisode.LoadImage(100,150);
|
||||
_ = calendarDayCalendarEpisode.LoadImage(100,150);
|
||||
} else{
|
||||
calendarDayCalendarEpisode.LoadImage();
|
||||
_ = calendarDayCalendarEpisode.LoadImage();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -165,9 +160,9 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
|||
|
||||
if (calendarDayCalendarEpisode.ImageBitmap == null){
|
||||
if (calendarDayCalendarEpisode.AnilistEpisode){
|
||||
calendarDayCalendarEpisode.LoadImage(100,150);
|
||||
_ = calendarDayCalendarEpisode.LoadImage(100,150);
|
||||
} else{
|
||||
calendarDayCalendarEpisode.LoadImage();
|
||||
_ = calendarDayCalendarEpisode.LoadImage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -263,7 +258,7 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
|||
if (value?.Content != null){
|
||||
CrunchyrollManager.Instance.CrunOptions.SelectedCalendarLanguage = value.Content.ToString();
|
||||
Refresh();
|
||||
CfgManager.WriteSettingsToFile();
|
||||
CfgManager.WriteCrSettings();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -276,7 +271,7 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
|||
|
||||
LoadCalendar(GetThisWeeksMondayDate(),DateTime.Now, true);
|
||||
|
||||
CfgManager.WriteSettingsToFile();
|
||||
CfgManager.WriteCrSettings();
|
||||
}
|
||||
|
||||
partial void OnHideDubsChanged(bool value){
|
||||
|
|
@ -285,7 +280,7 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
|||
}
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.CalendarHideDubs = value;
|
||||
CfgManager.WriteSettingsToFile();
|
||||
CfgManager.WriteCrSettings();
|
||||
}
|
||||
|
||||
partial void OnFilterByAirDateChanged(bool value){
|
||||
|
|
@ -294,7 +289,7 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
|||
}
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.CalendarFilterByAirDate = value;
|
||||
CfgManager.WriteSettingsToFile();
|
||||
CfgManager.WriteCrSettings();
|
||||
}
|
||||
|
||||
partial void OnShowUpcomingEpisodesChanged(bool value){
|
||||
|
|
@ -303,7 +298,7 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
|||
}
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.CalendarShowUpcomingEpisodes = value;
|
||||
CfgManager.WriteSettingsToFile();
|
||||
CfgManager.WriteCrSettings();
|
||||
}
|
||||
|
||||
partial void OnCurrentCalendarDubFilterChanged(ComboBoxItem? value){
|
||||
|
|
@ -313,7 +308,7 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
|||
|
||||
if (!string.IsNullOrEmpty(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.Crunchyroll;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.Files;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.Crunchyroll;
|
||||
|
||||
namespace CRD.ViewModels;
|
||||
|
||||
|
|
@ -36,12 +38,12 @@ public partial class DownloadsPageViewModel : ViewModelBase{
|
|||
QueueManager.Instance.UpdateDownloadListItems();
|
||||
}
|
||||
|
||||
CfgManager.WriteSettingsToFile();
|
||||
CfgManager.WriteCrSettings();
|
||||
}
|
||||
|
||||
partial void OnRemoveFinishedChanged(bool value){
|
||||
CrunchyrollManager.Instance.CrunOptions.RemoveFinishedDownload = value;
|
||||
CfgManager.WriteSettingsToFile();
|
||||
CfgManager.WriteCrSettings();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -67,9 +69,10 @@ public partial class DownloadItemModel : INotifyPropertyChanged{
|
|||
public DownloadItemModel(CrunchyEpMeta 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) : "") + " - " +
|
||||
epMeta.EpisodeTitle;
|
||||
|
||||
isDownloading = epMeta.DownloadProgress.IsDownloading || Done;
|
||||
|
||||
Done = epMeta.DownloadProgress.Done;
|
||||
|
|
@ -81,11 +84,19 @@ public partial class DownloadItemModel : INotifyPropertyChanged{
|
|||
Done ? (epMeta.DownloadProgress.Doing != string.Empty ? epMeta.DownloadProgress.Doing : "Done") :
|
||||
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;
|
||||
}
|
||||
|
||||
string JoinWithSeparator(params string[] parts){
|
||||
return string.Join(" - ", parts.Where(part => !string.IsNullOrEmpty(part)));
|
||||
}
|
||||
|
||||
private string GetDubString(){
|
||||
if (epMeta.SelectedDubs == null || epMeta.SelectedDubs.Count < 1){
|
||||
return "";
|
||||
|
|
@ -138,10 +149,15 @@ public partial class DownloadItemModel : INotifyPropertyChanged{
|
|||
Done ? (epMeta.DownloadProgress.Doing != string.Empty ? epMeta.DownloadProgress.Doing : "Done") :
|
||||
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;
|
||||
|
||||
|
||||
if (PropertyChanged != null){
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(isDownloading)));
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Percent)));
|
||||
|
|
@ -186,15 +202,15 @@ public partial class DownloadItemModel : INotifyPropertyChanged{
|
|||
epMeta.DownloadProgress.IsDownloading = true;
|
||||
Paused = !epMeta.Paused && !isDownloading || epMeta.Paused;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Paused)));
|
||||
|
||||
|
||||
CrDownloadOptions newOptions = Helpers.DeepCopy(CrunchyrollManager.Instance.CrunOptions);
|
||||
|
||||
if (epMeta.OnlySubs){
|
||||
newOptions.Novids = 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)){
|
||||
File.Delete(downloadItemDownloadedFile);
|
||||
}
|
||||
} catch (Exception e){
|
||||
} catch (Exception){
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ using CommunityToolkit.Mvvm.Input;
|
|||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.Files;
|
||||
using CRD.Utils.Sonarr;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.History;
|
||||
|
|
@ -29,7 +30,7 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
private ProgramManager _programManager;
|
||||
|
||||
[ObservableProperty]
|
||||
private HistorySeries _selectedSeries;
|
||||
private HistorySeries? _selectedSeries;
|
||||
|
||||
[ObservableProperty]
|
||||
private static bool _editMode;
|
||||
|
|
@ -72,10 +73,16 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isPosterViewSelected = false;
|
||||
private bool _isPosterViewSelected;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isTableViewSelected = false;
|
||||
private bool _isTableViewSelected;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _showSeries = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _showArtists;
|
||||
|
||||
[ObservableProperty]
|
||||
private static bool _viewSelectionOpen;
|
||||
|
|
@ -98,7 +105,7 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
private FilterType currentFilterType;
|
||||
|
||||
[ObservableProperty]
|
||||
private static bool _sortDir = false;
|
||||
private static bool _sortDir;
|
||||
|
||||
[ObservableProperty]
|
||||
private static bool _sonarrAvailable;
|
||||
|
|
@ -127,6 +134,8 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
currentFilterType = properties?.SelectedFilter ?? FilterType.All;
|
||||
ScaleValue = properties?.ScaleValue ?? 0.73;
|
||||
SortDir = properties?.Ascending ?? false;
|
||||
ShowSeries = properties?.ShowSeries ?? true;
|
||||
ShowArtists = properties?.ShowArtists ?? false;
|
||||
|
||||
foreach (HistoryViewType viewType in Enum.GetValues(typeof(HistoryViewType))){
|
||||
var combobox = new ComboBoxItem{ Content = viewType };
|
||||
|
|
@ -137,7 +146,7 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
}
|
||||
|
||||
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);
|
||||
if (sortingType == currentSortingType){
|
||||
SelectedSorting = combobox;
|
||||
|
|
@ -149,7 +158,7 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
continue;
|
||||
}
|
||||
|
||||
var item = new FilterListElement(){ FilterTitle = filterType.GetEnumMemberValue(), SelectedType = filterType };
|
||||
var item = new FilterListElement{ FilterTitle = filterType.GetEnumMemberValue(), SelectedType = filterType };
|
||||
FilterList.Add(item);
|
||||
if (filterType == currentFilterType){
|
||||
SelectedFilter = item;
|
||||
|
|
@ -162,7 +171,7 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
|
||||
foreach (var historySeries in Items){
|
||||
if (historySeries.ThumbnailImage == null){
|
||||
historySeries.LoadImage();
|
||||
_ = historySeries.LoadImage();
|
||||
}
|
||||
|
||||
historySeries.UpdateNewEpisodes();
|
||||
|
|
@ -179,15 +188,15 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties.SelectedSorting = currentSortingType;
|
||||
CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties.Ascending = SortDir;
|
||||
} else{
|
||||
CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties = new HistoryPageProperties()
|
||||
CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties = new HistoryPageProperties
|
||||
{ ScaleValue = ScaleValue, SelectedView = currentViewType, SelectedSorting = currentSortingType, Ascending = SortDir };
|
||||
}
|
||||
|
||||
CfgManager.WriteSettingsToFile();
|
||||
CfgManager.WriteCrSettings();
|
||||
}
|
||||
|
||||
partial void OnSelectedViewChanged(ComboBoxItem value){
|
||||
if (Enum.TryParse(value.Content + "", out HistoryViewType viewType)){
|
||||
partial void OnSelectedViewChanged(ComboBoxItem? value){
|
||||
if (Enum.TryParse(value?.Content + "", out HistoryViewType viewType)){
|
||||
currentViewType = viewType;
|
||||
IsPosterViewSelected = currentViewType == HistoryViewType.Posters;
|
||||
IsTableViewSelected = currentViewType == HistoryViewType.Table;
|
||||
|
|
@ -214,21 +223,33 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
return;
|
||||
}
|
||||
|
||||
if (newValue.SelectedSorting != null){
|
||||
currentSortingType = newValue.SelectedSorting;
|
||||
if (CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties != null) CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties.SelectedSorting = currentSortingType;
|
||||
CrunchyrollManager.Instance.History.SortItems();
|
||||
if (SelectedFilter != null){
|
||||
OnSelectedFilterChanged(SelectedFilter);
|
||||
}
|
||||
} else{
|
||||
Console.Error.WriteLine("Invalid viewtype selected");
|
||||
currentSortingType = newValue.SelectedSorting;
|
||||
if (CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties != null) CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties.SelectedSorting = currentSortingType;
|
||||
CrunchyrollManager.Instance.History.SortItems();
|
||||
if (SelectedFilter != null){
|
||||
OnSelectedFilterChanged(SelectedFilter);
|
||||
}
|
||||
|
||||
SortingSelectionOpen = false;
|
||||
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){
|
||||
if (value == null){
|
||||
|
|
@ -237,37 +258,52 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
|
||||
currentFilterType = value.SelectedType;
|
||||
if (CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties != null) CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties.SelectedFilter = currentFilterType;
|
||||
CfgManager.WriteCrSettings();
|
||||
|
||||
|
||||
ApplyFilter();
|
||||
}
|
||||
|
||||
private void ApplyFilter(){
|
||||
List<HistorySeries> filteredItems;
|
||||
|
||||
switch (currentFilterType){
|
||||
case FilterType.All:
|
||||
FilteredItems.Clear();
|
||||
FilteredItems.AddRange(Items);
|
||||
filteredItems = Items.ToList();
|
||||
break;
|
||||
|
||||
case FilterType.MissingEpisodes:
|
||||
List<HistorySeries> filteredItems = Items.Where(item => item.NewEpisodes > 0).ToList();
|
||||
FilteredItems.Clear();
|
||||
FilteredItems.AddRange(filteredItems);
|
||||
filteredItems = Items.Where(item => item.NewEpisodes > 0).ToList();
|
||||
break;
|
||||
|
||||
case FilterType.MissingEpisodesSonarr:
|
||||
|
||||
var missingSonarrFiltered = Items.Where(historySeries =>
|
||||
!string.IsNullOrEmpty(historySeries.SonarrSeriesId) && // Check series ID
|
||||
historySeries.Seasons.Any(season => // Check each season
|
||||
season.EpisodesList.Any(historyEpisode => // Check each episode
|
||||
!string.IsNullOrEmpty(historyEpisode.SonarrEpisodeId) && !historyEpisode.SonarrHasFile))) // Filter condition
|
||||
filteredItems = Items.Where(historySeries =>
|
||||
!string.IsNullOrEmpty(historySeries.SonarrSeriesId) &&
|
||||
historySeries.Seasons.Any(season =>
|
||||
season.EpisodesList.Any(historyEpisode =>
|
||||
!string.IsNullOrEmpty(historyEpisode.SonarrEpisodeId) && !historyEpisode.SonarrHasFile)))
|
||||
.ToList();
|
||||
|
||||
FilteredItems.Clear();
|
||||
FilteredItems.AddRange(missingSonarrFiltered);
|
||||
|
||||
break;
|
||||
|
||||
case FilterType.ContinuingOnly:
|
||||
List<HistorySeries> continuingFiltered = Items.Where(item => !string.IsNullOrEmpty(item.SonarrNextAirDate)).ToList();
|
||||
FilteredItems.Clear();
|
||||
FilteredItems.AddRange(continuingFiltered);
|
||||
filteredItems = Items.Where(item => !string.IsNullOrEmpty(item.SonarrNextAirDate)).ToList();
|
||||
break;
|
||||
|
||||
default:
|
||||
filteredItems = new List<HistorySeries>();
|
||||
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();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
SelectedSeries = null;
|
||||
|
||||
_selectedSeries = null;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
|
@ -348,7 +384,7 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async void AddMissingToQueue(){
|
||||
public async Task AddMissingToQueue(){
|
||||
var tasks = FilteredItems
|
||||
.Select(item => item.AddNewMissingToDownloads());
|
||||
|
||||
|
|
@ -408,7 +444,7 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
ProgressText = $"{count + 1}/{totalSeries}";
|
||||
|
||||
// Await the CRUpdateSeries task for each seriesId
|
||||
await crInstance.History.CRUpdateSeries(seriesIds[count], "");
|
||||
await crInstance.History.CrUpdateSeries(seriesIds[count], "");
|
||||
RaisePropertyChanged(nameof(ProgressText));
|
||||
}
|
||||
|
||||
|
|
@ -500,27 +536,30 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
}
|
||||
}
|
||||
|
||||
public class HistoryPageProperties(){
|
||||
public class HistoryPageProperties{
|
||||
public SortingType? SelectedSorting{ get; set; }
|
||||
public HistoryViewType SelectedView{ get; set; }
|
||||
public FilterType SelectedFilter{ get; set; }
|
||||
public double? ScaleValue{ 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 bool Ascending{ get; set; }
|
||||
}
|
||||
|
||||
public class SortingListElement(){
|
||||
public class SortingListElement{
|
||||
public SortingType SelectedSorting{ get; set; }
|
||||
public string? SortingTitle{ get; set; }
|
||||
}
|
||||
|
||||
public class FilterListElement(){
|
||||
public class FilterListElement{
|
||||
public FilterType SelectedType{ get; set; }
|
||||
public string? FilterTitle{ get; set; }
|
||||
}
|
||||
|
|
@ -29,6 +29,9 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
|
||||
[ObservableProperty]
|
||||
public static bool _sonarrAvailable;
|
||||
|
||||
[ObservableProperty]
|
||||
public static bool _showMonitoredBookmark;
|
||||
|
||||
[ObservableProperty]
|
||||
public static bool _sonarrConnected;
|
||||
|
|
@ -53,7 +56,7 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
_selectedSeries = CrunchyrollManager.Instance.SelectedSeries;
|
||||
|
||||
if (_selectedSeries.ThumbnailImage == null){
|
||||
_selectedSeries.LoadImage();
|
||||
_ = _selectedSeries.LoadImage();
|
||||
}
|
||||
|
||||
if (CrunchyrollManager.Instance.CrunOptions.SonarrProperties != null){
|
||||
|
|
@ -61,13 +64,18 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
|
||||
if (!string.IsNullOrEmpty(SelectedSeries.SonarrSeriesId)){
|
||||
SonarrAvailable = SelectedSeries.SonarrSeriesId.Length > 0 && SonarrConnected;
|
||||
|
||||
if (SonarrAvailable){
|
||||
ShowMonitoredBookmark = CrunchyrollManager.Instance.CrunOptions.HistorySkipUnmonitored;
|
||||
}
|
||||
|
||||
} else{
|
||||
SonarrAvailable = false;
|
||||
}
|
||||
} else{
|
||||
SonarrConnected = SonarrAvailable = false;
|
||||
}
|
||||
|
||||
|
||||
AvailableDubs = "Available Dubs: " + string.Join(", ", SelectedSeries.HistorySeriesAvailableDubLang);
|
||||
AvailableSubs = "Available Subs: " + string.Join(", ", SelectedSeries.HistorySeriesAvailableSoftSubs);
|
||||
|
||||
|
|
@ -87,17 +95,19 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
var seasonPath = season.SeasonDownloadPath;
|
||||
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)){
|
||||
SeriesFolderPath = parentFolderPath;
|
||||
SeriesFolderPathExists = true;
|
||||
if (Directory.Exists(parentFolderPath)){
|
||||
SeriesFolderPath = parentFolderPath;
|
||||
SeriesFolderPathExists = true;
|
||||
}
|
||||
}
|
||||
} catch (Exception e){
|
||||
Console.Error.WriteLine($"An error occurred while opening the folder: {e.Message}");
|
||||
}
|
||||
} else{
|
||||
var customPath = string.Empty;
|
||||
string customPath;
|
||||
|
||||
if (string.IsNullOrEmpty(SelectedSeries.SeriesTitle))
|
||||
return;
|
||||
|
|
@ -110,10 +120,10 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
// Check Crunchyroll download directory
|
||||
var downloadDirPath = CrunchyrollManager.Instance.CrunOptions.DownloadDirPath;
|
||||
if (!string.IsNullOrEmpty(downloadDirPath)){
|
||||
customPath = System.IO.Path.Combine(downloadDirPath, seriesTitle);
|
||||
customPath = Path.Combine(downloadDirPath, seriesTitle);
|
||||
} else{
|
||||
// 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
|
||||
|
|
@ -186,7 +196,7 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
SonarrConnected = SonarrAvailable = false;
|
||||
}
|
||||
|
||||
UpdateData("");
|
||||
_ = UpdateData("");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ using System;
|
|||
using System.Collections.ObjectModel;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.Imaging;
|
||||
using CRD.Downloader.Crunchyroll.ViewModels;
|
||||
using CRD.Downloader.Crunchyroll.Views;
|
||||
|
|
@ -15,14 +14,14 @@ using Image = Avalonia.Controls.Image;
|
|||
|
||||
namespace CRD.ViewModels;
|
||||
|
||||
public partial class SettingsPageViewModel : ViewModelBase{
|
||||
public class SettingsPageViewModel : ViewModelBase{
|
||||
|
||||
public ObservableCollection<TabViewItem> Tabs{ get; } = new();
|
||||
|
||||
private TabViewItem CreateTab(string header, string iconPath, UserControl content, object viewModel){
|
||||
content.DataContext = viewModel;
|
||||
|
||||
Bitmap bitmap = null;
|
||||
Bitmap? bitmap = null;
|
||||
try{
|
||||
// Load the image using AssetLoader.Open
|
||||
bitmap = new Bitmap(Avalonia.Platform.AssetLoader.Open(new Uri(iconPath)));
|
||||
|
|
|
|||
|
|
@ -1,30 +1,24 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Threading.Tasks;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Threading;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.Files;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.History;
|
||||
using CRD.Views;
|
||||
using Newtonsoft.Json;
|
||||
using ReactiveUI;
|
||||
using JsonSerializer = Newtonsoft.Json.JsonSerializer;
|
||||
|
||||
namespace CRD.ViewModels;
|
||||
|
||||
|
|
@ -147,7 +141,7 @@ public partial class UpcomingPageViewModel : ViewModelBase{
|
|||
#endregion
|
||||
|
||||
[ObservableProperty]
|
||||
private AnilistSeries _selectedSeries;
|
||||
private AnilistSeries? _selectedSeries;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _selectedIndex;
|
||||
|
|
@ -164,7 +158,7 @@ public partial class UpcomingPageViewModel : ViewModelBase{
|
|||
private SortingType currentSortingType;
|
||||
|
||||
[ObservableProperty]
|
||||
private static bool _sortDir = false;
|
||||
private static bool _sortDir;
|
||||
|
||||
public ObservableCollection<SortingListElement> SortingList{ get; } =[];
|
||||
|
||||
|
|
@ -236,12 +230,12 @@ public partial class UpcomingPageViewModel : ViewModelBase{
|
|||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async void AddToHistory(AnilistSeries series){
|
||||
public async Task AddToHistory(AnilistSeries series){
|
||||
if (!string.IsNullOrEmpty(series.CrunchyrollID)){
|
||||
if (CrunchyrollManager.Instance.CrunOptions.History){
|
||||
series.IsInHistory = true;
|
||||
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;
|
||||
RaisePropertyChanged(nameof(series.IsInHistory));
|
||||
|
||||
|
|
@ -435,7 +429,7 @@ public partial class UpcomingPageViewModel : ViewModelBase{
|
|||
CrunchyrollManager.Instance.CrunOptions.SeasonsPageProperties = new SeasonsPageProperties(){ SelectedSorting = currentSortingType, Ascending = SortDir };
|
||||
}
|
||||
|
||||
CfgManager.WriteSettingsToFile();
|
||||
CfgManager.WriteCrSettings();
|
||||
}
|
||||
|
||||
partial void OnSelectedSortingChanged(SortingListElement? oldValue, SortingListElement? newValue){
|
||||
|
|
@ -452,11 +446,9 @@ public partial class UpcomingPageViewModel : ViewModelBase{
|
|||
return;
|
||||
}
|
||||
|
||||
if (newValue.SelectedSorting != null){
|
||||
currentSortingType = newValue.SelectedSorting;
|
||||
if (CrunchyrollManager.Instance.CrunOptions.SeasonsPageProperties != null) CrunchyrollManager.Instance.CrunOptions.SeasonsPageProperties.SelectedSorting = currentSortingType;
|
||||
SortItems();
|
||||
}
|
||||
currentSortingType = newValue.SelectedSorting;
|
||||
if (CrunchyrollManager.Instance.CrunOptions.SeasonsPageProperties != null) CrunchyrollManager.Instance.CrunOptions.SeasonsPageProperties.SelectedSorting = currentSortingType;
|
||||
SortItems();
|
||||
|
||||
SortingSelectionOpen = false;
|
||||
UpdateSettings();
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
|||
using CommunityToolkit.Mvvm.Input;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.Ffmpeg_Encoding;
|
||||
using CRD.Utils.Files;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Views;
|
||||
using DynamicData;
|
||||
|
|
|
|||
|
|
@ -17,8 +17,9 @@ using CommunityToolkit.Mvvm.Input;
|
|||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.Files;
|
||||
using CRD.Utils.Sonarr;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.Crunchyroll;
|
||||
using CRD.Utils.Structs.History;
|
||||
using FluentAvalonia.Styling;
|
||||
|
||||
|
|
@ -35,8 +36,14 @@ public partial class GeneralSettingsViewModel : ViewModelBase{
|
|||
[ObservableProperty]
|
||||
private bool _history;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _historyIncludeCrArtists;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _historyAddSpecials;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _historySkipUnmonitored;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _historyCountSonarr;
|
||||
|
|
@ -236,7 +243,9 @@ public partial class GeneralSettingsViewModel : ViewModelBase{
|
|||
ProxyUsername = options.ProxyUsername ?? "";
|
||||
ProxyPassword = options.ProxyPassword ?? "";
|
||||
ProxyPort = options.ProxyPort;
|
||||
HistoryIncludeCrArtists = options.HistoryIncludeCrArtists;
|
||||
HistoryAddSpecials = options.HistoryAddSpecials;
|
||||
HistorySkipUnmonitored = options.HistorySkipUnmonitored;
|
||||
HistoryCountSonarr = options.HistoryCountSonarr;
|
||||
DownloadSpeed = options.DownloadSpeedLimit;
|
||||
DownloadToTempFolder = options.DownloadToTempFolder;
|
||||
|
|
@ -265,6 +274,8 @@ public partial class GeneralSettingsViewModel : ViewModelBase{
|
|||
|
||||
CrunchyrollManager.Instance.CrunOptions.DownloadToTempFolder = DownloadToTempFolder;
|
||||
CrunchyrollManager.Instance.CrunOptions.HistoryAddSpecials = HistoryAddSpecials;
|
||||
CrunchyrollManager.Instance.CrunOptions.HistoryIncludeCrArtists = HistoryIncludeCrArtists;
|
||||
CrunchyrollManager.Instance.CrunOptions.HistorySkipUnmonitored = HistorySkipUnmonitored;
|
||||
CrunchyrollManager.Instance.CrunOptions.HistoryCountSonarr = HistoryCountSonarr;
|
||||
CrunchyrollManager.Instance.CrunOptions.DownloadSpeedLimit = Math.Clamp((int)(DownloadSpeed ?? 0), 0, 1000000000);
|
||||
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;
|
||||
|
||||
CfgManager.WriteSettingsToFile();
|
||||
CfgManager.WriteCrSettings();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
|
@ -364,7 +375,7 @@ public partial class GeneralSettingsViewModel : ViewModelBase{
|
|||
pathSetter(selectedFolder.Path.LocalPath);
|
||||
var finalPath = string.IsNullOrEmpty(pathGetter()) ? defaultPath : pathGetter();
|
||||
pathSetter(finalPath);
|
||||
CfgManager.WriteSettingsToFile();
|
||||
CfgManager.WriteCrSettings();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -411,7 +422,7 @@ public partial class GeneralSettingsViewModel : ViewModelBase{
|
|||
pathSetter(selectedFile.Path.LocalPath);
|
||||
var finalPath = string.IsNullOrEmpty(pathGetter()) ? defaultPath : pathGetter();
|
||||
pathSetter(finalPath);
|
||||
CfgManager.WriteSettingsToFile();
|
||||
CfgManager.WriteCrSettings();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -490,9 +501,9 @@ public partial class GeneralSettingsViewModel : ViewModelBase{
|
|||
decompressedJson,
|
||||
CrunchyrollManager.Instance.SettingsJsonSerializerSettings
|
||||
) ?? new ObservableCollection<HistorySeries>();
|
||||
|
||||
|
||||
CrunchyrollManager.Instance.HistoryList = historyList;
|
||||
|
||||
|
||||
Parallel.ForEach(historyList, historySeries => {
|
||||
historySeries.Init();
|
||||
|
||||
|
|
@ -500,14 +511,13 @@ public partial class GeneralSettingsViewModel : ViewModelBase{
|
|||
historySeriesSeason.Init();
|
||||
}
|
||||
});
|
||||
|
||||
} else{
|
||||
CrunchyrollManager.Instance.HistoryList = new ObservableCollection<HistorySeries>();
|
||||
}
|
||||
} else{
|
||||
CrunchyrollManager.Instance.HistoryList = new ObservableCollection<HistorySeries>();
|
||||
}
|
||||
|
||||
|
||||
_ = Task.Run(() => SonarrClient.Instance.RefreshSonarrLite());
|
||||
} else{
|
||||
CrunchyrollManager.Instance.HistoryList = new ObservableCollection<HistorySeries>();
|
||||
|
|
|
|||
|
|
@ -212,6 +212,7 @@
|
|||
</Grid>
|
||||
<StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center" Margin="10">
|
||||
<TextBlock FontStyle="Italic"
|
||||
IsVisible="{Binding HasDubs}"
|
||||
Opacity="0.8" Text="Dubs: ">
|
||||
</TextBlock>
|
||||
<TextBlock Text="{Binding AvailableAudios, Converter={StaticResource UiListToStringConverter}}"
|
||||
|
|
|
|||
|
|
@ -186,15 +186,44 @@
|
|||
Placement="BottomEdgeAlignedRight"
|
||||
PlacementTarget="{Binding ElementName=DropdownButtonFilter}">
|
||||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||
<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 Orientation="Vertical">
|
||||
|
||||
<Grid Margin="8 0 5 0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" /> <!-- TextBlock takes remaining space -->
|
||||
<ColumnDefinition Width="Auto" /> <!-- ToggleSwitch takes only needed space -->
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<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>
|
||||
</Popup>
|
||||
</StackPanel>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
<UserControl.Resources>
|
||||
<ui:UiListToStringConverter x:Key="UiListToStringConverter" />
|
||||
<ui:UiListHasElementsConverter x:Key="UiListHasElementsConverter" />
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid>
|
||||
|
|
@ -57,8 +58,8 @@
|
|||
|
||||
<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="3" 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="3" IsVisible="{Binding SelectedSeries.HistorySeriesAvailableDubLang, Converter={StaticResource UiListHasElementsConverter}}" FontSize="15" Opacity="0.8" TextWrapping="Wrap" Text="{Binding AvailableDubs}"></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 Orientation="Horizontal" Margin="0 10 10 10">
|
||||
|
|
@ -183,7 +184,7 @@
|
|||
|
||||
</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>
|
||||
<StackPanel>
|
||||
<ToggleButton x:Name="DropdownButtonDub" Width="210" HorizontalContentAlignment="Stretch">
|
||||
|
|
@ -313,14 +314,15 @@
|
|||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel>
|
||||
<StackPanel VerticalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<TextBlock Text="E"></TextBlock>
|
||||
<TextBlock Text="{Binding Episode}"></TextBlock>
|
||||
<TextBlock Text=" - "></TextBlock>
|
||||
<TextBlock Text="{Binding EpisodeTitle}"></TextBlock>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"
|
||||
IsVisible="{Binding HistoryEpisodeAvailableDubLang, Converter={StaticResource UiListHasElementsConverter}}">
|
||||
<TextBlock FontStyle="Italic"
|
||||
FontSize="12"
|
||||
Opacity="0.8" Text="Dubs: ">
|
||||
|
|
@ -343,10 +345,30 @@
|
|||
|
||||
<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}">
|
||||
|
||||
|
||||
<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}"
|
||||
Source="../Assets/sonarr.png" Width="25"
|
||||
Height="25" />
|
||||
|
|
@ -382,7 +404,7 @@
|
|||
</Button>
|
||||
|
||||
<Button Margin="0 0 5 0" FontStyle="Italic" HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding DownloadEpisode}"
|
||||
CommandParameter="false">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
|
|
@ -392,9 +414,10 @@
|
|||
<TextBlock Text="Download Episode" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
</Button>
|
||||
|
||||
|
||||
<Button Margin="0 0 5 0" FontStyle="Italic" HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
IsEnabled="{Binding HistoryEpisodeAvailableSoftSubs, Converter={StaticResource UiListHasElementsConverter}}"
|
||||
Command="{Binding DownloadEpisode}"
|
||||
CommandParameter="true">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
|
|
@ -404,8 +427,6 @@
|
|||
<TextBlock Text="Download Subs" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
</Button>
|
||||
|
||||
|
||||
|
||||
|
||||
</StackPanel>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
|
||||
namespace CRD.Views;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,4 @@
|
|||
using System;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using CRD.Utils.Sonarr;
|
||||
using Avalonia.Controls;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.ViewModels;
|
||||
|
||||
|
|
|
|||
|
|
@ -33,17 +33,29 @@
|
|||
</controls:SettingsExpanderItem.Footer>
|
||||
</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.Footer>
|
||||
<CheckBox IsChecked="{Binding HistoryAddSpecials}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</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.Footer>
|
||||
<CheckBox IsChecked="{Binding HistoryCountSonarr}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</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>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue