mirror of
https://github.com/Crunchy-DL/Crunchy-Downloader.git
synced 2026-04-21 17:01:58 +00:00
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
17 changed files with 545 additions and 222 deletions
BIN
CRD/Assets/crunchy_icon_round.png
(Stored with Git LFS)
BIN
CRD/Assets/crunchy_icon_round.png
(Stored with Git LFS)
Binary file not shown.
|
|
@ -181,6 +181,10 @@ public class CalendarManager{
|
||||||
foreach (var crBrowseEpisode in newEpisodes){
|
foreach (var crBrowseEpisode in newEpisodes){
|
||||||
var targetDate = CrunchyrollManager.Instance.CrunOptions.CalendarFilterByAirDate ? crBrowseEpisode.EpisodeMetadata.EpisodeAirDate : crBrowseEpisode.LastPublic;
|
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 &&
|
if (CrunchyrollManager.Instance.CrunOptions.CalendarHideDubs && crBrowseEpisode.EpisodeMetadata.SeasonTitle != null &&
|
||||||
(crBrowseEpisode.EpisodeMetadata.SeasonTitle.EndsWith("Dub)") || crBrowseEpisode.EpisodeMetadata.AudioLocale != Locale.JaJp)){
|
(crBrowseEpisode.EpisodeMetadata.SeasonTitle.EndsWith("Dub)") || crBrowseEpisode.EpisodeMetadata.AudioLocale != Locale.JaJp)){
|
||||||
continue;
|
continue;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Specialized;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Web;
|
|
||||||
using CRD.Utils;
|
using CRD.Utils;
|
||||||
using CRD.Utils.Structs;
|
using CRD.Utils.Structs;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
@ -13,7 +11,6 @@ using Newtonsoft.Json;
|
||||||
namespace CRD.Downloader.Crunchyroll;
|
namespace CRD.Downloader.Crunchyroll;
|
||||||
|
|
||||||
public class CrAuth{
|
public class CrAuth{
|
||||||
|
|
||||||
private readonly CrunchyrollManager crunInstance = CrunchyrollManager.Instance;
|
private readonly CrunchyrollManager crunInstance = CrunchyrollManager.Instance;
|
||||||
|
|
||||||
public async Task AuthAnonymous(){
|
public async Task AuthAnonymous(){
|
||||||
|
|
@ -45,8 +42,6 @@ public class CrAuth{
|
||||||
PreferredContentSubtitleLanguage = "de-DE"
|
PreferredContentSubtitleLanguage = "de-DE"
|
||||||
};
|
};
|
||||||
|
|
||||||
// CrunchyrollManager.Instance.CmsToken = new CrCmsToken();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void JsonTokenToFileAndVariable(string content){
|
private void JsonTokenToFileAndVariable(string content){
|
||||||
|
|
@ -112,19 +107,21 @@ public class CrAuth{
|
||||||
if (responseSubs.IsOk){
|
if (responseSubs.IsOk){
|
||||||
var subsc = Helpers.Deserialize<Subscription>(responseSubs.ResponseContent, crunInstance.SettingsJsonSerializerSettings);
|
var subsc = Helpers.Deserialize<Subscription>(responseSubs.ResponseContent, crunInstance.SettingsJsonSerializerSettings);
|
||||||
crunInstance.Profile.Subscription = subsc;
|
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 thirdPartySub = subsc.ThirdPartySubscriptionProducts.First();
|
||||||
var expiration = thirdPartySub.InGrace ? thirdPartySub.InGraceExpirationDate : thirdPartySub.ExpirationDate;
|
var expiration = thirdPartySub.InGrace ? thirdPartySub.InGraceExpirationDate : thirdPartySub.ExpirationDate;
|
||||||
var remaining = expiration - DateTime.UtcNow;
|
var remaining = expiration - DateTime.UtcNow;
|
||||||
crunInstance.Profile.HasPremium = remaining > TimeSpan.Zero;
|
crunInstance.Profile.HasPremium = remaining > TimeSpan.Zero;
|
||||||
crunInstance.Profile.Subscription.IsActive = remaining > TimeSpan.Zero;
|
crunInstance.Profile.Subscription.IsActive = remaining > TimeSpan.Zero;
|
||||||
crunInstance.Profile.Subscription.NextRenewalDate = expiration;
|
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 nonRecurringSub = subsc.NonrecurringSubscriptionProducts.First();
|
||||||
var remaining = nonRecurringSub.EndDate - DateTime.UtcNow;
|
var remaining = nonRecurringSub.EndDate - DateTime.UtcNow;
|
||||||
crunInstance.Profile.HasPremium = remaining > TimeSpan.Zero;
|
crunInstance.Profile.HasPremium = remaining > TimeSpan.Zero;
|
||||||
crunInstance.Profile.Subscription.IsActive = remaining > TimeSpan.Zero;
|
crunInstance.Profile.Subscription.IsActive = remaining > TimeSpan.Zero;
|
||||||
crunInstance.Profile.Subscription.NextRenewalDate = nonRecurringSub.EndDate;
|
crunInstance.Profile.Subscription.NextRenewalDate = nonRecurringSub.EndDate;
|
||||||
|
} else if (subsc is{ SubscriptionProducts:{ Count: 0 }, FunimationSubscriptions.Count: > 0 }){
|
||||||
|
crunInstance.Profile.HasPremium = true;
|
||||||
} else{
|
} else{
|
||||||
crunInstance.Profile.HasPremium = subsc.IsActive;
|
crunInstance.Profile.HasPremium = subsc.IsActive;
|
||||||
}
|
}
|
||||||
|
|
@ -132,7 +129,6 @@ public class CrAuth{
|
||||||
crunInstance.Profile.HasPremium = false;
|
crunInstance.Profile.HasPremium = false;
|
||||||
Console.Error.WriteLine("Failed to check premium subscription status");
|
Console.Error.WriteLine("Failed to check premium subscription status");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -171,7 +167,6 @@ public class CrAuth{
|
||||||
await GetProfile();
|
await GetProfile();
|
||||||
}
|
}
|
||||||
|
|
||||||
// await GetCmsToken();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RefreshToken(bool needsToken){
|
public async Task RefreshToken(bool needsToken){
|
||||||
|
|
@ -210,52 +205,6 @@ public class CrAuth{
|
||||||
Console.Error.WriteLine("Refresh Token Auth Failed");
|
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 class CrunchyrollManager{
|
||||||
public CrToken? Token;
|
public CrToken? Token;
|
||||||
// public CrCmsToken? CmsToken;
|
|
||||||
|
|
||||||
public CrProfile Profile = new();
|
public CrProfile Profile = new();
|
||||||
private readonly Lazy<CrDownloadOptions> _optionsLazy;
|
private readonly Lazy<CrDownloadOptions> _optionsLazy;
|
||||||
|
|
@ -44,6 +43,8 @@ public class CrunchyrollManager{
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
public CrBrowseSeriesBase? AllCRSeries;
|
||||||
|
|
||||||
|
|
||||||
public string DefaultLocale = "en-US";
|
public string DefaultLocale = "en-US";
|
||||||
|
|
||||||
|
|
@ -170,11 +171,11 @@ public class CrunchyrollManager{
|
||||||
} else{
|
} else{
|
||||||
HistoryList =[];
|
HistoryList =[];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SonarrClient.Instance.RefreshSonarr();
|
SonarrClient.Instance.RefreshSonarr();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public async Task<bool> DownloadEpisode(CrunchyEpMeta data, CrDownloadOptions options){
|
public async Task<bool> DownloadEpisode(CrunchyEpMeta data, CrDownloadOptions options){
|
||||||
|
|
@ -216,6 +217,33 @@ public class CrunchyrollManager{
|
||||||
|
|
||||||
QueueManager.Instance.Queue.Refresh();
|
QueueManager.Instance.Queue.Refresh();
|
||||||
|
|
||||||
|
if (CrunOptions is{ DlVideoOnce: false, KeepDubsSeperate: true }){
|
||||||
|
var groupByDub = Helpers.GroupByLanguageWithSubtitles(res.Data);
|
||||||
|
|
||||||
|
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,
|
await MuxStreams(res.Data,
|
||||||
new CrunchyMuxOptions{
|
new CrunchyMuxOptions{
|
||||||
FfmpegOptions = options.FfmpegOptions,
|
FfmpegOptions = options.FfmpegOptions,
|
||||||
|
|
@ -235,6 +263,7 @@ public class CrunchyrollManager{
|
||||||
MuxDescription = options.IncludeVideoDescription
|
MuxDescription = options.IncludeVideoDescription
|
||||||
},
|
},
|
||||||
res.FileName);
|
res.FileName);
|
||||||
|
}
|
||||||
|
|
||||||
data.DownloadProgress = new DownloadProgress(){
|
data.DownloadProgress = new DownloadProgress(){
|
||||||
IsDownloading = true,
|
IsDownloading = true,
|
||||||
|
|
|
||||||
|
|
@ -544,6 +544,8 @@ public class History(){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly object _lock = new object();
|
||||||
|
|
||||||
public async Task MatchHistoryEpisodesWithSonarr(bool updateAll, HistorySeries historySeries){
|
public async Task MatchHistoryEpisodesWithSonarr(bool updateAll, HistorySeries historySeries){
|
||||||
if (crunInstance.CrunOptions.SonarrProperties is{ SonarrEnabled: false }){
|
if (crunInstance.CrunOptions.SonarrProperties is{ SonarrEnabled: false }){
|
||||||
return;
|
return;
|
||||||
|
|
@ -574,11 +576,15 @@ public class History(){
|
||||||
historyEpisode.SonarrHasFile = episode.HasFile;
|
historyEpisode.SonarrHasFile = episode.HasFile;
|
||||||
historyEpisode.SonarrAbsolutNumber = episode.AbsoluteEpisodeNumber + "";
|
historyEpisode.SonarrAbsolutNumber = episode.AbsoluteEpisodeNumber + "";
|
||||||
historyEpisode.SonarrSeasonNumber = episode.SeasonNumber + "";
|
historyEpisode.SonarrSeasonNumber = episode.SeasonNumber + "";
|
||||||
|
lock (_lock) {
|
||||||
episodes.Remove(episode);
|
episodes.Remove(episode);
|
||||||
|
}
|
||||||
} else{
|
} else{
|
||||||
|
lock (_lock) {
|
||||||
failedEpisodes.Add(historyEpisode);
|
failedEpisodes.Add(historyEpisode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Parallel.ForEach(failedEpisodes, historyEpisode => {
|
Parallel.ForEach(failedEpisodes, historyEpisode => {
|
||||||
|
|
@ -598,7 +604,9 @@ public class History(){
|
||||||
historyEpisode.SonarrHasFile = episode.HasFile;
|
historyEpisode.SonarrHasFile = episode.HasFile;
|
||||||
historyEpisode.SonarrAbsolutNumber = episode.AbsoluteEpisodeNumber + "";
|
historyEpisode.SonarrAbsolutNumber = episode.AbsoluteEpisodeNumber + "";
|
||||||
historyEpisode.SonarrSeasonNumber = episode.SeasonNumber + "";
|
historyEpisode.SonarrSeasonNumber = episode.SeasonNumber + "";
|
||||||
|
lock (_lock) {
|
||||||
episodes.Remove(episode);
|
episodes.Remove(episode);
|
||||||
|
}
|
||||||
} else{
|
} else{
|
||||||
var episode1 = episodes.Find(ele => {
|
var episode1 = episodes.Find(ele => {
|
||||||
if (ele == null){
|
if (ele == null){
|
||||||
|
|
@ -614,7 +622,9 @@ public class History(){
|
||||||
historyEpisode.SonarrHasFile = episode1.HasFile;
|
historyEpisode.SonarrHasFile = episode1.HasFile;
|
||||||
historyEpisode.SonarrAbsolutNumber = episode1.AbsoluteEpisodeNumber + "";
|
historyEpisode.SonarrAbsolutNumber = episode1.AbsoluteEpisodeNumber + "";
|
||||||
historyEpisode.SonarrSeasonNumber = episode1.SeasonNumber + "";
|
historyEpisode.SonarrSeasonNumber = episode1.SeasonNumber + "";
|
||||||
|
lock (_lock) {
|
||||||
episodes.Remove(episode1);
|
episodes.Remove(episode1);
|
||||||
|
}
|
||||||
} else{
|
} else{
|
||||||
var episode2 = episodes.Find(ele => {
|
var episode2 = episodes.Find(ele => {
|
||||||
if (ele == null){
|
if (ele == null){
|
||||||
|
|
@ -629,7 +639,9 @@ public class History(){
|
||||||
historyEpisode.SonarrHasFile = episode2.HasFile;
|
historyEpisode.SonarrHasFile = episode2.HasFile;
|
||||||
historyEpisode.SonarrAbsolutNumber = episode2.AbsoluteEpisodeNumber + "";
|
historyEpisode.SonarrAbsolutNumber = episode2.AbsoluteEpisodeNumber + "";
|
||||||
historyEpisode.SonarrSeasonNumber = episode2.SeasonNumber + "";
|
historyEpisode.SonarrSeasonNumber = episode2.SeasonNumber + "";
|
||||||
|
lock (_lock) {
|
||||||
episodes.Remove(episode2);
|
episodes.Remove(episode2);
|
||||||
|
}
|
||||||
} else{
|
} else{
|
||||||
Console.Error.WriteLine($"Could not match episode {historyEpisode.EpisodeTitle} to sonarr episode");
|
Console.Error.WriteLine($"Could not match episode {historyEpisode.EpisodeTitle} to sonarr episode");
|
||||||
}
|
}
|
||||||
|
|
@ -701,6 +713,27 @@ public class History(){
|
||||||
return highestSimilarity < 0.8 ? null : closestMatch;
|
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){
|
private double CalculateSimilarity(string source, string target){
|
||||||
int distance = LevenshteinDistance(source, target);
|
int distance = LevenshteinDistance(source, target);
|
||||||
return 1.0 - (double)distance / Math.Max(source.Length, target.Length);
|
return 1.0 - (double)distance / Math.Max(source.Length, target.Length);
|
||||||
|
|
|
||||||
|
|
@ -191,6 +191,17 @@ public enum SortingType{
|
||||||
HistorySeriesAddDate,
|
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{
|
public enum SonarrCoverType{
|
||||||
Banner,
|
Banner,
|
||||||
FanArt,
|
FanArt,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using System.Runtime.Serialization;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Avalonia.Media.Imaging;
|
using Avalonia.Media.Imaging;
|
||||||
|
using CRD.Utils.Structs;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace CRD.Utils;
|
namespace CRD.Utils;
|
||||||
|
|
@ -157,7 +158,7 @@ public class Helpers{
|
||||||
var title = chapters[i].Title;
|
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
|
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
|
endTime = startTime + 10000; // Correct end time if it is before start time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -226,7 +227,7 @@ public class Helpers{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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{
|
try{
|
||||||
using (var process = new Process()){
|
using (var process = new Process()){
|
||||||
process.StartInfo.WorkingDirectory = workingDir;
|
process.StartInfo.WorkingDirectory = workingDir;
|
||||||
|
|
@ -364,4 +365,17 @@ public class Helpers{
|
||||||
return null;
|
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}\"");
|
args.Add($"-i \"{sub.value.File}\"");
|
||||||
metaData.Add($"-map {index}:s");
|
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){
|
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{
|
} else{
|
||||||
args.Add($"-disposition:s:{sub.i} 0");
|
metaData.Add($"-disposition:s:{sub.i} 0");
|
||||||
}
|
}
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,9 @@ public class CrDownloadOptions{
|
||||||
[YamlMember(Alias = "dl_video_once", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "dl_video_once", ApplyNamingConventions = false)]
|
||||||
public bool DlVideoOnce{ get; set; }
|
public bool DlVideoOnce{ get; set; }
|
||||||
|
|
||||||
|
[YamlMember(Alias = "keep_dubs_seperate", ApplyNamingConventions = false)]
|
||||||
|
public bool KeepDubsSeperate{ get; set; }
|
||||||
|
|
||||||
[YamlIgnore]
|
[YamlIgnore]
|
||||||
public bool? Skipmux{ get; set; }
|
public bool? Skipmux{ get; set; }
|
||||||
|
|
||||||
|
|
@ -159,6 +162,9 @@ public class CrDownloadOptions{
|
||||||
[YamlMember(Alias = "history_add_specials", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "history_add_specials", ApplyNamingConventions = false)]
|
||||||
public bool HistoryAddSpecials{ get; set; }
|
public bool HistoryAddSpecials{ get; set; }
|
||||||
|
|
||||||
|
[YamlMember(Alias = "history_count_sonarr", ApplyNamingConventions = false)]
|
||||||
|
public bool HistoryCountSonarr{ get; set; }
|
||||||
|
|
||||||
[YamlMember(Alias = "sonarr_properties", ApplyNamingConventions = false)]
|
[YamlMember(Alias = "sonarr_properties", ApplyNamingConventions = false)]
|
||||||
public SonarrProperties? SonarrProperties{ get; set; }
|
public SonarrProperties? SonarrProperties{ get; set; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,9 @@ public class Subscription{
|
||||||
|
|
||||||
[JsonProperty("nonrecurring_subscription_products")]
|
[JsonProperty("nonrecurring_subscription_products")]
|
||||||
public List<NonRecurringSubscriptionProduct>? NonrecurringSubscriptionProducts{ get; set; }
|
public List<NonRecurringSubscriptionProduct>? NonrecurringSubscriptionProducts{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("funimation_subscriptions")]
|
||||||
|
public List<object>? FunimationSubscriptions{ get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class NonRecurringSubscriptionProduct{
|
public class NonRecurringSubscriptionProduct{
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,9 @@ public class HistorySeries : INotifyPropertyChanged{
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public Bitmap? ThumbnailImage{ get; set; }
|
public Bitmap? ThumbnailImage{ get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public bool IsImageLoaded{ get; private set; } = false;
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public bool FetchingData{ get; set; }
|
public bool FetchingData{ get; set; }
|
||||||
|
|
||||||
|
|
@ -168,17 +171,19 @@ public class HistorySeries : INotifyPropertyChanged{
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public async Task LoadImage(){
|
public async Task LoadImage(){
|
||||||
|
if (IsImageLoaded || string.IsNullOrEmpty(ThumbnailImageUrl))
|
||||||
|
return;
|
||||||
|
|
||||||
try{
|
try{
|
||||||
using (var client = new HttpClient()){
|
using var client = new HttpClient();
|
||||||
var response = await client.GetAsync(ThumbnailImageUrl);
|
var response = await client.GetAsync(ThumbnailImageUrl);
|
||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
using (var stream = await response.Content.ReadAsStreamAsync()){
|
using var stream = await response.Content.ReadAsStreamAsync();
|
||||||
ThumbnailImage = new Bitmap(stream);
|
ThumbnailImage = new Bitmap(stream);
|
||||||
|
IsImageLoaded = true;
|
||||||
|
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ThumbnailImage)));
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ThumbnailImage)));
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception ex){
|
} catch (Exception ex){
|
||||||
// Handle exceptions
|
|
||||||
Console.Error.WriteLine("Failed to load image: " + ex.Message);
|
Console.Error.WriteLine("Failed to load image: " + ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -187,7 +192,22 @@ public class HistorySeries : INotifyPropertyChanged{
|
||||||
int count = 0;
|
int count = 0;
|
||||||
bool foundWatched = false;
|
bool foundWatched = false;
|
||||||
var historyAddSpecials = CrunchyrollManager.Instance.CrunOptions.HistoryAddSpecials;
|
var historyAddSpecials = CrunchyrollManager.Instance.CrunOptions.HistoryAddSpecials;
|
||||||
|
var sonarrEnabled = CrunchyrollManager.Instance.CrunOptions.SonarrProperties != null && CrunchyrollManager.Instance.CrunOptions.SonarrProperties.SonarrEnabled;
|
||||||
|
|
||||||
|
if (sonarrEnabled && CrunchyrollManager.Instance.CrunOptions.HistoryCountSonarr && !string.IsNullOrEmpty(SonarrSeriesId)){
|
||||||
|
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 (!string.IsNullOrEmpty(episode.SonarrEpisodeId) && !episode.SonarrHasFile){
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else{
|
||||||
for (int i = Seasons.Count - 1; i >= 0; i--){
|
for (int i = Seasons.Count - 1; i >= 0; i--){
|
||||||
var season = Seasons[i];
|
var season = Seasons[i];
|
||||||
|
|
||||||
|
|
@ -230,6 +250,8 @@ public class HistorySeries : INotifyPropertyChanged{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
NewEpisodes = count;
|
NewEpisodes = count;
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NewEpisodes)));
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NewEpisodes)));
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ public partial class AccountPageViewModel : ViewModelBase{
|
||||||
private static DispatcherTimer? _timer;
|
private static DispatcherTimer? _timer;
|
||||||
private DateTime _targetTime;
|
private DateTime _targetTime;
|
||||||
private bool IsCancelled = false;
|
private bool IsCancelled = false;
|
||||||
|
private bool UnknownEndDate = false;
|
||||||
|
|
||||||
public AccountPageViewModel(){
|
public AccountPageViewModel(){
|
||||||
UpdatetProfile();
|
UpdatetProfile();
|
||||||
|
|
@ -68,6 +69,9 @@ public partial class AccountPageViewModel : ViewModelBase{
|
||||||
}
|
}
|
||||||
}else if(CrunchyrollManager.Instance.Profile.Subscription?.NonrecurringSubscriptionProducts.Count >= 1){
|
}else if(CrunchyrollManager.Instance.Profile.Subscription?.NonrecurringSubscriptionProducts.Count >= 1){
|
||||||
IsCancelled = true;
|
IsCancelled = true;
|
||||||
|
}else if(CrunchyrollManager.Instance.Profile.Subscription?.FunimationSubscriptions.Count >= 1){
|
||||||
|
IsCancelled = true;
|
||||||
|
UnknownEndDate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CrunchyrollManager.Instance.Profile.Subscription?.NextRenewalDate != null){
|
if (CrunchyrollManager.Instance.Profile.Subscription?.NextRenewalDate != null){
|
||||||
|
|
@ -92,6 +96,10 @@ public partial class AccountPageViewModel : ViewModelBase{
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (UnknownEndDate){
|
||||||
|
RemainingTime = "Unknown Subscription end date";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
@ -14,85 +16,116 @@ using CommunityToolkit.Mvvm.Input;
|
||||||
using CRD.Downloader;
|
using CRD.Downloader;
|
||||||
using CRD.Downloader.Crunchyroll;
|
using CRD.Downloader.Crunchyroll;
|
||||||
using CRD.Utils;
|
using CRD.Utils;
|
||||||
|
using CRD.Utils.Sonarr;
|
||||||
using CRD.Utils.Structs;
|
using CRD.Utils.Structs;
|
||||||
using CRD.Utils.Structs.History;
|
using CRD.Utils.Structs.History;
|
||||||
using CRD.Views;
|
using CRD.Views;
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
|
using HarfBuzzSharp;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace CRD.ViewModels;
|
namespace CRD.ViewModels;
|
||||||
|
|
||||||
public partial class HistoryPageViewModel : ViewModelBase{
|
public partial class HistoryPageViewModel : ViewModelBase{
|
||||||
public ObservableCollection<HistorySeries> Items{ get; }
|
public ObservableCollection<HistorySeries> Items{ get; }
|
||||||
|
public ObservableCollection<HistorySeries> FilteredItems{ get; }
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private static bool _fetchingData;
|
private static bool _fetchingData;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public HistorySeries _selectedSeries;
|
private HistorySeries _selectedSeries;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public static bool _editMode;
|
private static bool _editMode;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public double _scaleValue;
|
private double _scaleValue;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public ComboBoxItem _selectedView;
|
private ComboBoxItem? _selectedView;
|
||||||
|
|
||||||
public ObservableCollection<ComboBoxItem> ViewsList{ get; } =[];
|
public ObservableCollection<ComboBoxItem> ViewsList{ get; } =[];
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public SortingListElement _selectedSorting;
|
private SortingListElement? _selectedSorting;
|
||||||
|
|
||||||
public ObservableCollection<SortingListElement> SortingList{ get; } =[];
|
public ObservableCollection<SortingListElement> SortingList{ get; } =[];
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public double _posterWidth;
|
private FilterListElement? _selectedFilter;
|
||||||
|
|
||||||
|
public ObservableCollection<FilterListElement> FilterList{ get; } =[];
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public double _posterHeight;
|
private double _posterWidth;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public double _posterImageWidth;
|
private double _posterHeight;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public double _posterImageHeight;
|
private double _posterImageWidth;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public double _posterTextSize;
|
private double _posterImageHeight;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public Thickness _cornerMargin;
|
private double _posterTextSize;
|
||||||
|
|
||||||
private HistoryViewType currentViewType = HistoryViewType.Posters;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public bool _isPosterViewSelected = false;
|
private Thickness _cornerMargin;
|
||||||
|
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public bool _isTableViewSelected = false;
|
private bool _isPosterViewSelected = false;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public static bool _viewSelectionOpen;
|
private bool _isTableViewSelected = false;
|
||||||
|
|
||||||
[ObservableProperty]
|
[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 IStorageProvider _storageProvider;
|
||||||
|
|
||||||
private SortingType currentSortingType = SortingType.NextAirDate;
|
private HistoryViewType currentViewType;
|
||||||
|
|
||||||
|
private SortingType currentSortingType;
|
||||||
|
|
||||||
|
private FilterType currentFilterType;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public static bool _sortDir = false;
|
private static bool _sortDir = false;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private static bool _sonarrAvailable;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private static string _progressText;
|
||||||
|
|
||||||
public HistoryPageViewModel(){
|
public HistoryPageViewModel(){
|
||||||
|
if (CrunchyrollManager.Instance.CrunOptions.SonarrProperties != null){
|
||||||
|
SonarrAvailable = CrunchyrollManager.Instance.CrunOptions.SonarrProperties.SonarrEnabled;
|
||||||
|
} else{
|
||||||
|
SonarrAvailable = false;
|
||||||
|
}
|
||||||
|
|
||||||
Items = CrunchyrollManager.Instance.HistoryList;
|
Items = CrunchyrollManager.Instance.HistoryList;
|
||||||
|
FilteredItems = new ObservableCollection<HistorySeries>();
|
||||||
|
|
||||||
HistoryPageProperties? properties = CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties;
|
HistoryPageProperties? properties = CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties;
|
||||||
|
|
||||||
currentViewType = properties?.SelectedView ?? HistoryViewType.Posters;
|
currentViewType = properties?.SelectedView ?? HistoryViewType.Posters;
|
||||||
currentSortingType = properties?.SelectedSorting ?? SortingType.SeriesTitle;
|
currentSortingType = properties?.SelectedSorting ?? SortingType.SeriesTitle;
|
||||||
|
currentFilterType = properties?.SelectedFilter ?? FilterType.All;
|
||||||
ScaleValue = properties?.ScaleValue ?? 0.73;
|
ScaleValue = properties?.ScaleValue ?? 0.73;
|
||||||
SortDir = properties?.Ascending ?? false;
|
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;
|
IsPosterViewSelected = currentViewType == HistoryViewType.Posters;
|
||||||
IsTableViewSelected = currentViewType == HistoryViewType.Table;
|
IsTableViewSelected = currentViewType == HistoryViewType.Table;
|
||||||
|
|
||||||
|
|
@ -172,6 +218,10 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
||||||
currentSortingType = newValue.SelectedSorting;
|
currentSortingType = newValue.SelectedSorting;
|
||||||
if (CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties != null) CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties.SelectedSorting = currentSortingType;
|
if (CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties != null) CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties.SelectedSorting = currentSortingType;
|
||||||
CrunchyrollManager.Instance.History.SortItems();
|
CrunchyrollManager.Instance.History.SortItems();
|
||||||
|
if (SelectedFilter != null){
|
||||||
|
OnSelectedFilterChanged(SelectedFilter);
|
||||||
|
}
|
||||||
|
|
||||||
} else{
|
} else{
|
||||||
Console.Error.WriteLine("Invalid viewtype selected");
|
Console.Error.WriteLine("Invalid viewtype selected");
|
||||||
}
|
}
|
||||||
|
|
@ -180,17 +230,46 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
||||||
UpdateSettings();
|
UpdateSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryParseEnum<T>(string value, out T result) where T : struct, Enum{
|
|
||||||
foreach (var field in typeof(T).GetFields()){
|
partial void OnSelectedFilterChanged(FilterListElement? value){
|
||||||
var attribute = field.GetCustomAttribute<EnumMemberAttribute>();
|
|
||||||
if (attribute != null && attribute.Value == value){
|
if (value == null){
|
||||||
result = (T)field.GetValue(null);
|
return;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result = default;
|
currentFilterType = value.SelectedType;
|
||||||
return false;
|
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){
|
if (objectToRemove != null){
|
||||||
CrunchyrollManager.Instance.HistoryList.Remove(objectToRemove);
|
CrunchyrollManager.Instance.HistoryList.Remove(objectToRemove);
|
||||||
Items.Remove(objectToRemove);
|
Items.Remove(objectToRemove);
|
||||||
|
FilteredItems.Remove(objectToRemove);
|
||||||
CfgManager.UpdateHistoryFile();
|
CfgManager.UpdateHistoryFile();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -245,18 +325,18 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
public async void RefreshAll(){
|
public async Task RefreshAll(){
|
||||||
FetchingData = true;
|
FetchingData = true;
|
||||||
RaisePropertyChanged(nameof(FetchingData));
|
RaisePropertyChanged(nameof(FetchingData));
|
||||||
for (int i = 0; i < Items.Count; i++){
|
foreach (var item in FilteredItems){
|
||||||
Items[i].SetFetchingData();
|
item.SetFetchingData();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < Items.Count; i++){
|
for (int i = 0; i < FilteredItems.Count; i++){
|
||||||
FetchingData = true;
|
FetchingData = true;
|
||||||
RaisePropertyChanged(nameof(FetchingData));
|
RaisePropertyChanged(nameof(FetchingData));
|
||||||
await Items[i].FetchData("");
|
await FilteredItems[i].FetchData("");
|
||||||
Items[i].UpdateNewEpisodes();
|
FilteredItems[i].UpdateNewEpisodes();
|
||||||
}
|
}
|
||||||
|
|
||||||
FetchingData = false;
|
FetchingData = false;
|
||||||
|
|
@ -266,8 +346,76 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
public async void AddMissingToQueue(){
|
public async void AddMissingToQueue(){
|
||||||
for (int i = 0; i < Items.Count; i++){
|
var tasks = FilteredItems
|
||||||
await Items[i].AddNewMissingToDownloads();
|
.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;
|
season.SeasonDownloadPath = selectedFolder.Path.LocalPath;
|
||||||
CfgManager.UpdateHistoryFile();
|
CfgManager.UpdateHistoryFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -322,23 +469,28 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
public async Task DownloadSeasonAll(HistorySeason season){
|
public async Task DownloadSeasonAll(HistorySeason season){
|
||||||
foreach (var historyEpisode in season.EpisodesList){
|
var downloadTasks = season.EpisodesList
|
||||||
await historyEpisode.DownloadEpisode();
|
.Select(episode => episode.DownloadEpisode());
|
||||||
}
|
|
||||||
|
await Task.WhenAll(downloadTasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
public async Task DownloadSeasonMissing(HistorySeason season){
|
public async Task DownloadSeasonMissing(HistorySeason season){
|
||||||
foreach (var historyEpisode in season.EpisodesList.Where(historyEpisode => !historyEpisode.WasDownloaded)){
|
var downloadTasks = season.EpisodesList
|
||||||
await historyEpisode.DownloadEpisode();
|
.Where(episode => !episode.WasDownloaded)
|
||||||
}
|
.Select(episode => episode.DownloadEpisode());
|
||||||
|
|
||||||
|
await Task.WhenAll(downloadTasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
public async Task DownloadSeasonMissingSonarr(HistorySeason season){
|
public async Task DownloadSeasonMissingSonarr(HistorySeason season){
|
||||||
foreach (var historyEpisode in season.EpisodesList.Where(historyEpisode => !historyEpisode.SonarrHasFile)){
|
var downloadTasks = season.EpisodesList
|
||||||
await historyEpisode.DownloadEpisode();
|
.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 class HistoryPageProperties(){
|
||||||
public SortingType? SelectedSorting{ get; set; }
|
public SortingType? SelectedSorting{ get; set; }
|
||||||
public HistoryViewType SelectedView{ get; set; }
|
public HistoryViewType SelectedView{ get; set; }
|
||||||
|
public FilterType SelectedFilter{ get; set; }
|
||||||
public double? ScaleValue{ get; set; }
|
public double? ScaleValue{ get; set; }
|
||||||
|
|
||||||
public bool Ascending{ get; set; }
|
public bool Ascending{ get; set; }
|
||||||
|
|
@ -359,3 +512,8 @@ public class SortingListElement(){
|
||||||
public SortingType SelectedSorting{ get; set; }
|
public SortingType SelectedSorting{ get; set; }
|
||||||
public string? SortingTitle{ get; set; }
|
public string? SortingTitle{ get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class FilterListElement(){
|
||||||
|
public FilterType SelectedType{ get; set; }
|
||||||
|
public string? FilterTitle{ get; set; }
|
||||||
|
}
|
||||||
|
|
@ -82,23 +82,28 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
public async Task DownloadSeasonAll(HistorySeason season){
|
public async Task DownloadSeasonAll(HistorySeason season){
|
||||||
foreach (var historyEpisode in season.EpisodesList){
|
var downloadTasks = season.EpisodesList
|
||||||
await historyEpisode.DownloadEpisode();
|
.Select(episode => episode.DownloadEpisode());
|
||||||
}
|
|
||||||
|
await Task.WhenAll(downloadTasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
public async Task DownloadSeasonMissing(HistorySeason season){
|
public async Task DownloadSeasonMissing(HistorySeason season){
|
||||||
foreach (var historyEpisode in season.EpisodesList.Where(historyEpisode => !historyEpisode.WasDownloaded)){
|
var downloadTasks = season.EpisodesList
|
||||||
await historyEpisode.DownloadEpisode();
|
.Where(episode => !episode.WasDownloaded)
|
||||||
}
|
.Select(episode => episode.DownloadEpisode());
|
||||||
|
|
||||||
|
await Task.WhenAll(downloadTasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
public async Task DownloadSeasonMissingSonarr(HistorySeason season){
|
public async Task DownloadSeasonMissingSonarr(HistorySeason season){
|
||||||
foreach (var historyEpisode in season.EpisodesList.Where(historyEpisode => !historyEpisode.SonarrHasFile)){
|
var downloadTasks = season.EpisodesList
|
||||||
await historyEpisode.DownloadEpisode();
|
.Where(episode => !episode.SonarrHasFile)
|
||||||
}
|
.Select(episode => episode.DownloadEpisode());
|
||||||
|
|
||||||
|
await Task.WhenAll(downloadTasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,9 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _downloadVideoForEveryDub;
|
private bool _downloadVideoForEveryDub;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _keepDubsSeparate;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _skipSubMux;
|
private bool _skipSubMux;
|
||||||
|
|
||||||
|
|
@ -78,6 +81,9 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _historyAddSpecials;
|
private bool _historyAddSpecials;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _historyCountSonarr;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private double? _leadingNumbers;
|
private double? _leadingNumbers;
|
||||||
|
|
||||||
|
|
@ -388,6 +394,7 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
||||||
DefaultSubForcedDisplay = options.DefaultSubForcedDisplay;
|
DefaultSubForcedDisplay = options.DefaultSubForcedDisplay;
|
||||||
DefaultSubSigns = options.DefaultSubSigns;
|
DefaultSubSigns = options.DefaultSubSigns;
|
||||||
HistoryAddSpecials = options.HistoryAddSpecials;
|
HistoryAddSpecials = options.HistoryAddSpecials;
|
||||||
|
HistoryCountSonarr = options.HistoryCountSonarr;
|
||||||
DownloadSpeed = options.DownloadSpeedLimit;
|
DownloadSpeed = options.DownloadSpeedLimit;
|
||||||
IncludeEpisodeDescription = options.IncludeVideoDescription;
|
IncludeEpisodeDescription = options.IncludeVideoDescription;
|
||||||
FileTitle = options.VideoTitle ?? "";
|
FileTitle = options.VideoTitle ?? "";
|
||||||
|
|
@ -395,6 +402,7 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
||||||
DownloadVideo = !options.Novids;
|
DownloadVideo = !options.Novids;
|
||||||
DownloadAudio = !options.Noaudio;
|
DownloadAudio = !options.Noaudio;
|
||||||
DownloadVideoForEveryDub = !options.DlVideoOnce;
|
DownloadVideoForEveryDub = !options.DlVideoOnce;
|
||||||
|
KeepDubsSeparate = options.KeepDubsSeperate;
|
||||||
DownloadChapters = options.Chapters;
|
DownloadChapters = options.Chapters;
|
||||||
MuxToMp4 = options.Mp4;
|
MuxToMp4 = options.Mp4;
|
||||||
SyncTimings = options.SyncTiming;
|
SyncTimings = options.SyncTiming;
|
||||||
|
|
@ -457,10 +465,12 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
||||||
CrunchyrollManager.Instance.CrunOptions.DefaultSubForcedDisplay = DefaultSubForcedDisplay;
|
CrunchyrollManager.Instance.CrunOptions.DefaultSubForcedDisplay = DefaultSubForcedDisplay;
|
||||||
CrunchyrollManager.Instance.CrunOptions.IncludeVideoDescription = IncludeEpisodeDescription;
|
CrunchyrollManager.Instance.CrunOptions.IncludeVideoDescription = IncludeEpisodeDescription;
|
||||||
CrunchyrollManager.Instance.CrunOptions.HistoryAddSpecials = HistoryAddSpecials;
|
CrunchyrollManager.Instance.CrunOptions.HistoryAddSpecials = HistoryAddSpecials;
|
||||||
|
CrunchyrollManager.Instance.CrunOptions.HistoryCountSonarr = HistoryCountSonarr;
|
||||||
CrunchyrollManager.Instance.CrunOptions.VideoTitle = FileTitle;
|
CrunchyrollManager.Instance.CrunOptions.VideoTitle = FileTitle;
|
||||||
CrunchyrollManager.Instance.CrunOptions.Novids = !DownloadVideo;
|
CrunchyrollManager.Instance.CrunOptions.Novids = !DownloadVideo;
|
||||||
CrunchyrollManager.Instance.CrunOptions.Noaudio = !DownloadAudio;
|
CrunchyrollManager.Instance.CrunOptions.Noaudio = !DownloadAudio;
|
||||||
CrunchyrollManager.Instance.CrunOptions.DlVideoOnce = !DownloadVideoForEveryDub;
|
CrunchyrollManager.Instance.CrunOptions.DlVideoOnce = !DownloadVideoForEveryDub;
|
||||||
|
CrunchyrollManager.Instance.CrunOptions.KeepDubsSeperate = KeepDubsSeparate;
|
||||||
CrunchyrollManager.Instance.CrunOptions.Chapters = DownloadChapters;
|
CrunchyrollManager.Instance.CrunOptions.Chapters = DownloadChapters;
|
||||||
CrunchyrollManager.Instance.CrunOptions.Mp4 = MuxToMp4;
|
CrunchyrollManager.Instance.CrunOptions.Mp4 = MuxToMp4;
|
||||||
CrunchyrollManager.Instance.CrunOptions.SyncTiming = SyncTimings;
|
CrunchyrollManager.Instance.CrunOptions.SyncTiming = SyncTimings;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
xmlns:history="clr-namespace:CRD.Utils.Structs.History"
|
xmlns:history="clr-namespace:CRD.Utils.Structs.History"
|
||||||
xmlns:structs="clr-namespace:CRD.Utils.Structs"
|
xmlns:structs="clr-namespace:CRD.Utils.Structs"
|
||||||
|
xmlns:local="clr-namespace:CRD.Utils"
|
||||||
x:DataType="vm:HistoryPageViewModel"
|
x:DataType="vm:HistoryPageViewModel"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="CRD.Views.HistoryPageView">
|
x:Class="CRD.Views.HistoryPageView">
|
||||||
|
|
@ -63,6 +64,46 @@
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ToggleButton>
|
</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 RefreshAll}" Margin="10" IsEnabled="{Binding !FetchingData}">Refresh All</Button> -->
|
||||||
<!-- <Button Command="{Binding AddMissingToQueue}" Margin="10 0" IsEnabled="{Binding !FetchingData}">Add To Queue</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> -->
|
<!-- <ToggleButton IsChecked="{Binding EditMode}" Margin="10 0" IsEnabled="{Binding !FetchingData}">Edit</ToggleButton> -->
|
||||||
|
|
@ -131,18 +172,38 @@
|
||||||
</Popup>
|
</Popup>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<Button Width="70" Height="70" Background="Transparent" BorderThickness="0" Margin="5 0"
|
<StackPanel>
|
||||||
|
<ToggleButton x:Name="DropdownButtonFilter" Width="70" Height="70" Background="Transparent" BorderThickness="0" Margin="5 0"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
IsEnabled="False">
|
IsEnabled="{Binding !FetchingData}">
|
||||||
<StackPanel Orientation="Vertical">
|
<StackPanel Orientation="Vertical">
|
||||||
<controls:SymbolIcon Symbol="Filter" FontSize="32" />
|
<controls:SymbolIcon Symbol="Filter" FontSize="32" />
|
||||||
<TextBlock Text="Filter" FontSize="12"></TextBlock>
|
<TextBlock Text="Filter" FontSize="12"></TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</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}"
|
</StackPanel>
|
||||||
|
|
||||||
|
|
||||||
|
<ListBox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" ItemsSource="{Binding FilteredItems}"
|
||||||
SelectedItem="{Binding SelectedSeries}"
|
SelectedItem="{Binding SelectedSeries}"
|
||||||
Margin="5" IsVisible="{Binding IsPosterViewSelected}">
|
Margin="5" IsVisible="{Binding IsPosterViewSelected}">
|
||||||
|
|
||||||
|
|
@ -235,12 +296,13 @@
|
||||||
<Grid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" IsVisible="{Binding IsTableViewSelected}">
|
<Grid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" IsVisible="{Binding IsTableViewSelected}">
|
||||||
|
|
||||||
<ScrollViewer>
|
<ScrollViewer>
|
||||||
<ItemsControl ItemsSource="{Binding Items}"
|
|
||||||
IsEnabled="{Binding !FetchingData}"
|
|
||||||
Margin="5" IsVisible="{Binding IsTableViewSelected}">
|
|
||||||
|
|
||||||
<ItemsControl.ItemTemplate>
|
<controls:ItemsRepeater ItemsSource="{Binding FilteredItems}">
|
||||||
<DataTemplate>
|
<controls:ItemsRepeater.Layout>
|
||||||
|
<controls:StackLayout Orientation="Vertical" Spacing="5" />
|
||||||
|
</controls:ItemsRepeater.Layout>
|
||||||
|
<controls:ItemsRepeater.ItemTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type history:HistorySeries}">
|
||||||
<Grid>
|
<Grid>
|
||||||
<controls:SettingsExpander
|
<controls:SettingsExpander
|
||||||
x:Name="SettingsExpanderSeries"
|
x:Name="SettingsExpanderSeries"
|
||||||
|
|
@ -328,8 +390,10 @@
|
||||||
IsVisible="{Binding SonarrSeriesId, Converter={StaticResource UiSonarrIdToVisibilityConverter}}"
|
IsVisible="{Binding SonarrSeriesId, Converter={StaticResource UiSonarrIdToVisibilityConverter}}"
|
||||||
Command="{Binding OpenSonarrPage}">
|
Command="{Binding OpenSonarrPage}">
|
||||||
<Grid>
|
<Grid>
|
||||||
<controls:ImageIcon Source="../Assets/sonarr.png"
|
<controls:ImageIcon
|
||||||
Width="30" Height="30" />
|
Source="../Assets/sonarr.png"
|
||||||
|
Width="30"
|
||||||
|
Height="30" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
|
@ -529,7 +593,6 @@
|
||||||
Width="25"
|
Width="25"
|
||||||
Height="25" />
|
Height="25" />
|
||||||
|
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -819,11 +882,9 @@
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ItemsControl.ItemTemplate>
|
</controls:ItemsRepeater.ItemTemplate>
|
||||||
</ItemsControl>
|
</controls:ItemsRepeater>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,12 @@
|
||||||
</controls:SettingsExpanderItem.Footer>
|
</controls:SettingsExpanderItem.Footer>
|
||||||
</controls:SettingsExpanderItem>
|
</controls:SettingsExpanderItem>
|
||||||
|
|
||||||
|
<controls:SettingsExpanderItem Content="History Missing/New Count from Sonarr" Description="The missing count (number in the orange corner) will count the episodes missing from sonarr">
|
||||||
|
<controls:SettingsExpanderItem.Footer>
|
||||||
|
<CheckBox IsChecked="{Binding HistoryCountSonarr}"> </CheckBox>
|
||||||
|
</controls:SettingsExpanderItem.Footer>
|
||||||
|
</controls:SettingsExpanderItem>
|
||||||
|
|
||||||
</controls:SettingsExpander>
|
</controls:SettingsExpander>
|
||||||
|
|
||||||
<controls:SettingsExpander Header="Download Settings"
|
<controls:SettingsExpander Header="Download Settings"
|
||||||
|
|
@ -213,7 +219,11 @@
|
||||||
|
|
||||||
<controls:SettingsExpanderItem Content="Download Video for every dub">
|
<controls:SettingsExpanderItem Content="Download Video for every dub">
|
||||||
<controls:SettingsExpanderItem.Footer>
|
<controls:SettingsExpanderItem.Footer>
|
||||||
|
<StackPanel>
|
||||||
<CheckBox IsChecked="{Binding DownloadVideoForEveryDub}"> </CheckBox>
|
<CheckBox IsChecked="{Binding DownloadVideoForEveryDub}"> </CheckBox>
|
||||||
|
<CheckBox IsVisible="{Binding DownloadVideoForEveryDub}" Content="Keep files separate" IsChecked="{Binding KeepDubsSeparate}"> </CheckBox>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
</controls:SettingsExpanderItem.Footer>
|
</controls:SettingsExpanderItem.Footer>
|
||||||
</controls:SettingsExpanderItem>
|
</controls:SettingsExpanderItem>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue