Add - Option to create video file for each dub downloaded https://github.com/Crunchy-DL/Crunchy-Downloader/issues/60
Add - Filter for history (All, Missing Episodes, Missing Sonarr Episodes, Continuing Only) https://github.com/Crunchy-DL/Crunchy-Downloader/discussions/58 Add - Sonarr button to history to add missing series from sonarr to the history and add all episodes that are missing from sonarr https://github.com/Crunchy-DL/Crunchy-Downloader/discussions/58 Fix - RAM bug with history table view Fix - Custom calendar UTC times https://github.com/Crunchy-DL/Crunchy-Downloader/discussions/59 Fix - Funimation subscription detection https://github.com/Crunchy-DL/Crunchy-Downloader/issues/56
This commit is contained in:
parent
43823a69e8
commit
3f79e45131
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:60deeb982f931b150ed8512e9ae284061722fd3719acc717ca812098ce4547f4
|
||||
size 116598
|
||||
oid sha256:eb8049bb754d9e938ac7adf0b7884274c68d265b7ce7b0c4fa2085f84677296b
|
||||
size 5780
|
||||
|
|
|
@ -181,6 +181,10 @@ public class CalendarManager{
|
|||
foreach (var crBrowseEpisode in newEpisodes){
|
||||
var targetDate = CrunchyrollManager.Instance.CrunOptions.CalendarFilterByAirDate ? crBrowseEpisode.EpisodeMetadata.EpisodeAirDate : crBrowseEpisode.LastPublic;
|
||||
|
||||
if (targetDate.Kind == DateTimeKind.Utc){
|
||||
targetDate = targetDate.ToLocalTime();
|
||||
}
|
||||
|
||||
if (CrunchyrollManager.Instance.CrunOptions.CalendarHideDubs && crBrowseEpisode.EpisodeMetadata.SeasonTitle != null &&
|
||||
(crBrowseEpisode.EpisodeMetadata.SeasonTitle.EndsWith("Dub)") || crBrowseEpisode.EpisodeMetadata.AudioLocale != Locale.JaJp)){
|
||||
continue;
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.Structs;
|
||||
using Newtonsoft.Json;
|
||||
|
@ -13,7 +11,6 @@ using Newtonsoft.Json;
|
|||
namespace CRD.Downloader.Crunchyroll;
|
||||
|
||||
public class CrAuth{
|
||||
|
||||
private readonly CrunchyrollManager crunInstance = CrunchyrollManager.Instance;
|
||||
|
||||
public async Task AuthAnonymous(){
|
||||
|
@ -44,9 +41,7 @@ public class CrAuth{
|
|||
PreferredContentAudioLanguage = "ja-JP",
|
||||
PreferredContentSubtitleLanguage = "de-DE"
|
||||
};
|
||||
|
||||
// CrunchyrollManager.Instance.CmsToken = new CrCmsToken();
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void JsonTokenToFileAndVariable(string content){
|
||||
|
@ -104,35 +99,36 @@ public class CrAuth{
|
|||
|
||||
if (profileTemp != null){
|
||||
crunInstance.Profile = profileTemp;
|
||||
|
||||
|
||||
var requestSubs = HttpClientReq.CreateRequestMessage(Api.Subscription + crunInstance.Token.account_id, HttpMethod.Get, true, false, null);
|
||||
|
||||
var responseSubs = await HttpClientReq.Instance.SendHttpRequest(requestSubs);
|
||||
|
||||
|
||||
if (responseSubs.IsOk){
|
||||
var subsc = Helpers.Deserialize<Subscription>(responseSubs.ResponseContent, crunInstance.SettingsJsonSerializerSettings);
|
||||
crunInstance.Profile.Subscription = subsc;
|
||||
if ( subsc is{ SubscriptionProducts:{ Count: 0 }, ThirdPartySubscriptionProducts.Count: > 0 }){
|
||||
if (subsc is{ SubscriptionProducts:{ Count: 0 }, ThirdPartySubscriptionProducts.Count: > 0 }){
|
||||
var thirdPartySub = subsc.ThirdPartySubscriptionProducts.First();
|
||||
var expiration = thirdPartySub.InGrace ? thirdPartySub.InGraceExpirationDate : thirdPartySub.ExpirationDate;
|
||||
var remaining = expiration - DateTime.UtcNow;
|
||||
crunInstance.Profile.HasPremium = remaining > TimeSpan.Zero;
|
||||
crunInstance.Profile.Subscription.IsActive = remaining > TimeSpan.Zero;
|
||||
crunInstance.Profile.Subscription.NextRenewalDate = expiration;
|
||||
} else if(subsc is{ SubscriptionProducts:{ Count: 0 }, NonrecurringSubscriptionProducts.Count: > 0 }){
|
||||
} else if (subsc is{ SubscriptionProducts:{ Count: 0 }, NonrecurringSubscriptionProducts.Count: > 0 }){
|
||||
var nonRecurringSub = subsc.NonrecurringSubscriptionProducts.First();
|
||||
var remaining = nonRecurringSub.EndDate - DateTime.UtcNow;
|
||||
crunInstance.Profile.HasPremium = remaining > TimeSpan.Zero;
|
||||
crunInstance.Profile.Subscription.IsActive = remaining > TimeSpan.Zero;
|
||||
crunInstance.Profile.Subscription.NextRenewalDate = nonRecurringSub.EndDate;
|
||||
} else if (subsc is{ SubscriptionProducts:{ Count: 0 }, FunimationSubscriptions.Count: > 0 }){
|
||||
crunInstance.Profile.HasPremium = true;
|
||||
} else{
|
||||
crunInstance.Profile.HasPremium = subsc.IsActive;
|
||||
crunInstance.Profile.HasPremium = subsc.IsActive;
|
||||
}
|
||||
} else{
|
||||
crunInstance.Profile.HasPremium = false;
|
||||
Console.Error.WriteLine("Failed to check premium subscription status");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -167,11 +163,10 @@ public class CrAuth{
|
|||
|
||||
if (crunInstance.Token?.refresh_token != null){
|
||||
HttpClientReq.Instance.SetETPCookie(crunInstance.Token.refresh_token);
|
||||
|
||||
|
||||
await GetProfile();
|
||||
}
|
||||
|
||||
// await GetCmsToken();
|
||||
|
||||
}
|
||||
|
||||
public async Task RefreshToken(bool needsToken){
|
||||
|
@ -209,53 +204,7 @@ public class CrAuth{
|
|||
} else{
|
||||
Console.Error.WriteLine("Refresh Token Auth Failed");
|
||||
}
|
||||
|
||||
// await GetCmsToken();
|
||||
|
||||
}
|
||||
|
||||
|
||||
// public async Task GetCmsToken(){
|
||||
// if (crunInstance.Token?.access_token == null){
|
||||
// Console.Error.WriteLine($"Missing Access Token: {crunInstance.Token?.access_token}");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// var request = HttpClientReq.CreateRequestMessage(Api.BetaCmsToken, HttpMethod.Get, true, true, null);
|
||||
//
|
||||
// var response = await HttpClientReq.Instance.SendHttpRequest(request);
|
||||
//
|
||||
// if (response.IsOk){
|
||||
// crunInstance.CmsToken = JsonConvert.DeserializeObject<CrCmsToken>(response.ResponseContent, crunInstance.SettingsJsonSerializerSettings);
|
||||
// } else{
|
||||
// Console.Error.WriteLine("CMS Token Auth Failed");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public async Task GetCmsData(){
|
||||
// if (crunInstance.CmsToken?.Cms == null){
|
||||
// Console.Error.WriteLine("Missing CMS Token");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// UriBuilder uriBuilder = new UriBuilder(Api.BetaCms + crunInstance.CmsToken.Cms.Bucket + "/index?");
|
||||
//
|
||||
// NameValueCollection query = HttpUtility.ParseQueryString(uriBuilder.Query);
|
||||
//
|
||||
// query["preferred_audio_language"] = "ja-JP";
|
||||
// query["Policy"] = crunInstance.CmsToken.Cms.Policy;
|
||||
// query["Signature"] = crunInstance.CmsToken.Cms.Signature;
|
||||
// query["Key-Pair-Id"] = crunInstance.CmsToken.Cms.KeyPairId;
|
||||
//
|
||||
// uriBuilder.Query = query.ToString();
|
||||
//
|
||||
// var request = new HttpRequestMessage(HttpMethod.Get, uriBuilder.ToString());
|
||||
//
|
||||
// var response = await HttpClientReq.Instance.SendHttpRequest(request);
|
||||
//
|
||||
// if (response.IsOk){
|
||||
// Console.WriteLine(response.ResponseContent);
|
||||
// } else{
|
||||
// Console.Error.WriteLine("Failed to Get CMS Index");
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
|
@ -28,7 +28,6 @@ namespace CRD.Downloader.Crunchyroll;
|
|||
|
||||
public class CrunchyrollManager{
|
||||
public CrToken? Token;
|
||||
// public CrCmsToken? CmsToken;
|
||||
|
||||
public CrProfile Profile = new();
|
||||
private readonly Lazy<CrDownloadOptions> _optionsLazy;
|
||||
|
@ -44,6 +43,8 @@ public class CrunchyrollManager{
|
|||
|
||||
#endregion
|
||||
|
||||
public CrBrowseSeriesBase? AllCRSeries;
|
||||
|
||||
|
||||
public string DefaultLocale = "en-US";
|
||||
|
||||
|
@ -170,9 +171,9 @@ public class CrunchyrollManager{
|
|||
} else{
|
||||
HistoryList =[];
|
||||
}
|
||||
|
||||
SonarrClient.Instance.RefreshSonarr();
|
||||
}
|
||||
|
||||
SonarrClient.Instance.RefreshSonarr();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -215,27 +216,55 @@ public class CrunchyrollManager{
|
|||
};
|
||||
|
||||
QueueManager.Instance.Queue.Refresh();
|
||||
|
||||
if (CrunOptions is{ DlVideoOnce: false, KeepDubsSeperate: true }){
|
||||
var groupByDub = Helpers.GroupByLanguageWithSubtitles(res.Data);
|
||||
|
||||
await MuxStreams(res.Data,
|
||||
new CrunchyMuxOptions{
|
||||
FfmpegOptions = options.FfmpegOptions,
|
||||
SkipSubMux = options.SkipSubsMux,
|
||||
Output = res.FileName,
|
||||
Mp4 = options.Mp4,
|
||||
VideoTitle = res.VideoTitle,
|
||||
Novids = options.Novids,
|
||||
NoCleanup = options.Nocleanup,
|
||||
DefaultAudio = Languages.FindLang(options.DefaultAudio),
|
||||
DefaultSub = Languages.FindLang(options.DefaultSub),
|
||||
MkvmergeOptions = options.MkvmergeOptions,
|
||||
ForceMuxer = options.Force,
|
||||
SyncTiming = options.SyncTiming,
|
||||
CcTag = options.CcTag,
|
||||
KeepAllVideos = true,
|
||||
MuxDescription = options.IncludeVideoDescription
|
||||
},
|
||||
res.FileName);
|
||||
|
||||
foreach (var keyValue in groupByDub){
|
||||
await MuxStreams(keyValue.Value,
|
||||
new CrunchyMuxOptions{
|
||||
FfmpegOptions = options.FfmpegOptions,
|
||||
SkipSubMux = options.SkipSubsMux,
|
||||
Output = res.FileName,
|
||||
Mp4 = options.Mp4,
|
||||
VideoTitle = res.VideoTitle,
|
||||
Novids = options.Novids,
|
||||
NoCleanup = options.Nocleanup,
|
||||
DefaultAudio = Languages.FindLang(options.DefaultAudio),
|
||||
DefaultSub = Languages.FindLang(options.DefaultSub),
|
||||
MkvmergeOptions = options.MkvmergeOptions,
|
||||
ForceMuxer = options.Force,
|
||||
SyncTiming = options.SyncTiming,
|
||||
CcTag = options.CcTag,
|
||||
KeepAllVideos = true,
|
||||
MuxDescription = options.IncludeVideoDescription
|
||||
},
|
||||
res.FileName);
|
||||
}
|
||||
|
||||
|
||||
} else{
|
||||
await MuxStreams(res.Data,
|
||||
new CrunchyMuxOptions{
|
||||
FfmpegOptions = options.FfmpegOptions,
|
||||
SkipSubMux = options.SkipSubsMux,
|
||||
Output = res.FileName,
|
||||
Mp4 = options.Mp4,
|
||||
VideoTitle = res.VideoTitle,
|
||||
Novids = options.Novids,
|
||||
NoCleanup = options.Nocleanup,
|
||||
DefaultAudio = Languages.FindLang(options.DefaultAudio),
|
||||
DefaultSub = Languages.FindLang(options.DefaultSub),
|
||||
MkvmergeOptions = options.MkvmergeOptions,
|
||||
ForceMuxer = options.Force,
|
||||
SyncTiming = options.SyncTiming,
|
||||
CcTag = options.CcTag,
|
||||
KeepAllVideos = true,
|
||||
MuxDescription = options.IncludeVideoDescription
|
||||
},
|
||||
res.FileName);
|
||||
}
|
||||
|
||||
data.DownloadProgress = new DownloadProgress(){
|
||||
IsDownloading = true,
|
||||
Done = true,
|
||||
|
|
|
@ -544,6 +544,8 @@ public class History(){
|
|||
}
|
||||
}
|
||||
|
||||
private static readonly object _lock = new object();
|
||||
|
||||
public async Task MatchHistoryEpisodesWithSonarr(bool updateAll, HistorySeries historySeries){
|
||||
if (crunInstance.CrunOptions.SonarrProperties is{ SonarrEnabled: false }){
|
||||
return;
|
||||
|
@ -574,9 +576,13 @@ public class History(){
|
|||
historyEpisode.SonarrHasFile = episode.HasFile;
|
||||
historyEpisode.SonarrAbsolutNumber = episode.AbsoluteEpisodeNumber + "";
|
||||
historyEpisode.SonarrSeasonNumber = episode.SeasonNumber + "";
|
||||
episodes.Remove(episode);
|
||||
lock (_lock) {
|
||||
episodes.Remove(episode);
|
||||
}
|
||||
} else{
|
||||
failedEpisodes.Add(historyEpisode);
|
||||
lock (_lock) {
|
||||
failedEpisodes.Add(historyEpisode);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -598,7 +604,9 @@ public class History(){
|
|||
historyEpisode.SonarrHasFile = episode.HasFile;
|
||||
historyEpisode.SonarrAbsolutNumber = episode.AbsoluteEpisodeNumber + "";
|
||||
historyEpisode.SonarrSeasonNumber = episode.SeasonNumber + "";
|
||||
episodes.Remove(episode);
|
||||
lock (_lock) {
|
||||
episodes.Remove(episode);
|
||||
}
|
||||
} else{
|
||||
var episode1 = episodes.Find(ele => {
|
||||
if (ele == null){
|
||||
|
@ -614,7 +622,9 @@ public class History(){
|
|||
historyEpisode.SonarrHasFile = episode1.HasFile;
|
||||
historyEpisode.SonarrAbsolutNumber = episode1.AbsoluteEpisodeNumber + "";
|
||||
historyEpisode.SonarrSeasonNumber = episode1.SeasonNumber + "";
|
||||
episodes.Remove(episode1);
|
||||
lock (_lock) {
|
||||
episodes.Remove(episode1);
|
||||
}
|
||||
} else{
|
||||
var episode2 = episodes.Find(ele => {
|
||||
if (ele == null){
|
||||
|
@ -629,7 +639,9 @@ public class History(){
|
|||
historyEpisode.SonarrHasFile = episode2.HasFile;
|
||||
historyEpisode.SonarrAbsolutNumber = episode2.AbsoluteEpisodeNumber + "";
|
||||
historyEpisode.SonarrSeasonNumber = episode2.SeasonNumber + "";
|
||||
episodes.Remove(episode2);
|
||||
lock (_lock) {
|
||||
episodes.Remove(episode2);
|
||||
}
|
||||
} else{
|
||||
Console.Error.WriteLine($"Could not match episode {historyEpisode.EpisodeTitle} to sonarr episode");
|
||||
}
|
||||
|
@ -700,6 +712,27 @@ public class History(){
|
|||
|
||||
return highestSimilarity < 0.8 ? null : closestMatch;
|
||||
}
|
||||
|
||||
public CrBrowseSeries? FindClosestMatchCrSeries(List<CrBrowseSeries> episodeList, string title){
|
||||
CrBrowseSeries? closestMatch = null;
|
||||
double highestSimilarity = 0.0;
|
||||
object lockObject = new object(); // To synchronize access to shared variables
|
||||
|
||||
Parallel.ForEach(episodeList, episode => {
|
||||
if (episode != null){
|
||||
double similarity = CalculateSimilarity(episode.Title, title);
|
||||
lock (lockObject) // Ensure thread-safe access to shared variables
|
||||
{
|
||||
if (similarity > highestSimilarity){
|
||||
highestSimilarity = similarity;
|
||||
closestMatch = episode;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return highestSimilarity < 0.8 ? null : closestMatch;
|
||||
}
|
||||
|
||||
private double CalculateSimilarity(string source, string target){
|
||||
int distance = LevenshteinDistance(source, target);
|
||||
|
|
|
@ -191,6 +191,17 @@ public enum SortingType{
|
|||
HistorySeriesAddDate,
|
||||
}
|
||||
|
||||
public enum FilterType{
|
||||
[EnumMember(Value = "All")]
|
||||
All,
|
||||
[EnumMember(Value = "Missing Episodes")]
|
||||
MissingEpisodes,
|
||||
[EnumMember(Value = "Missing Episodes Sonarr")]
|
||||
MissingEpisodesSonarr,
|
||||
[EnumMember(Value = "Continuing Only")]
|
||||
ContinuingOnly,
|
||||
}
|
||||
|
||||
public enum SonarrCoverType{
|
||||
Banner,
|
||||
FanArt,
|
||||
|
|
|
@ -8,6 +8,7 @@ using System.Runtime.Serialization;
|
|||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Media.Imaging;
|
||||
using CRD.Utils.Structs;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CRD.Utils;
|
||||
|
@ -157,7 +158,7 @@ public class Helpers{
|
|||
var title = chapters[i].Title;
|
||||
var endTime = (i + 1 < chapters.Count) ? chapters[i + 1].StartTime : startTime + 10000; // Add 10 seconds to the last chapter end time
|
||||
|
||||
if (endTime < startTime) {
|
||||
if (endTime < startTime){
|
||||
endTime = startTime + 10000; // Correct end time if it is before start time
|
||||
}
|
||||
|
||||
|
@ -210,12 +211,12 @@ public class Helpers{
|
|||
return (IsOk: false, ErrorCode: -1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void DeleteFile(string filePath){
|
||||
if (string.IsNullOrEmpty(filePath)){
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try{
|
||||
if (File.Exists(filePath)){
|
||||
File.Delete(filePath);
|
||||
|
@ -225,8 +226,8 @@ public class Helpers{
|
|||
// Handle exceptions if you need to log them or throw
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<(bool IsOk, int ErrorCode)> ExecuteCommandAsyncWorkDir(string type, string bin, string command,string workingDir){
|
||||
|
||||
public static async Task<(bool IsOk, int ErrorCode)> ExecuteCommandAsyncWorkDir(string type, string bin, string command, string workingDir){
|
||||
try{
|
||||
using (var process = new Process()){
|
||||
process.StartInfo.WorkingDirectory = workingDir;
|
||||
|
@ -345,8 +346,8 @@ public class Helpers{
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static async Task<Bitmap?> LoadImage(string imageUrl){
|
||||
try{
|
||||
using (var client = new HttpClient()){
|
||||
|
@ -363,5 +364,18 @@ public class Helpers{
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static Dictionary<string, List<DownloadedMedia>> GroupByLanguageWithSubtitles(List<DownloadedMedia> allMedia){
|
||||
var languageGroups = allMedia
|
||||
.GroupBy(media => {
|
||||
if (media.Type == DownloadMediaType.Subtitle && media.RelatedVideoDownloadMedia != null){
|
||||
return media.RelatedVideoDownloadMedia.Lang.CrLocale;
|
||||
}
|
||||
|
||||
return media.Lang.CrLocale;
|
||||
})
|
||||
.ToDictionary(group => group.Key, group => group.ToList());
|
||||
|
||||
return languageGroups;
|
||||
}
|
||||
}
|
|
@ -82,9 +82,9 @@ public class Merger{
|
|||
args.Add($"-i \"{sub.value.File}\"");
|
||||
metaData.Add($"-map {index}:s");
|
||||
if (options.Defaults.Sub.Code == sub.value.Language.Code && CrunchyrollManager.Instance.CrunOptions.DefaultSubSigns == sub.value.Signs && sub.value.ClosedCaption == false){
|
||||
args.Add($"-disposition:s:{sub.i} default");
|
||||
metaData.Add($"-disposition:s:{sub.i} default");
|
||||
} else{
|
||||
args.Add($"-disposition:s:{sub.i} 0");
|
||||
metaData.Add($"-disposition:s:{sub.i} 0");
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
|
|
@ -110,6 +110,9 @@ public class CrDownloadOptions{
|
|||
|
||||
[YamlMember(Alias = "dl_video_once", ApplyNamingConventions = false)]
|
||||
public bool DlVideoOnce{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "keep_dubs_seperate", ApplyNamingConventions = false)]
|
||||
public bool KeepDubsSeperate{ get; set; }
|
||||
|
||||
[YamlIgnore]
|
||||
public bool? Skipmux{ get; set; }
|
||||
|
@ -159,6 +162,9 @@ public class CrDownloadOptions{
|
|||
[YamlMember(Alias = "history_add_specials", ApplyNamingConventions = false)]
|
||||
public bool HistoryAddSpecials{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "history_count_sonarr", ApplyNamingConventions = false)]
|
||||
public bool HistoryCountSonarr{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "sonarr_properties", ApplyNamingConventions = false)]
|
||||
public SonarrProperties? SonarrProperties{ get; set; }
|
||||
|
||||
|
|
|
@ -46,6 +46,9 @@ public class Subscription{
|
|||
|
||||
[JsonProperty("nonrecurring_subscription_products")]
|
||||
public List<NonRecurringSubscriptionProduct>? NonrecurringSubscriptionProducts{ get; set; }
|
||||
|
||||
[JsonProperty("funimation_subscriptions")]
|
||||
public List<object>? FunimationSubscriptions{ get; set; }
|
||||
}
|
||||
|
||||
public class NonRecurringSubscriptionProduct{
|
||||
|
|
|
@ -63,6 +63,9 @@ public class HistorySeries : INotifyPropertyChanged{
|
|||
[JsonIgnore]
|
||||
public Bitmap? ThumbnailImage{ get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsImageLoaded{ get; private set; } = false;
|
||||
|
||||
[JsonIgnore]
|
||||
public bool FetchingData{ get; set; }
|
||||
|
||||
|
@ -168,17 +171,19 @@ public class HistorySeries : INotifyPropertyChanged{
|
|||
#endregion
|
||||
|
||||
public async Task LoadImage(){
|
||||
if (IsImageLoaded || string.IsNullOrEmpty(ThumbnailImageUrl))
|
||||
return;
|
||||
|
||||
try{
|
||||
using (var client = new HttpClient()){
|
||||
var response = await client.GetAsync(ThumbnailImageUrl);
|
||||
response.EnsureSuccessStatusCode();
|
||||
using (var stream = await response.Content.ReadAsStreamAsync()){
|
||||
ThumbnailImage = new Bitmap(stream);
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ThumbnailImage)));
|
||||
}
|
||||
}
|
||||
using var client = new HttpClient();
|
||||
var response = await client.GetAsync(ThumbnailImageUrl);
|
||||
response.EnsureSuccessStatusCode();
|
||||
using var stream = await response.Content.ReadAsStreamAsync();
|
||||
ThumbnailImage = new Bitmap(stream);
|
||||
IsImageLoaded = true;
|
||||
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ThumbnailImage)));
|
||||
} catch (Exception ex){
|
||||
// Handle exceptions
|
||||
Console.Error.WriteLine("Failed to load image: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
@ -187,50 +192,67 @@ 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;
|
||||
|
||||
for (int i = Seasons.Count - 1; i >= 0; i--){
|
||||
var season = Seasons[i];
|
||||
if (sonarrEnabled && CrunchyrollManager.Instance.CrunOptions.HistoryCountSonarr && !string.IsNullOrEmpty(SonarrSeriesId)){
|
||||
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 (!episodes[j].WasDownloaded){
|
||||
count++;
|
||||
}
|
||||
var episodesList = season.EpisodesList;
|
||||
for (int j = episodesList.Count - 1; j >= 0; j--){
|
||||
var episode = episodesList[j];
|
||||
|
||||
if (!string.IsNullOrEmpty(episode.SonarrEpisodeId) && !episode.SonarrHasFile){
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
} else{
|
||||
for (int i = Seasons.Count - 1; i >= 0; i--){
|
||||
var season = Seasons[i];
|
||||
|
||||
var episodesList = season.EpisodesList;
|
||||
for (int j = episodesList.Count - 1; j >= 0; j--){
|
||||
var episode = episodesList[j];
|
||||
|
||||
if (episode.SpecialEpisode){
|
||||
if (historyAddSpecials && !episode.WasDownloaded){
|
||||
count++;
|
||||
if (season.SpecialSeason == true){
|
||||
if (historyAddSpecials){
|
||||
var episodes = season.EpisodesList;
|
||||
for (int j = episodes.Count - 1; j >= 0; j--){
|
||||
if (!episodes[j].WasDownloaded){
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!episode.WasDownloaded && !foundWatched){
|
||||
count++;
|
||||
} else{
|
||||
foundWatched = true;
|
||||
if (!historyAddSpecials){
|
||||
break;
|
||||
var episodesList = season.EpisodesList;
|
||||
for (int j = episodesList.Count - 1; j >= 0; j--){
|
||||
var episode = episodesList[j];
|
||||
|
||||
if (episode.SpecialEpisode){
|
||||
if (historyAddSpecials && !episode.WasDownloaded){
|
||||
count++;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!episode.WasDownloaded && !foundWatched){
|
||||
count++;
|
||||
} else{
|
||||
foundWatched = true;
|
||||
if (!historyAddSpecials){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (foundWatched && !historyAddSpecials){
|
||||
break;
|
||||
if (foundWatched && !historyAddSpecials){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
NewEpisodes = count;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NewEpisodes)));
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ public partial class AccountPageViewModel : ViewModelBase{
|
|||
private static DispatcherTimer? _timer;
|
||||
private DateTime _targetTime;
|
||||
private bool IsCancelled = false;
|
||||
private bool UnknownEndDate = false;
|
||||
|
||||
public AccountPageViewModel(){
|
||||
UpdatetProfile();
|
||||
|
@ -68,6 +69,9 @@ public partial class AccountPageViewModel : ViewModelBase{
|
|||
}
|
||||
}else if(CrunchyrollManager.Instance.Profile.Subscription?.NonrecurringSubscriptionProducts.Count >= 1){
|
||||
IsCancelled = true;
|
||||
}else if(CrunchyrollManager.Instance.Profile.Subscription?.FunimationSubscriptions.Count >= 1){
|
||||
IsCancelled = true;
|
||||
UnknownEndDate = true;
|
||||
}
|
||||
|
||||
if (CrunchyrollManager.Instance.Profile.Subscription?.NextRenewalDate != null){
|
||||
|
@ -92,6 +96,10 @@ public partial class AccountPageViewModel : ViewModelBase{
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
if (UnknownEndDate){
|
||||
RemainingTime = "Unknown Subscription end date";
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
@ -14,85 +16,116 @@ using CommunityToolkit.Mvvm.Input;
|
|||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.Sonarr;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.History;
|
||||
using CRD.Views;
|
||||
using DynamicData;
|
||||
using HarfBuzzSharp;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace CRD.ViewModels;
|
||||
|
||||
public partial class HistoryPageViewModel : ViewModelBase{
|
||||
public ObservableCollection<HistorySeries> Items{ get; }
|
||||
public ObservableCollection<HistorySeries> FilteredItems{ get; }
|
||||
|
||||
[ObservableProperty]
|
||||
private static bool _fetchingData;
|
||||
|
||||
[ObservableProperty]
|
||||
public HistorySeries _selectedSeries;
|
||||
private HistorySeries _selectedSeries;
|
||||
|
||||
[ObservableProperty]
|
||||
public static bool _editMode;
|
||||
private static bool _editMode;
|
||||
|
||||
[ObservableProperty]
|
||||
public double _scaleValue;
|
||||
private double _scaleValue;
|
||||
|
||||
[ObservableProperty]
|
||||
public ComboBoxItem _selectedView;
|
||||
private ComboBoxItem? _selectedView;
|
||||
|
||||
public ObservableCollection<ComboBoxItem> ViewsList{ get; } =[];
|
||||
|
||||
[ObservableProperty]
|
||||
public SortingListElement _selectedSorting;
|
||||
private SortingListElement? _selectedSorting;
|
||||
|
||||
public ObservableCollection<SortingListElement> SortingList{ get; } =[];
|
||||
|
||||
[ObservableProperty]
|
||||
public double _posterWidth;
|
||||
private FilterListElement? _selectedFilter;
|
||||
|
||||
public ObservableCollection<FilterListElement> FilterList{ get; } =[];
|
||||
|
||||
[ObservableProperty]
|
||||
public double _posterHeight;
|
||||
private double _posterWidth;
|
||||
|
||||
[ObservableProperty]
|
||||
public double _posterImageWidth;
|
||||
private double _posterHeight;
|
||||
|
||||
[ObservableProperty]
|
||||
public double _posterImageHeight;
|
||||
private double _posterImageWidth;
|
||||
|
||||
[ObservableProperty]
|
||||
public double _posterTextSize;
|
||||
private double _posterImageHeight;
|
||||
|
||||
[ObservableProperty]
|
||||
public Thickness _cornerMargin;
|
||||
|
||||
private HistoryViewType currentViewType = HistoryViewType.Posters;
|
||||
private double _posterTextSize;
|
||||
|
||||
[ObservableProperty]
|
||||
public bool _isPosterViewSelected = false;
|
||||
private Thickness _cornerMargin;
|
||||
|
||||
|
||||
[ObservableProperty]
|
||||
public bool _isTableViewSelected = false;
|
||||
private bool _isPosterViewSelected = false;
|
||||
|
||||
[ObservableProperty]
|
||||
public static bool _viewSelectionOpen;
|
||||
private bool _isTableViewSelected = false;
|
||||
|
||||
[ObservableProperty]
|
||||
public static bool _sortingSelectionOpen;
|
||||
private static bool _viewSelectionOpen;
|
||||
|
||||
[ObservableProperty]
|
||||
private static bool _sortingSelectionOpen;
|
||||
|
||||
[ObservableProperty]
|
||||
private static bool _addingMissingSonarrSeries;
|
||||
|
||||
[ObservableProperty]
|
||||
private static bool _sonarrOptionsOpen;
|
||||
|
||||
private IStorageProvider _storageProvider;
|
||||
|
||||
private SortingType currentSortingType = SortingType.NextAirDate;
|
||||
private HistoryViewType currentViewType;
|
||||
|
||||
private SortingType currentSortingType;
|
||||
|
||||
private FilterType currentFilterType;
|
||||
|
||||
[ObservableProperty]
|
||||
public static bool _sortDir = false;
|
||||
private static bool _sortDir = false;
|
||||
|
||||
[ObservableProperty]
|
||||
private static bool _sonarrAvailable;
|
||||
|
||||
[ObservableProperty]
|
||||
private static string _progressText;
|
||||
|
||||
public HistoryPageViewModel(){
|
||||
if (CrunchyrollManager.Instance.CrunOptions.SonarrProperties != null){
|
||||
SonarrAvailable = CrunchyrollManager.Instance.CrunOptions.SonarrProperties.SonarrEnabled;
|
||||
} else{
|
||||
SonarrAvailable = false;
|
||||
}
|
||||
|
||||
Items = CrunchyrollManager.Instance.HistoryList;
|
||||
FilteredItems = new ObservableCollection<HistorySeries>();
|
||||
|
||||
HistoryPageProperties? properties = CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties;
|
||||
|
||||
currentViewType = properties?.SelectedView ?? HistoryViewType.Posters;
|
||||
currentSortingType = properties?.SelectedSorting ?? SortingType.SeriesTitle;
|
||||
currentFilterType = properties?.SelectedFilter ?? FilterType.All;
|
||||
ScaleValue = properties?.ScaleValue ?? 0.73;
|
||||
SortDir = properties?.Ascending ?? false;
|
||||
|
||||
|
@ -112,6 +145,19 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
}
|
||||
}
|
||||
|
||||
foreach (FilterType filterType in Enum.GetValues(typeof(FilterType))){
|
||||
|
||||
if (!SonarrAvailable && (filterType == FilterType.MissingEpisodesSonarr || filterType == FilterType.ContinuingOnly)){
|
||||
continue;
|
||||
}
|
||||
|
||||
var item = new FilterListElement(){ FilterTitle = filterType.GetEnumMemberValue(), SelectedType = filterType };
|
||||
FilterList.Add(item);
|
||||
if (filterType == currentFilterType){
|
||||
SelectedFilter = item;
|
||||
}
|
||||
}
|
||||
|
||||
IsPosterViewSelected = currentViewType == HistoryViewType.Posters;
|
||||
IsTableViewSelected = currentViewType == HistoryViewType.Table;
|
||||
|
||||
|
@ -172,6 +218,10 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
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");
|
||||
}
|
||||
|
@ -180,17 +230,46 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
UpdateSettings();
|
||||
}
|
||||
|
||||
private bool TryParseEnum<T>(string value, out T result) where T : struct, Enum{
|
||||
foreach (var field in typeof(T).GetFields()){
|
||||
var attribute = field.GetCustomAttribute<EnumMemberAttribute>();
|
||||
if (attribute != null && attribute.Value == value){
|
||||
result = (T)field.GetValue(null);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
result = default;
|
||||
return false;
|
||||
partial void OnSelectedFilterChanged(FilterListElement? value){
|
||||
|
||||
if (value == null){
|
||||
return;
|
||||
}
|
||||
|
||||
currentFilterType = value.SelectedType;
|
||||
if (CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties != null) CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties.SelectedFilter = currentFilterType;
|
||||
|
||||
|
||||
switch (currentFilterType){
|
||||
case FilterType.All:
|
||||
FilteredItems.Clear();
|
||||
FilteredItems.AddRange(Items);
|
||||
break;
|
||||
case FilterType.MissingEpisodes:
|
||||
List<HistorySeries> filteredItems = Items.Where(item => item.NewEpisodes > 0).ToList();
|
||||
FilteredItems.Clear();
|
||||
FilteredItems.AddRange(filteredItems);
|
||||
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
|
||||
.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);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -230,6 +309,7 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
if (objectToRemove != null){
|
||||
CrunchyrollManager.Instance.HistoryList.Remove(objectToRemove);
|
||||
Items.Remove(objectToRemove);
|
||||
FilteredItems.Remove(objectToRemove);
|
||||
CfgManager.UpdateHistoryFile();
|
||||
}
|
||||
}
|
||||
|
@ -245,18 +325,18 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async void RefreshAll(){
|
||||
public async Task RefreshAll(){
|
||||
FetchingData = true;
|
||||
RaisePropertyChanged(nameof(FetchingData));
|
||||
for (int i = 0; i < Items.Count; i++){
|
||||
Items[i].SetFetchingData();
|
||||
foreach (var item in FilteredItems){
|
||||
item.SetFetchingData();
|
||||
}
|
||||
|
||||
for (int i = 0; i < Items.Count; i++){
|
||||
for (int i = 0; i < FilteredItems.Count; i++){
|
||||
FetchingData = true;
|
||||
RaisePropertyChanged(nameof(FetchingData));
|
||||
await Items[i].FetchData("");
|
||||
Items[i].UpdateNewEpisodes();
|
||||
await FilteredItems[i].FetchData("");
|
||||
FilteredItems[i].UpdateNewEpisodes();
|
||||
}
|
||||
|
||||
FetchingData = false;
|
||||
|
@ -266,8 +346,76 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
|
||||
[RelayCommand]
|
||||
public async void AddMissingToQueue(){
|
||||
for (int i = 0; i < Items.Count; i++){
|
||||
await Items[i].AddNewMissingToDownloads();
|
||||
var tasks = FilteredItems
|
||||
.Select(item => item.AddNewMissingToDownloads());
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task DownloadMissingSonarr(){
|
||||
await Task.WhenAll(
|
||||
FilteredItems.Where(series => !string.IsNullOrEmpty(series.SonarrSeriesId))
|
||||
.SelectMany(item => item.Seasons)
|
||||
.SelectMany(season => season.EpisodesList)
|
||||
.Where(historyEpisode => !string.IsNullOrEmpty(historyEpisode.SonarrEpisodeId) && !historyEpisode.SonarrHasFile)
|
||||
.Select(historyEpisode => historyEpisode.DownloadEpisode())
|
||||
);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task AddMissingSonarrSeriesToHistory(){
|
||||
SonarrOptionsOpen = false;
|
||||
AddingMissingSonarrSeries = true;
|
||||
FetchingData = true;
|
||||
|
||||
var crInstance = CrunchyrollManager.Instance;
|
||||
|
||||
if (crInstance.AllCRSeries == null){
|
||||
crInstance.AllCRSeries = await crInstance.CrSeries.GetAllSeries(string.IsNullOrEmpty(crInstance.CrunOptions.HistoryLang) ? crInstance.DefaultLocale : crInstance.CrunOptions.HistoryLang);
|
||||
}
|
||||
|
||||
if (crInstance.AllCRSeries?.Data is{ Count: > 0 }){
|
||||
var concurrentSeriesIds = new ConcurrentBag<string>();
|
||||
|
||||
Parallel.ForEach(SonarrClient.Instance.SonarrSeries, series => {
|
||||
if (crInstance.HistoryList.All(historySeries => historySeries.SonarrSeriesId != series.Id.ToString())){
|
||||
var match = crInstance.History.FindClosestMatchCrSeries(crInstance.AllCRSeries.Data, series.Title);
|
||||
|
||||
if (match != null){
|
||||
Console.WriteLine($"[Sonarr Match] Found match with {series.Title} and CR - {match.Title}");
|
||||
if (!string.IsNullOrEmpty(match.Id)){
|
||||
concurrentSeriesIds.Add(match.Id);
|
||||
} else{
|
||||
Console.Error.WriteLine($"[Sonarr Match] Series ID empty for {series.Title}");
|
||||
}
|
||||
} else{
|
||||
Console.Error.WriteLine($"[Sonarr Match] Could not match {series.Title}");
|
||||
}
|
||||
} else{
|
||||
Console.Error.WriteLine($"[Sonarr Match] {series.Title} already matched");
|
||||
}
|
||||
});
|
||||
|
||||
var seriesIds = concurrentSeriesIds.ToList();
|
||||
var totalSeries = seriesIds.Count;
|
||||
|
||||
for (int count = 0; count < totalSeries; count++){
|
||||
ProgressText = $"{count + 1}/{totalSeries}";
|
||||
|
||||
// Await the CRUpdateSeries task for each seriesId
|
||||
await crInstance.History.CRUpdateSeries(seriesIds[count], "");
|
||||
}
|
||||
|
||||
// var updateTasks = seriesIds.Select(seriesId => crInstance.History.CRUpdateSeries(seriesId, ""));
|
||||
// await Task.WhenAll(updateTasks);
|
||||
}
|
||||
|
||||
ProgressText = "";
|
||||
AddingMissingSonarrSeries = false;
|
||||
FetchingData = false;
|
||||
if (SelectedFilter != null){
|
||||
OnSelectedFilterChanged(SelectedFilter);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -292,7 +440,6 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
season.SeasonDownloadPath = selectedFolder.Path.LocalPath;
|
||||
CfgManager.UpdateHistoryFile();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -319,26 +466,31 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[RelayCommand]
|
||||
public async Task DownloadSeasonAll(HistorySeason season){
|
||||
foreach (var historyEpisode in season.EpisodesList){
|
||||
await historyEpisode.DownloadEpisode();
|
||||
}
|
||||
var downloadTasks = season.EpisodesList
|
||||
.Select(episode => episode.DownloadEpisode());
|
||||
|
||||
await Task.WhenAll(downloadTasks);
|
||||
}
|
||||
|
||||
|
||||
[RelayCommand]
|
||||
public async Task DownloadSeasonMissing(HistorySeason season){
|
||||
foreach (var historyEpisode in season.EpisodesList.Where(historyEpisode => !historyEpisode.WasDownloaded)){
|
||||
await historyEpisode.DownloadEpisode();
|
||||
}
|
||||
var downloadTasks = season.EpisodesList
|
||||
.Where(episode => !episode.WasDownloaded)
|
||||
.Select(episode => episode.DownloadEpisode());
|
||||
|
||||
await Task.WhenAll(downloadTasks);
|
||||
}
|
||||
|
||||
|
||||
[RelayCommand]
|
||||
public async Task DownloadSeasonMissingSonarr(HistorySeason season){
|
||||
foreach (var historyEpisode in season.EpisodesList.Where(historyEpisode => !historyEpisode.SonarrHasFile)){
|
||||
await historyEpisode.DownloadEpisode();
|
||||
}
|
||||
var downloadTasks = season.EpisodesList
|
||||
.Where(episode => !episode.SonarrHasFile)
|
||||
.Select(episode => episode.DownloadEpisode());
|
||||
|
||||
await Task.WhenAll(downloadTasks);
|
||||
}
|
||||
|
||||
|
||||
|
@ -350,6 +502,7 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
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; }
|
||||
|
@ -358,4 +511,9 @@ public class HistoryPageProperties(){
|
|||
public class SortingListElement(){
|
||||
public SortingType SelectedSorting{ get; set; }
|
||||
public string? SortingTitle{ get; set; }
|
||||
}
|
||||
|
||||
public class FilterListElement(){
|
||||
public FilterType SelectedType{ get; set; }
|
||||
public string? FilterTitle{ get; set; }
|
||||
}
|
|
@ -82,23 +82,28 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
|
||||
[RelayCommand]
|
||||
public async Task DownloadSeasonAll(HistorySeason season){
|
||||
foreach (var historyEpisode in season.EpisodesList){
|
||||
await historyEpisode.DownloadEpisode();
|
||||
}
|
||||
var downloadTasks = season.EpisodesList
|
||||
.Select(episode => episode.DownloadEpisode());
|
||||
|
||||
await Task.WhenAll(downloadTasks);
|
||||
}
|
||||
|
||||
|
||||
[RelayCommand]
|
||||
public async Task DownloadSeasonMissing(HistorySeason season){
|
||||
foreach (var historyEpisode in season.EpisodesList.Where(historyEpisode => !historyEpisode.WasDownloaded)){
|
||||
await historyEpisode.DownloadEpisode();
|
||||
}
|
||||
var downloadTasks = season.EpisodesList
|
||||
.Where(episode => !episode.WasDownloaded)
|
||||
.Select(episode => episode.DownloadEpisode());
|
||||
|
||||
await Task.WhenAll(downloadTasks);
|
||||
}
|
||||
|
||||
|
||||
[RelayCommand]
|
||||
public async Task DownloadSeasonMissingSonarr(HistorySeason season){
|
||||
foreach (var historyEpisode in season.EpisodesList.Where(historyEpisode => !historyEpisode.SonarrHasFile)){
|
||||
await historyEpisode.DownloadEpisode();
|
||||
}
|
||||
var downloadTasks = season.EpisodesList
|
||||
.Where(episode => !episode.SonarrHasFile)
|
||||
.Select(episode => episode.DownloadEpisode());
|
||||
|
||||
await Task.WhenAll(downloadTasks);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
|
|
@ -68,6 +68,9 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
|
||||
[ObservableProperty]
|
||||
private bool _downloadVideoForEveryDub;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _keepDubsSeparate;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _skipSubMux;
|
||||
|
@ -78,6 +81,9 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
[ObservableProperty]
|
||||
private bool _historyAddSpecials;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _historyCountSonarr;
|
||||
|
||||
[ObservableProperty]
|
||||
private double? _leadingNumbers;
|
||||
|
||||
|
@ -388,6 +394,7 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
DefaultSubForcedDisplay = options.DefaultSubForcedDisplay;
|
||||
DefaultSubSigns = options.DefaultSubSigns;
|
||||
HistoryAddSpecials = options.HistoryAddSpecials;
|
||||
HistoryCountSonarr = options.HistoryCountSonarr;
|
||||
DownloadSpeed = options.DownloadSpeedLimit;
|
||||
IncludeEpisodeDescription = options.IncludeVideoDescription;
|
||||
FileTitle = options.VideoTitle ?? "";
|
||||
|
@ -395,6 +402,7 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
DownloadVideo = !options.Novids;
|
||||
DownloadAudio = !options.Noaudio;
|
||||
DownloadVideoForEveryDub = !options.DlVideoOnce;
|
||||
KeepDubsSeparate = options.KeepDubsSeperate;
|
||||
DownloadChapters = options.Chapters;
|
||||
MuxToMp4 = options.Mp4;
|
||||
SyncTimings = options.SyncTiming;
|
||||
|
@ -457,10 +465,12 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
CrunchyrollManager.Instance.CrunOptions.DefaultSubForcedDisplay = DefaultSubForcedDisplay;
|
||||
CrunchyrollManager.Instance.CrunOptions.IncludeVideoDescription = IncludeEpisodeDescription;
|
||||
CrunchyrollManager.Instance.CrunOptions.HistoryAddSpecials = HistoryAddSpecials;
|
||||
CrunchyrollManager.Instance.CrunOptions.HistoryCountSonarr = HistoryCountSonarr;
|
||||
CrunchyrollManager.Instance.CrunOptions.VideoTitle = FileTitle;
|
||||
CrunchyrollManager.Instance.CrunOptions.Novids = !DownloadVideo;
|
||||
CrunchyrollManager.Instance.CrunOptions.Noaudio = !DownloadAudio;
|
||||
CrunchyrollManager.Instance.CrunOptions.DlVideoOnce = !DownloadVideoForEveryDub;
|
||||
CrunchyrollManager.Instance.CrunOptions.KeepDubsSeperate = KeepDubsSeparate;
|
||||
CrunchyrollManager.Instance.CrunOptions.Chapters = DownloadChapters;
|
||||
CrunchyrollManager.Instance.CrunOptions.Mp4 = MuxToMp4;
|
||||
CrunchyrollManager.Instance.CrunOptions.SyncTiming = SyncTimings;
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:history="clr-namespace:CRD.Utils.Structs.History"
|
||||
xmlns:structs="clr-namespace:CRD.Utils.Structs"
|
||||
xmlns:local="clr-namespace:CRD.Utils"
|
||||
x:DataType="vm:HistoryPageViewModel"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="CRD.Views.HistoryPageView">
|
||||
|
@ -63,6 +64,46 @@
|
|||
</StackPanel>
|
||||
</ToggleButton>
|
||||
|
||||
<Rectangle Width="1" Height="50" Fill="Gray" Margin="10,0" />
|
||||
|
||||
<StackPanel Margin="10,0">
|
||||
<ToggleButton x:Name="DropdownButtonSonarr" Width="70" Height="70" Background="Transparent"
|
||||
BorderThickness="0" CornerRadius="5"
|
||||
IsVisible="{Binding SonarrAvailable}"
|
||||
IsChecked="{Binding SonarrOptionsOpen}"
|
||||
IsEnabled="{Binding !FetchingData}">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<controls:ImageIcon VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0 1 0 0" Source="../Assets/sonarr.png" Width="30" Height="30" />
|
||||
<TextBlock Text="Sonarr" TextWrapping="Wrap" FontSize="12"></TextBlock>
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
<Popup IsLightDismissEnabled="True"
|
||||
IsOpen="{Binding IsChecked, ElementName=DropdownButtonSonarr, Mode=TwoWay}"
|
||||
Placement="BottomEdgeAlignedRight"
|
||||
PlacementTarget="{Binding ElementName=DropdownButtonSonarr}">
|
||||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||
<StackPanel>
|
||||
<Button Margin="10 5"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsEnabled="{Binding !AddingMissingSonarrSeries}"
|
||||
Content="Add Sonarr Series"
|
||||
Command="{Binding AddMissingSonarrSeriesToHistory}">
|
||||
</Button>
|
||||
<Button Margin="10 5"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Stretch"
|
||||
Content="Download Missing from Sonarr"
|
||||
Command="{Binding DownloadMissingSonarr}">
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Popup>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding ProgressText}"></TextBlock>
|
||||
|
||||
|
||||
<!-- <Button Command="{Binding RefreshAll}" Margin="10" IsEnabled="{Binding !FetchingData}">Refresh All</Button> -->
|
||||
<!-- <Button Command="{Binding AddMissingToQueue}" Margin="10 0" IsEnabled="{Binding !FetchingData}">Add To Queue</Button> -->
|
||||
<!-- <ToggleButton IsChecked="{Binding EditMode}" Margin="10 0" IsEnabled="{Binding !FetchingData}">Edit</ToggleButton> -->
|
||||
|
@ -131,18 +172,38 @@
|
|||
</Popup>
|
||||
</StackPanel>
|
||||
|
||||
<Button Width="70" Height="70" Background="Transparent" BorderThickness="0" Margin="5 0"
|
||||
VerticalAlignment="Center"
|
||||
IsEnabled="False">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<controls:SymbolIcon Symbol="Filter" FontSize="32" />
|
||||
<TextBlock Text="Filter" FontSize="12"></TextBlock>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<StackPanel>
|
||||
<ToggleButton x:Name="DropdownButtonFilter" Width="70" Height="70" Background="Transparent" BorderThickness="0" Margin="5 0"
|
||||
VerticalAlignment="Center"
|
||||
IsEnabled="{Binding !FetchingData}">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<controls:SymbolIcon Symbol="Filter" FontSize="32" />
|
||||
<TextBlock Text="Filter" FontSize="12"></TextBlock>
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
<Popup IsLightDismissEnabled="True"
|
||||
IsOpen="{Binding IsChecked, ElementName=DropdownButtonFilter, Mode=TwoWay}"
|
||||
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>
|
||||
</Border>
|
||||
</Popup>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
</StackPanel>
|
||||
|
||||
|
||||
<ListBox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" ItemsSource="{Binding Items}"
|
||||
<ListBox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" ItemsSource="{Binding FilteredItems}"
|
||||
SelectedItem="{Binding SelectedSeries}"
|
||||
Margin="5" IsVisible="{Binding IsPosterViewSelected}">
|
||||
|
||||
|
@ -235,12 +296,13 @@
|
|||
<Grid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" IsVisible="{Binding IsTableViewSelected}">
|
||||
|
||||
<ScrollViewer>
|
||||
<ItemsControl ItemsSource="{Binding Items}"
|
||||
IsEnabled="{Binding !FetchingData}"
|
||||
Margin="5" IsVisible="{Binding IsTableViewSelected}">
|
||||
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<controls:ItemsRepeater ItemsSource="{Binding FilteredItems}">
|
||||
<controls:ItemsRepeater.Layout>
|
||||
<controls:StackLayout Orientation="Vertical" Spacing="5" />
|
||||
</controls:ItemsRepeater.Layout>
|
||||
<controls:ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type history:HistorySeries}">
|
||||
<Grid>
|
||||
<controls:SettingsExpander
|
||||
x:Name="SettingsExpanderSeries"
|
||||
|
@ -328,8 +390,10 @@
|
|||
IsVisible="{Binding SonarrSeriesId, Converter={StaticResource UiSonarrIdToVisibilityConverter}}"
|
||||
Command="{Binding OpenSonarrPage}">
|
||||
<Grid>
|
||||
<controls:ImageIcon Source="../Assets/sonarr.png"
|
||||
Width="30" Height="30" />
|
||||
<controls:ImageIcon
|
||||
Source="../Assets/sonarr.png"
|
||||
Width="30"
|
||||
Height="30" />
|
||||
</Grid>
|
||||
</Button>
|
||||
|
||||
|
@ -529,7 +593,6 @@
|
|||
Width="25"
|
||||
Height="25" />
|
||||
|
||||
|
||||
</StackPanel>
|
||||
|
||||
|
||||
|
@ -819,11 +882,9 @@
|
|||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</controls:ItemsRepeater.ItemTemplate>
|
||||
</controls:ItemsRepeater>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
||||
|
|
|
@ -149,6 +149,12 @@
|
|||
<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:SettingsExpander>
|
||||
|
||||
|
@ -213,7 +219,11 @@
|
|||
|
||||
<controls:SettingsExpanderItem Content="Download Video for every dub">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding DownloadVideoForEveryDub}"> </CheckBox>
|
||||
<StackPanel>
|
||||
<CheckBox IsChecked="{Binding DownloadVideoForEveryDub}"> </CheckBox>
|
||||
<CheckBox IsVisible="{Binding DownloadVideoForEveryDub}" Content="Keep files separate" IsChecked="{Binding KeepDubsSeparate}"> </CheckBox>
|
||||
</StackPanel>
|
||||
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
|
|
Loading…
Reference in New Issue