mirror of
https://github.com/Crunchy-DL/Crunchy-Downloader.git
synced 2026-04-21 17:01:58 +00:00
Add - Added --historyRefreshActive parameter
Add - Added **hardware acceleration options** for sync timings Add - Added an **icon for episodes removed from Crunchyroll** or moved to a different season Add - Added **highlighting for selected dubs/subs** in history Add - Added **highlighting of episode titles** when all selected dubs/subs are available Add - Added option to **set history series to inactive** Add - Added **filter for Active and Inactive** series in history Add - Added **download retry options** to settings, making retry variables editable Chg - Changed **Sonarr missing filter** to respect the "Skip Unmonitored" setting Chg - Changed to **show an error** if an episode wasn't added to the queue Chg - Changed to show **dates for episodes** in the history Chg - Changed **sync timings algorithm** to improve overall performance Chg - Changed **sync timings algorithm** to more accurately synchronize dubs Chg - Changed **series/season refresh messages** to indicate failure or success more clearly Chg - Changed **error display frequency** to reduce repeated popups for the same message Fix - Fixed **movie detection** to properly verify if the ID corresponds to a movie Fix - Fixed **long option hiding remove buttons** for FFmpeg and MKVMerge additional options Fix - Fixed **toast message timer** not updating correctly Fix - Fixed **path length error** Fix - Fixed a **rare crash when navigating history**
This commit is contained in:
parent
709647bb99
commit
4f6d0f2257
35 changed files with 1178 additions and 237 deletions
|
|
@ -24,6 +24,8 @@ public partial class App : Application{
|
||||||
desktop.MainWindow = new MainWindow{
|
desktop.MainWindow = new MainWindow{
|
||||||
DataContext = new MainWindowViewModel(manager),
|
DataContext = new MainWindowViewModel(manager),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
desktop.MainWindow.Opened += (_, _) => { manager.SetBackgroundImage(); };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -114,16 +114,16 @@ public class CrAuth{
|
||||||
JsonTokenToFileAndVariable(response.ResponseContent, uuid);
|
JsonTokenToFileAndVariable(response.ResponseContent, uuid);
|
||||||
} else{
|
} else{
|
||||||
if (response.ResponseContent.Contains("invalid_credentials")){
|
if (response.ResponseContent.Contains("invalid_credentials")){
|
||||||
MessageBus.Current.SendMessage(new ToastMessage($"Failed to login - because of invalid login credentials", ToastType.Error, 10));
|
MessageBus.Current.SendMessage(new ToastMessage($"Failed to login - because of invalid login credentials", ToastType.Error, 5));
|
||||||
} else if (response.ResponseContent.Contains("<title>Just a moment...</title>") ||
|
} else if (response.ResponseContent.Contains("<title>Just a moment...</title>") ||
|
||||||
response.ResponseContent.Contains("<title>Access denied</title>") ||
|
response.ResponseContent.Contains("<title>Access denied</title>") ||
|
||||||
response.ResponseContent.Contains("<title>Attention Required! | Cloudflare</title>") ||
|
response.ResponseContent.Contains("<title>Attention Required! | Cloudflare</title>") ||
|
||||||
response.ResponseContent.Trim().Equals("error code: 1020") ||
|
response.ResponseContent.Trim().Equals("error code: 1020") ||
|
||||||
response.ResponseContent.IndexOf("<title>DDOS-GUARD</title>", StringComparison.OrdinalIgnoreCase) > -1){
|
response.ResponseContent.IndexOf("<title>DDOS-GUARD</title>", StringComparison.OrdinalIgnoreCase) > -1){
|
||||||
MessageBus.Current.SendMessage(new ToastMessage($"Failed to login - Cloudflare error try to change to BetaAPI in settings", ToastType.Error, 10));
|
MessageBus.Current.SendMessage(new ToastMessage($"Failed to login - Cloudflare error try to change to BetaAPI in settings", ToastType.Error, 5));
|
||||||
} else{
|
} else{
|
||||||
MessageBus.Current.SendMessage(new ToastMessage($"Failed to login - {response.ResponseContent.Substring(0, response.ResponseContent.Length < 200 ? response.ResponseContent.Length : 200)}",
|
MessageBus.Current.SendMessage(new ToastMessage($"Failed to login - {response.ResponseContent.Substring(0, response.ResponseContent.Length < 200 ? response.ResponseContent.Length : 200)}",
|
||||||
ToastType.Error, 10));
|
ToastType.Error, 5));
|
||||||
await Console.Error.WriteLineAsync("Full Response: " + response.ResponseContent);
|
await Console.Error.WriteLineAsync("Full Response: " + response.ResponseContent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -231,6 +231,15 @@ public class CrAuth{
|
||||||
|
|
||||||
var response = await HttpClientReq.Instance.SendHttpRequest(request);
|
var response = await HttpClientReq.Instance.SendHttpRequest(request);
|
||||||
|
|
||||||
|
if (response.ResponseContent.Contains("<title>Just a moment...</title>") ||
|
||||||
|
response.ResponseContent.Contains("<title>Access denied</title>") ||
|
||||||
|
response.ResponseContent.Contains("<title>Attention Required! | Cloudflare</title>") ||
|
||||||
|
response.ResponseContent.Trim().Equals("error code: 1020") ||
|
||||||
|
response.ResponseContent.IndexOf("<title>DDOS-GUARD</title>", StringComparison.OrdinalIgnoreCase) > -1){
|
||||||
|
MessageBus.Current.SendMessage(new ToastMessage($"Failed to login - Cloudflare error try to change to BetaAPI in settings", ToastType.Error, 5));
|
||||||
|
Console.Error.WriteLine($"Failed to login - Cloudflare error try to change to BetaAPI in settings");
|
||||||
|
}
|
||||||
|
|
||||||
if (response.IsOk){
|
if (response.IsOk){
|
||||||
JsonTokenToFileAndVariable(response.ResponseContent, uuid);
|
JsonTokenToFileAndVariable(response.ResponseContent, uuid);
|
||||||
|
|
||||||
|
|
@ -242,6 +251,9 @@ public class CrAuth{
|
||||||
} else{
|
} else{
|
||||||
Console.Error.WriteLine("Token Auth Failed");
|
Console.Error.WriteLine("Token Auth Failed");
|
||||||
await AuthAnonymous();
|
await AuthAnonymous();
|
||||||
|
|
||||||
|
MainWindow.Instance.ShowError("Login failed. Please check the log for more details.");
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,18 +40,22 @@ public class CrMovies{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (movie.Total == 1 && movie.Data != null){
|
if (movie is{ Total: 1, Data: not null }){
|
||||||
return movie.Data.First();
|
var movieRes = movie.Data.First();
|
||||||
|
return movieRes.type != "movie" ? null : movieRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.Error.WriteLine("Multiple movie returned with one ID?");
|
Console.Error.WriteLine("Multiple movie returned with one ID?");
|
||||||
if (movie.Data != null) return movie.Data.First();
|
if (movie.Data != null){
|
||||||
|
var movieRes = movie.Data.First();
|
||||||
|
return movieRes.type != "movie" ? null : movieRes;
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public CrunchyEpMeta? EpisodeMeta(CrunchyMovie episodeP, List<string> dubLang){
|
public CrunchyEpMeta? EpisodeMeta(CrunchyMovie episodeP, List<string> dubLang){
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(episodeP.AudioLocale) && !dubLang.Contains(episodeP.AudioLocale)){
|
if (!string.IsNullOrEmpty(episodeP.AudioLocale) && !dubLang.Contains(episodeP.AudioLocale)){
|
||||||
Console.Error.WriteLine("Movie not available in the selected dub lang");
|
Console.Error.WriteLine("Movie not available in the selected dub lang");
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -78,7 +82,7 @@ public class CrMovies{
|
||||||
Time = 0,
|
Time = 0,
|
||||||
DownloadSpeed = 0
|
DownloadSpeed = 0
|
||||||
};
|
};
|
||||||
epMeta.AvailableSubs = new List<string>();
|
epMeta.AvailableSubs = [];
|
||||||
epMeta.Description = episodeP.Description;
|
epMeta.Description = episodeP.Description;
|
||||||
epMeta.Hslang = CrunchyrollManager.Instance.CrunOptions.Hslang;
|
epMeta.Hslang = CrunchyrollManager.Instance.CrunOptions.Hslang;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,11 @@ public class CrMusic{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<CrunchyMusicVideoList?> ParseArtistVideosByIdAsync(string artistId, string crLocale, bool forcedLang = false, bool updateHistory = false){
|
public async Task<CrunchyMusicVideoList?> ParseArtistVideosByIdAsync(string? artistId, string crLocale, bool forcedLang = false, bool updateHistory = false){
|
||||||
|
if (string.IsNullOrEmpty(artistId)){
|
||||||
|
return new CrunchyMusicVideoList();
|
||||||
|
}
|
||||||
|
|
||||||
var musicVideosTask = FetchMediaListAsync($"{ApiUrls.Content}/music/artists/{artistId}/music_videos", crLocale, forcedLang);
|
var musicVideosTask = FetchMediaListAsync($"{ApiUrls.Content}/music/artists/{artistId}/music_videos", crLocale, forcedLang);
|
||||||
var concertsTask = FetchMediaListAsync($"{ApiUrls.Content}/music/artists/{artistId}/concerts", crLocale, forcedLang);
|
var concertsTask = FetchMediaListAsync($"{ApiUrls.Content}/music/artists/{artistId}/concerts", crLocale, forcedLang);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
@ -114,7 +115,8 @@ public class CrunchyrollManager{
|
||||||
options.QualityVideo = "best";
|
options.QualityVideo = "best";
|
||||||
options.CcTag = "CC";
|
options.CcTag = "CC";
|
||||||
options.CcSubsFont = "Trebuchet MS";
|
options.CcSubsFont = "Trebuchet MS";
|
||||||
options.FsRetryTime = 5;
|
options.RetryDelay = 5;
|
||||||
|
options.RetryAttempts = 5;
|
||||||
options.Numbers = 2;
|
options.Numbers = 2;
|
||||||
options.Timeout = 15000;
|
options.Timeout = 15000;
|
||||||
options.DubLang = new List<string>(){ "ja-JP" };
|
options.DubLang = new List<string>(){ "ja-JP" };
|
||||||
|
|
@ -409,7 +411,7 @@ public class CrunchyrollManager{
|
||||||
CcSubsMuxingFlag = options.CcSubsMuxingFlag,
|
CcSubsMuxingFlag = options.CcSubsMuxingFlag,
|
||||||
SignsSubsAsForced = options.SignsSubsAsForced,
|
SignsSubsAsForced = options.SignsSubsAsForced,
|
||||||
},
|
},
|
||||||
fileNameAndPath);
|
fileNameAndPath, data);
|
||||||
|
|
||||||
if (result is{ merger: not null, isMuxed: true }){
|
if (result is{ merger: not null, isMuxed: true }){
|
||||||
mergers.Add(result.merger);
|
mergers.Add(result.merger);
|
||||||
|
|
@ -474,7 +476,7 @@ public class CrunchyrollManager{
|
||||||
CcSubsMuxingFlag = options.CcSubsMuxingFlag,
|
CcSubsMuxingFlag = options.CcSubsMuxingFlag,
|
||||||
SignsSubsAsForced = options.SignsSubsAsForced,
|
SignsSubsAsForced = options.SignsSubsAsForced,
|
||||||
},
|
},
|
||||||
fileNameAndPath);
|
fileNameAndPath, data);
|
||||||
|
|
||||||
syncError = result.syncError;
|
syncError = result.syncError;
|
||||||
muxError = !result.isMuxed;
|
muxError = !result.isMuxed;
|
||||||
|
|
@ -646,7 +648,7 @@ public class CrunchyrollManager{
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private async Task<(Merger? merger, bool isMuxed, bool syncError)> MuxStreams(List<DownloadedMedia> data, CrunchyMuxOptions options, string filename){
|
private async Task<(Merger? merger, bool isMuxed, bool syncError)> MuxStreams(List<DownloadedMedia> data, CrunchyMuxOptions options, string filename, CrunchyEpMeta crunchyEpMeta){
|
||||||
var muxToMp3 = false;
|
var muxToMp3 = false;
|
||||||
|
|
||||||
if (options.Novids == true || data.FindAll(a => a.Type == DownloadMediaType.Video).Count == 0){
|
if (options.Novids == true || data.FindAll(a => a.Type == DownloadMediaType.Video).Count == 0){
|
||||||
|
|
@ -733,6 +735,16 @@ public class CrunchyrollManager{
|
||||||
bool isMuxed, syncError = false;
|
bool isMuxed, syncError = false;
|
||||||
|
|
||||||
if (options is{ SyncTiming: true, DlVideoOnce: true }){
|
if (options is{ SyncTiming: true, DlVideoOnce: true }){
|
||||||
|
crunchyEpMeta.DownloadProgress = new DownloadProgress(){
|
||||||
|
IsDownloading = true,
|
||||||
|
Percent = 100,
|
||||||
|
Time = 0,
|
||||||
|
DownloadSpeed = 0,
|
||||||
|
Doing = "Muxing – Syncing Dub Timings"
|
||||||
|
};
|
||||||
|
|
||||||
|
QueueManager.Instance.Queue.Refresh();
|
||||||
|
|
||||||
var basePath = merger.options.OnlyVid.First().Path;
|
var basePath = merger.options.OnlyVid.First().Path;
|
||||||
var syncVideosList = data.Where(a => a.Type == DownloadMediaType.SyncVideo).ToList();
|
var syncVideosList = data.Where(a => a.Type == DownloadMediaType.SyncVideo).ToList();
|
||||||
|
|
||||||
|
|
@ -764,6 +776,16 @@ public class CrunchyrollManager{
|
||||||
syncVideosList.ForEach(syncVideo => {
|
syncVideosList.ForEach(syncVideo => {
|
||||||
if (syncVideo.Path != null) Helpers.DeleteFile(syncVideo.Path);
|
if (syncVideo.Path != null) Helpers.DeleteFile(syncVideo.Path);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
crunchyEpMeta.DownloadProgress = new DownloadProgress(){
|
||||||
|
IsDownloading = true,
|
||||||
|
Percent = 100,
|
||||||
|
Time = 0,
|
||||||
|
DownloadSpeed = 0,
|
||||||
|
Doing = "Muxing"
|
||||||
|
};
|
||||||
|
|
||||||
|
QueueManager.Instance.Queue.Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.Mp4 && !muxToMp3){
|
if (!options.Mp4 && !muxToMp3){
|
||||||
|
|
@ -777,12 +799,12 @@ public class CrunchyrollManager{
|
||||||
|
|
||||||
private async Task<DownloadResponse> DownloadMediaList(CrunchyEpMeta data, CrDownloadOptions options){
|
private async Task<DownloadResponse> DownloadMediaList(CrunchyEpMeta data, CrDownloadOptions options){
|
||||||
if (Profile.Username == "???"){
|
if (Profile.Username == "???"){
|
||||||
MainWindow.Instance.ShowError("User Account not recognized - are you signed in?");
|
MainWindow.Instance.ShowError($"User Account not recognized - are you signed in?");
|
||||||
return new DownloadResponse{
|
return new DownloadResponse{
|
||||||
Data = new List<DownloadedMedia>(),
|
Data = new List<DownloadedMedia>(),
|
||||||
Error = true,
|
Error = true,
|
||||||
FileName = "./unknown",
|
FileName = "./unknown",
|
||||||
ErrorText = "Login problem"
|
ErrorText = "User Account not recognized - are you signed in?"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1347,7 +1369,7 @@ public class CrunchyrollManager{
|
||||||
|
|
||||||
fileName = Path.Combine(FileNameManager.ParseFileName(options.FileName, variables, options.Numbers, options.Override).ToArray());
|
fileName = Path.Combine(FileNameManager.ParseFileName(options.FileName, variables, options.Numbers, options.Override).ToArray());
|
||||||
|
|
||||||
string onlyFileName = Path.GetFileNameWithoutExtension(fileName);
|
string onlyFileName = Path.GetFileName(fileName);
|
||||||
int maxLength = 220;
|
int maxLength = 220;
|
||||||
|
|
||||||
if (onlyFileName.Length > maxLength){
|
if (onlyFileName.Length > maxLength){
|
||||||
|
|
@ -1361,7 +1383,7 @@ public class CrunchyrollManager{
|
||||||
if (excessLength > 0 && ((string)titleVariable.ReplaceWith).Length > excessLength){
|
if (excessLength > 0 && ((string)titleVariable.ReplaceWith).Length > excessLength){
|
||||||
titleVariable.ReplaceWith = ((string)titleVariable.ReplaceWith).Substring(0, ((string)titleVariable.ReplaceWith).Length - excessLength);
|
titleVariable.ReplaceWith = ((string)titleVariable.ReplaceWith).Substring(0, ((string)titleVariable.ReplaceWith).Length - excessLength);
|
||||||
fileName = Path.Combine(FileNameManager.ParseFileName(options.FileName, variables, options.Numbers, options.Override).ToArray());
|
fileName = Path.Combine(FileNameManager.ParseFileName(options.FileName, variables, options.Numbers, options.Override).ToArray());
|
||||||
onlyFileName = Path.GetFileNameWithoutExtension(fileName);
|
onlyFileName = Path.GetFileName(fileName);
|
||||||
|
|
||||||
if (onlyFileName.Length > maxLength){
|
if (onlyFileName.Length > maxLength){
|
||||||
fileName = Helpers.LimitFileNameLength(fileName, maxLength);
|
fileName = Helpers.LimitFileNameLength(fileName, maxLength);
|
||||||
|
|
@ -1372,7 +1394,7 @@ public class CrunchyrollManager{
|
||||||
fileName = Helpers.LimitFileNameLength(fileName, maxLength);
|
fileName = Helpers.LimitFileNameLength(fileName, maxLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.Error.WriteLine($"Filename changed to {Path.GetFileNameWithoutExtension(fileName)}");
|
Console.Error.WriteLine($"Filename changed to {Path.GetFileName(fileName)}");
|
||||||
}
|
}
|
||||||
|
|
||||||
//string outFile = Path.Combine(FileNameManager.ParseFileName(options.FileName + "." + (epMeta.Lang?.CrLocale ?? lang.Value.Name), variables, options.Numbers, options.Override).ToArray());
|
//string outFile = Path.Combine(FileNameManager.ParseFileName(options.FileName + "." + (epMeta.Lang?.CrLocale ?? lang.Value.Name), variables, options.Numbers, options.Override).ToArray());
|
||||||
|
|
@ -1704,7 +1726,7 @@ public class CrunchyrollManager{
|
||||||
try{
|
try{
|
||||||
// Parsing and constructing the file names
|
// Parsing and constructing the file names
|
||||||
fileName = Path.Combine(FileNameManager.ParseFileName(options.FileName, variables, options.Numbers, options.Override).ToArray());
|
fileName = Path.Combine(FileNameManager.ParseFileName(options.FileName, variables, options.Numbers, options.Override).ToArray());
|
||||||
string outFile = Path.Combine(FileNameManager.ParseFileName(options.FileName + "." + (epMeta.Lang?.CrLocale), variables, options.Numbers, options.Override).ToArray());
|
var outFile = Path.Combine(FileNameManager.ParseFileName(options.FileName + "." + (epMeta.Lang?.CrLocale), variables, options.Numbers, options.Override).ToArray());
|
||||||
if (Path.IsPathRooted(outFile)){
|
if (Path.IsPathRooted(outFile)){
|
||||||
tsFile = outFile;
|
tsFile = outFile;
|
||||||
} else{
|
} else{
|
||||||
|
|
@ -1712,14 +1734,14 @@ public class CrunchyrollManager{
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the path is absolute
|
// Check if the path is absolute
|
||||||
bool isAbsolute = Path.IsPathRooted(outFile);
|
var isAbsolute = Path.IsPathRooted(outFile);
|
||||||
|
|
||||||
// Get all directory parts of the path except the last segment (assuming it's a file)
|
// Get all directory parts of the path except the last segment (assuming it's a file)
|
||||||
string[] directories = Path.GetDirectoryName(outFile)?.Split(Path.DirectorySeparatorChar) ?? Array.Empty<string>();
|
var directories = Path.GetDirectoryName(outFile)?.Split(Path.DirectorySeparatorChar) ??[];
|
||||||
|
|
||||||
// Initialize the cumulative path based on whether the original path is absolute or not
|
// Initialize the cumulative path based on whether the original path is absolute or not
|
||||||
string cumulativePath = isAbsolute ? "" : fileDir;
|
var cumulativePath = isAbsolute ? "" : fileDir;
|
||||||
for (int i = 0; i < directories.Length; i++){
|
for (var i = 0; i < directories.Length; i++){
|
||||||
// Build the path incrementally
|
// Build the path incrementally
|
||||||
cumulativePath = Path.Combine(cumulativePath, directories[i]);
|
cumulativePath = Path.Combine(cumulativePath, directories[i]);
|
||||||
|
|
||||||
|
|
@ -2028,7 +2050,8 @@ public class CrunchyrollManager{
|
||||||
M3U8Json = videoJson,
|
M3U8Json = videoJson,
|
||||||
// BaseUrl = chunkPlaylist.BaseUrl,
|
// BaseUrl = chunkPlaylist.BaseUrl,
|
||||||
Threads = options.Partsize,
|
Threads = options.Partsize,
|
||||||
FsRetryTime = options.FsRetryTime * 1000,
|
FsRetryTime = options.RetryDelay * 1000,
|
||||||
|
Retries = options.RetryAttempts,
|
||||||
Override = options.Force,
|
Override = options.Force,
|
||||||
}, data, true, false);
|
}, data, true, false);
|
||||||
|
|
||||||
|
|
@ -2085,7 +2108,8 @@ public class CrunchyrollManager{
|
||||||
M3U8Json = audioJson,
|
M3U8Json = audioJson,
|
||||||
// BaseUrl = chunkPlaylist.BaseUrl,
|
// BaseUrl = chunkPlaylist.BaseUrl,
|
||||||
Threads = options.Partsize,
|
Threads = options.Partsize,
|
||||||
FsRetryTime = options.FsRetryTime * 1000,
|
FsRetryTime = options.RetryDelay * 1000,
|
||||||
|
Retries = options.RetryAttempts,
|
||||||
Override = options.Force,
|
Override = options.Force,
|
||||||
}, data, false, true);
|
}, data, false, true);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -20,6 +21,7 @@ using CRD.Utils.Structs.History;
|
||||||
using CRD.ViewModels;
|
using CRD.ViewModels;
|
||||||
using CRD.ViewModels.Utils;
|
using CRD.ViewModels.Utils;
|
||||||
using CRD.Views.Utils;
|
using CRD.Views.Utils;
|
||||||
|
using DynamicData;
|
||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
|
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
@ -209,6 +211,11 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
||||||
new(){ Content = "tv/samsung" }
|
new(){ Content = "tv/samsung" }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public ObservableCollection<StringItemWithDisplayName> FFmpegHWAccel{ get; } =[];
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private StringItemWithDisplayName _selectedFFmpegHWAccel;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _isEncodeEnabled;
|
private bool _isEncodeEnabled;
|
||||||
|
|
||||||
|
|
@ -275,6 +282,12 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
||||||
ComboBoxItem? streamEndpoint = StreamEndpoints.FirstOrDefault(a => a.Content != null && (string)a.Content == (options.StreamEndpoint ?? "")) ?? null;
|
ComboBoxItem? streamEndpoint = StreamEndpoints.FirstOrDefault(a => a.Content != null && (string)a.Content == (options.StreamEndpoint ?? "")) ?? null;
|
||||||
SelectedStreamEndpoint = streamEndpoint ?? StreamEndpoints[0];
|
SelectedStreamEndpoint = streamEndpoint ?? StreamEndpoints[0];
|
||||||
|
|
||||||
|
FFmpegHWAccel.AddRange(GetAvailableHWAccelOptions());
|
||||||
|
|
||||||
|
StringItemWithDisplayName? hwAccellFlag = FFmpegHWAccel.FirstOrDefault(a => a.value == options.FfmpegHwAccelFlag) ?? null;
|
||||||
|
SelectedFFmpegHWAccel = hwAccellFlag ?? FFmpegHWAccel[0];
|
||||||
|
|
||||||
|
|
||||||
var softSubLang = SubLangList.Where(a => options.DlSubs.Contains(a.Content)).ToList();
|
var softSubLang = SubLangList.Where(a => options.DlSubs.Contains(a.Content)).ToList();
|
||||||
|
|
||||||
SelectedSubLang.Clear();
|
SelectedSubLang.Clear();
|
||||||
|
|
@ -391,6 +404,8 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
||||||
|
|
||||||
CrunchyrollManager.Instance.CrunOptions.SubsAddScaledBorder = GetScaledBorderAndShadowSelection();
|
CrunchyrollManager.Instance.CrunOptions.SubsAddScaledBorder = GetScaledBorderAndShadowSelection();
|
||||||
|
|
||||||
|
CrunchyrollManager.Instance.CrunOptions.FfmpegHwAccelFlag = SelectedFFmpegHWAccel.value;
|
||||||
|
|
||||||
List<string> softSubs = new List<string>();
|
List<string> softSubs = new List<string>();
|
||||||
foreach (var listBoxItem in SelectedSubLang){
|
foreach (var listBoxItem in SelectedSubLang){
|
||||||
softSubs.Add(listBoxItem.Content + "");
|
softSubs.Add(listBoxItem.Content + "");
|
||||||
|
|
@ -568,4 +583,61 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
||||||
SelectedEncodingPreset = encodingPresetSelected ?? EncodingPresetsList[0];
|
SelectedEncodingPreset = encodingPresetSelected ?? EncodingPresetsList[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<StringItemWithDisplayName> GetAvailableHWAccelOptions(){
|
||||||
|
try{
|
||||||
|
using (var process = new Process()){
|
||||||
|
process.StartInfo.FileName = CfgManager.PathFFMPEG;
|
||||||
|
process.StartInfo.Arguments = "-hwaccels";
|
||||||
|
process.StartInfo.UseShellExecute = false;
|
||||||
|
process.StartInfo.RedirectStandardOutput = true;
|
||||||
|
process.StartInfo.RedirectStandardError = true;
|
||||||
|
process.StartInfo.CreateNoWindow = true;
|
||||||
|
|
||||||
|
string output = string.Empty;
|
||||||
|
|
||||||
|
process.OutputDataReceived += (sender, e) => {
|
||||||
|
if (!string.IsNullOrEmpty(e.Data)){
|
||||||
|
output += e.Data + Environment.NewLine;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
process.Start();
|
||||||
|
|
||||||
|
process.BeginOutputReadLine();
|
||||||
|
// process.BeginErrorReadLine();
|
||||||
|
|
||||||
|
process.WaitForExit();
|
||||||
|
|
||||||
|
var lines = output.Split(new[]{ '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
var accels = lines.Skip(1).Select(l => l.Trim().ToLower()).ToList();
|
||||||
|
return MapHWAccelOptions(accels);
|
||||||
|
}
|
||||||
|
} catch (Exception e){
|
||||||
|
Console.WriteLine("Failed to get Available HW Accel Options" + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return[];
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<StringItemWithDisplayName> MapHWAccelOptions(List<string> accels){
|
||||||
|
var options = new List<StringItemWithDisplayName>{
|
||||||
|
new(){ DisplayName = "CPU Only", value = "" },
|
||||||
|
new(){ DisplayName = "Auto", value = "-hwaccel auto " }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (accels.Contains("cuda")) options.Add(new StringItemWithDisplayName{ DisplayName = "NVIDIA (CUDA)", value = "-hwaccel cuda " });
|
||||||
|
if (accels.Contains("qsv")) options.Add(new StringItemWithDisplayName{ DisplayName = "Intel Quick Sync (QSV)", value = "-hwaccel qsv " });
|
||||||
|
if (accels.Contains("dxva2")) options.Add(new StringItemWithDisplayName{ DisplayName = "AMD/Intel DXVA2", value = "-hwaccel dxva2" });
|
||||||
|
if (accels.Contains("d3d11va")) options.Add(new StringItemWithDisplayName{ DisplayName = "AMD/Intel D3D11VA", value = "-hwaccel d3d11va " });
|
||||||
|
if (accels.Contains("d3d12va")) options.Add(new StringItemWithDisplayName{ DisplayName = "AMD/Intel D3D12VA", value = "-hwaccel d3d12va " });
|
||||||
|
if (accels.Contains("vaapi")) options.Add(new StringItemWithDisplayName{ DisplayName = "VAAPI (Linux)", value = "-hwaccel vaapi " });
|
||||||
|
if (accels.Contains("videotoolbox")) options.Add(new StringItemWithDisplayName{ DisplayName = "Apple VideoToolbox", value = "-hwaccel videotoolbox " });
|
||||||
|
|
||||||
|
// if (accels.Contains("opencl")) options.Add(new(){DisplayName = "OpenCL (Advanced)", value ="-hwaccel opencl "});
|
||||||
|
// if (accels.Contains("vulkan")) options.Add(new(){DisplayName = "Vulkan (Experimental)", value ="-hwaccel vulkan "});
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -409,7 +409,23 @@
|
||||||
|
|
||||||
<controls:SettingsExpanderItem IsVisible="{Binding !SkipMuxing}" Content="Sync Timings" Description="Does not work for all episodes but for the ones that only have a different intro">
|
<controls:SettingsExpanderItem IsVisible="{Binding !SkipMuxing}" Content="Sync Timings" Description="Does not work for all episodes but for the ones that only have a different intro">
|
||||||
<controls:SettingsExpanderItem.Footer>
|
<controls:SettingsExpanderItem.Footer>
|
||||||
<CheckBox IsChecked="{Binding SyncTimings}"> </CheckBox>
|
<StackPanel HorizontalAlignment="Right">
|
||||||
|
<CheckBox HorizontalAlignment="Right" IsChecked="{Binding SyncTimings}"> </CheckBox>
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" IsVisible="{Binding SyncTimings}">
|
||||||
|
<TextBlock VerticalAlignment="Center" Text="Video Processing Method" Margin=" 0 0 5 0" ></TextBlock>
|
||||||
|
<ComboBox HorizontalAlignment="Right"
|
||||||
|
ItemsSource="{Binding FFmpegHWAccel}"
|
||||||
|
SelectedItem="{Binding SelectedFFmpegHWAccel}">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBlock Text="{Binding DisplayName}"></TextBlock>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
</controls:SettingsExpanderItem.Footer>
|
</controls:SettingsExpanderItem.Footer>
|
||||||
</controls:SettingsExpanderItem>
|
</controls:SettingsExpanderItem>
|
||||||
|
|
||||||
|
|
@ -438,7 +454,11 @@
|
||||||
<Border BorderBrush="#4a4a4a" Background="{DynamicResource ControlAltFillColorQuarternary}" BorderThickness="1"
|
<Border BorderBrush="#4a4a4a" Background="{DynamicResource ControlAltFillColorQuarternary}" BorderThickness="1"
|
||||||
CornerRadius="10" Margin="2">
|
CornerRadius="10" Margin="2">
|
||||||
<StackPanel Orientation="Horizontal" Margin="5">
|
<StackPanel Orientation="Horizontal" Margin="5">
|
||||||
<TextBlock Text="{Binding stringValue}" Margin="5,0" />
|
<TextBlock Text="{Binding stringValue}" Margin="5,0" TextTrimming="CharacterEllipsis" MaxWidth="300" TextWrapping="NoWrap">
|
||||||
|
<ToolTip.Tip>
|
||||||
|
<TextBlock Text="{Binding stringValue}" FontSize="15" />
|
||||||
|
</ToolTip.Tip>
|
||||||
|
</TextBlock>
|
||||||
<Button Content="X" FontSize="10" VerticalAlignment="Center"
|
<Button Content="X" FontSize="10" VerticalAlignment="Center"
|
||||||
HorizontalAlignment="Center" Width="15" Height="15" Padding="0"
|
HorizontalAlignment="Center" Width="15" Height="15" Padding="0"
|
||||||
Command="{Binding $parent[ItemsControl].((vm:CrunchyrollSettingsViewModel)DataContext).RemoveMkvMergeParam}"
|
Command="{Binding $parent[ItemsControl].((vm:CrunchyrollSettingsViewModel)DataContext).RemoveMkvMergeParam}"
|
||||||
|
|
@ -476,7 +496,11 @@
|
||||||
<Border BorderBrush="#4a4a4a" Background="{DynamicResource ControlAltFillColorQuarternary}" BorderThickness="1"
|
<Border BorderBrush="#4a4a4a" Background="{DynamicResource ControlAltFillColorQuarternary}" BorderThickness="1"
|
||||||
CornerRadius="10" Margin="2">
|
CornerRadius="10" Margin="2">
|
||||||
<StackPanel Orientation="Horizontal" Margin="5">
|
<StackPanel Orientation="Horizontal" Margin="5">
|
||||||
<TextBlock Text="{Binding stringValue}" Margin="5,0" />
|
<TextBlock Text="{Binding stringValue}" Margin="5,0" TextTrimming="CharacterEllipsis" MaxWidth="300" TextWrapping="NoWrap">
|
||||||
|
<ToolTip.Tip>
|
||||||
|
<TextBlock Text="{Binding stringValue}" FontSize="15" />
|
||||||
|
</ToolTip.Tip>
|
||||||
|
</TextBlock>
|
||||||
<Button Content="X" FontSize="10" VerticalAlignment="Center"
|
<Button Content="X" FontSize="10" VerticalAlignment="Center"
|
||||||
HorizontalAlignment="Center" Width="15" Height="15" Padding="0"
|
HorizontalAlignment="Center" Width="15" Height="15" Padding="0"
|
||||||
Command="{Binding $parent[ItemsControl].((vm:CrunchyrollSettingsViewModel)DataContext).RemoveFfmpegParam}"
|
Command="{Binding $parent[ItemsControl].((vm:CrunchyrollSettingsViewModel)DataContext).RemoveFfmpegParam}"
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,30 @@ namespace CRD.Downloader;
|
||||||
public class History{
|
public class History{
|
||||||
private readonly CrunchyrollManager crunInstance = CrunchyrollManager.Instance;
|
private readonly CrunchyrollManager crunInstance = CrunchyrollManager.Instance;
|
||||||
|
|
||||||
public async Task<bool> CrUpdateSeries(string seriesId, string? seasonId){
|
public async Task<bool> CrUpdateSeries(string? seriesId, string? seasonId){
|
||||||
|
if (string.IsNullOrEmpty(seriesId)){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
await crunInstance.CrAuth.RefreshToken(true);
|
await crunInstance.CrAuth.RefreshToken(true);
|
||||||
|
|
||||||
|
var historySeries = crunInstance.HistoryList.FirstOrDefault(series => series.SeriesId == seriesId);
|
||||||
|
|
||||||
|
if (historySeries != null){
|
||||||
|
if (string.IsNullOrEmpty(seasonId)){
|
||||||
|
foreach (var historySeriesSeason in historySeries.Seasons){
|
||||||
|
foreach (var historyEpisode in historySeriesSeason.EpisodesList){
|
||||||
|
historyEpisode.IsEpisodeAvailableOnStreamingService = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else{
|
||||||
|
foreach (var historyEpisode in historySeries.Seasons.First(historySeason => historySeason.SeasonId == seasonId).EpisodesList){
|
||||||
|
historyEpisode.IsEpisodeAvailableOnStreamingService = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
CrSeriesSearch? parsedSeries = await crunInstance.CrSeries.ParseSeriesById(seriesId, "ja-JP", true);
|
CrSeriesSearch? parsedSeries = await crunInstance.CrSeries.ParseSeriesById(seriesId, "ja-JP", true);
|
||||||
|
|
||||||
if (parsedSeries == null){
|
if (parsedSeries == null){
|
||||||
|
|
@ -31,6 +52,7 @@ public class History{
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsedSeries.Data != null){
|
if (parsedSeries.Data != null){
|
||||||
|
var result = false;
|
||||||
foreach (var s in parsedSeries.Data){
|
foreach (var s in parsedSeries.Data){
|
||||||
var sId = s.Id;
|
var sId = s.Id;
|
||||||
if (s.Versions is{ Count: > 0 }){
|
if (s.Versions is{ Count: > 0 }){
|
||||||
|
|
@ -48,17 +70,19 @@ public class History{
|
||||||
|
|
||||||
var seasonData = await crunInstance.CrSeries.GetSeasonDataById(sId, string.IsNullOrEmpty(crunInstance.CrunOptions.HistoryLang) ? crunInstance.DefaultLocale : crunInstance.CrunOptions.HistoryLang, true);
|
var seasonData = await crunInstance.CrSeries.GetSeasonDataById(sId, string.IsNullOrEmpty(crunInstance.CrunOptions.HistoryLang) ? crunInstance.DefaultLocale : crunInstance.CrunOptions.HistoryLang, true);
|
||||||
|
|
||||||
if (seasonData.Data is{ Count: > 0 }) await UpdateWithSeasonData(seasonData.Data.ToList<IHistorySource>());
|
if (seasonData.Data is{ Count: > 0 }){
|
||||||
|
result = true;
|
||||||
|
await UpdateWithSeasonData(seasonData.Data.ToList<IHistorySource>());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
historySeries ??= crunInstance.HistoryList.FirstOrDefault(series => series.SeriesId == seriesId);
|
||||||
var historySeries = crunInstance.HistoryList.FirstOrDefault(series => series.SeriesId == seriesId);
|
|
||||||
|
|
||||||
if (historySeries != null){
|
if (historySeries != null){
|
||||||
MatchHistorySeriesWithSonarr(false);
|
MatchHistorySeriesWithSonarr(false);
|
||||||
await MatchHistoryEpisodesWithSonarr(false, historySeries);
|
await MatchHistoryEpisodesWithSonarr(false, historySeries);
|
||||||
CfgManager.UpdateHistoryFile();
|
CfgManager.UpdateHistoryFile();
|
||||||
return true;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -115,7 +139,6 @@ public class History{
|
||||||
historySeries.SeriesStreamingService = StreamingService.Crunchyroll;
|
historySeries.SeriesStreamingService = StreamingService.Crunchyroll;
|
||||||
|
|
||||||
await RefreshSeriesData(seriesId, historySeries);
|
await RefreshSeriesData(seriesId, historySeries);
|
||||||
|
|
||||||
var historySeason = historySeries.Seasons.FirstOrDefault(s => s.SeasonId == firstEpisode.GetSeasonId());
|
var historySeason = historySeries.Seasons.FirstOrDefault(s => s.SeasonId == firstEpisode.GetSeasonId());
|
||||||
|
|
||||||
if (historySeason != null){
|
if (historySeason != null){
|
||||||
|
|
@ -140,7 +163,8 @@ public class History{
|
||||||
HistoryEpisodeAvailableDubLang = historySource.GetEpisodeAvailableDubLang(),
|
HistoryEpisodeAvailableDubLang = historySource.GetEpisodeAvailableDubLang(),
|
||||||
HistoryEpisodeAvailableSoftSubs = historySource.GetEpisodeAvailableSoftSubs(),
|
HistoryEpisodeAvailableSoftSubs = historySource.GetEpisodeAvailableSoftSubs(),
|
||||||
EpisodeCrPremiumAirDate = historySource.GetAvailableDate(),
|
EpisodeCrPremiumAirDate = historySource.GetAvailableDate(),
|
||||||
EpisodeType = historySource.GetEpisodeType()
|
EpisodeType = historySource.GetEpisodeType(),
|
||||||
|
IsEpisodeAvailableOnStreamingService = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
historySeason.EpisodesList.Add(newHistoryEpisode);
|
historySeason.EpisodesList.Add(newHistoryEpisode);
|
||||||
|
|
@ -154,6 +178,7 @@ public class History{
|
||||||
historyEpisode.EpisodeSeasonNum = historySource.GetSeasonNum();
|
historyEpisode.EpisodeSeasonNum = historySource.GetSeasonNum();
|
||||||
historyEpisode.EpisodeCrPremiumAirDate = historySource.GetAvailableDate();
|
historyEpisode.EpisodeCrPremiumAirDate = historySource.GetAvailableDate();
|
||||||
historyEpisode.EpisodeType = historySource.GetEpisodeType();
|
historyEpisode.EpisodeType = historySource.GetEpisodeType();
|
||||||
|
historyEpisode.IsEpisodeAvailableOnStreamingService = true;
|
||||||
|
|
||||||
historyEpisode.HistoryEpisodeAvailableDubLang = historySource.GetEpisodeAvailableDubLang();
|
historyEpisode.HistoryEpisodeAvailableDubLang = historySource.GetEpisodeAvailableDubLang();
|
||||||
historyEpisode.HistoryEpisodeAvailableSoftSubs = historySource.GetEpisodeAvailableSoftSubs();
|
historyEpisode.HistoryEpisodeAvailableSoftSubs = historySource.GetEpisodeAvailableSoftSubs();
|
||||||
|
|
@ -218,7 +243,7 @@ public class History{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageBus.Current.SendMessage(new ToastMessage($"Couldn't update download History", ToastType.Warning, 1));
|
MessageBus.Current.SendMessage(new ToastMessage($"Couldn't update download History", ToastType.Warning, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
public HistoryEpisode? GetHistoryEpisode(string? seriesId, string? seasonId, string episodeId){
|
public HistoryEpisode? GetHistoryEpisode(string? seriesId, string? seasonId, string episodeId){
|
||||||
|
|
@ -277,11 +302,11 @@ public class History{
|
||||||
if (historySeries != null){
|
if (historySeries != null){
|
||||||
var historySeason = historySeries.Seasons.FirstOrDefault(s => s.SeasonId == seasonId);
|
var historySeason = historySeries.Seasons.FirstOrDefault(s => s.SeasonId == seasonId);
|
||||||
if (historySeries.HistorySeriesDubLangOverride.Count > 0){
|
if (historySeries.HistorySeriesDubLangOverride.Count > 0){
|
||||||
dublist = historySeries.HistorySeriesDubLangOverride;
|
dublist = historySeries.HistorySeriesDubLangOverride.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (historySeries.HistorySeriesSoftSubsOverride.Count > 0){
|
if (historySeries.HistorySeriesSoftSubsOverride.Count > 0){
|
||||||
sublist = historySeries.HistorySeriesSoftSubsOverride;
|
sublist = historySeries.HistorySeriesSoftSubsOverride.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(historySeries.SeriesDownloadPath)){
|
if (!string.IsNullOrEmpty(historySeries.SeriesDownloadPath)){
|
||||||
|
|
@ -295,11 +320,11 @@ public class History{
|
||||||
if (historySeason != null){
|
if (historySeason != null){
|
||||||
var historyEpisode = historySeason.EpisodesList.Find(e => e.EpisodeId == episodeId);
|
var historyEpisode = historySeason.EpisodesList.Find(e => e.EpisodeId == episodeId);
|
||||||
if (historySeason.HistorySeasonDubLangOverride.Count > 0){
|
if (historySeason.HistorySeasonDubLangOverride.Count > 0){
|
||||||
dublist = historySeason.HistorySeasonDubLangOverride;
|
dublist = historySeason.HistorySeasonDubLangOverride.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (historySeason.HistorySeasonSoftSubsOverride.Count > 0){
|
if (historySeason.HistorySeasonSoftSubsOverride.Count > 0){
|
||||||
sublist = historySeason.HistorySeasonSoftSubsOverride;
|
sublist = historySeason.HistorySeasonSoftSubsOverride.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(historySeason.SeasonDownloadPath)){
|
if (!string.IsNullOrEmpty(historySeason.SeasonDownloadPath)){
|
||||||
|
|
@ -327,11 +352,11 @@ public class History{
|
||||||
if (historySeries != null){
|
if (historySeries != null){
|
||||||
var historySeason = historySeries.Seasons.FirstOrDefault(s => s.SeasonId == seasonId);
|
var historySeason = historySeries.Seasons.FirstOrDefault(s => s.SeasonId == seasonId);
|
||||||
if (historySeries.HistorySeriesDubLangOverride.Count > 0){
|
if (historySeries.HistorySeriesDubLangOverride.Count > 0){
|
||||||
dublist = historySeries.HistorySeriesDubLangOverride;
|
dublist = historySeries.HistorySeriesDubLangOverride.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (historySeason is{ HistorySeasonDubLangOverride.Count: > 0 }){
|
if (historySeason is{ HistorySeasonDubLangOverride.Count: > 0 }){
|
||||||
dublist = historySeason.HistorySeasonDubLangOverride;
|
dublist = historySeason.HistorySeasonDubLangOverride.ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -347,7 +372,7 @@ public class History{
|
||||||
if (historySeries != null){
|
if (historySeries != null){
|
||||||
var historySeason = historySeries.Seasons.FirstOrDefault(s => s.SeasonId == seasonId);
|
var historySeason = historySeries.Seasons.FirstOrDefault(s => s.SeasonId == seasonId);
|
||||||
if (historySeries.HistorySeriesSoftSubsOverride.Count > 0){
|
if (historySeries.HistorySeriesSoftSubsOverride.Count > 0){
|
||||||
sublist = historySeries.HistorySeriesSoftSubsOverride;
|
sublist = historySeries.HistorySeriesSoftSubsOverride.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(historySeries.HistorySeriesVideoQualityOverride)){
|
if (!string.IsNullOrEmpty(historySeries.HistorySeriesVideoQualityOverride)){
|
||||||
|
|
@ -355,7 +380,7 @@ public class History{
|
||||||
}
|
}
|
||||||
|
|
||||||
if (historySeason is{ HistorySeasonSoftSubsOverride.Count: > 0 }){
|
if (historySeason is{ HistorySeasonSoftSubsOverride.Count: > 0 }){
|
||||||
sublist = historySeason.HistorySeasonSoftSubsOverride;
|
sublist = historySeason.HistorySeasonSoftSubsOverride.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (historySeason != null && !string.IsNullOrEmpty(historySeason.HistorySeasonVideoQualityOverride)){
|
if (historySeason != null && !string.IsNullOrEmpty(historySeason.HistorySeasonVideoQualityOverride)){
|
||||||
|
|
@ -551,7 +576,8 @@ public class History{
|
||||||
HistoryEpisodeAvailableDubLang = historySource.GetEpisodeAvailableDubLang(),
|
HistoryEpisodeAvailableDubLang = historySource.GetEpisodeAvailableDubLang(),
|
||||||
HistoryEpisodeAvailableSoftSubs = historySource.GetEpisodeAvailableSoftSubs(),
|
HistoryEpisodeAvailableSoftSubs = historySource.GetEpisodeAvailableSoftSubs(),
|
||||||
EpisodeCrPremiumAirDate = historySource.GetAvailableDate(),
|
EpisodeCrPremiumAirDate = historySource.GetAvailableDate(),
|
||||||
EpisodeType = historySource.GetEpisodeType()
|
EpisodeType = historySource.GetEpisodeType(),
|
||||||
|
IsEpisodeAvailableOnStreamingService = true
|
||||||
};
|
};
|
||||||
|
|
||||||
newSeason.EpisodesList.Add(newHistoryEpisode);
|
newSeason.EpisodesList.Add(newHistoryEpisode);
|
||||||
|
|
@ -566,13 +592,22 @@ public class History{
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var historySeries in crunInstance.HistoryList){
|
foreach (var historySeries in crunInstance.HistoryList){
|
||||||
if (updateAll || string.IsNullOrEmpty(historySeries.SonarrSeriesId)){
|
if (string.IsNullOrEmpty(historySeries.SonarrSeriesId)){
|
||||||
var sonarrSeries = FindClosestMatch(historySeries.SeriesTitle ?? string.Empty);
|
var sonarrSeries = FindClosestMatch(historySeries.SeriesTitle ?? string.Empty);
|
||||||
if (sonarrSeries != null){
|
if (sonarrSeries != null){
|
||||||
historySeries.SonarrSeriesId = sonarrSeries.Id + "";
|
historySeries.SonarrSeriesId = sonarrSeries.Id + "";
|
||||||
historySeries.SonarrTvDbId = sonarrSeries.TvdbId + "";
|
historySeries.SonarrTvDbId = sonarrSeries.TvdbId + "";
|
||||||
historySeries.SonarrSlugTitle = sonarrSeries.TitleSlug;
|
historySeries.SonarrSlugTitle = sonarrSeries.TitleSlug;
|
||||||
}
|
}
|
||||||
|
} else if (updateAll){
|
||||||
|
var sonarrSeries = SonarrClient.Instance.SonarrSeries.FirstOrDefault(series => series.Id + "" == historySeries.SonarrSeriesId);
|
||||||
|
if (sonarrSeries != null){
|
||||||
|
historySeries.SonarrSeriesId = sonarrSeries.Id + "";
|
||||||
|
historySeries.SonarrTvDbId = sonarrSeries.TvdbId + "";
|
||||||
|
historySeries.SonarrSlugTitle = sonarrSeries.TitleSlug;
|
||||||
|
} else{
|
||||||
|
Console.Error.WriteLine($"Unable to find sonarr series for {historySeries.SeriesTitle}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,9 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CRD.Downloader.Crunchyroll;
|
using CRD.Downloader.Crunchyroll;
|
||||||
using CRD.Utils;
|
using CRD.Utils;
|
||||||
using CRD.Utils.Structs;
|
using CRD.Utils.Structs;
|
||||||
|
using CRD.Utils.Structs.History;
|
||||||
using CRD.Utils.Updater;
|
using CRD.Utils.Updater;
|
||||||
|
using ExtendedXmlSerializer.Core.Sources;
|
||||||
using FluentAvalonia.Styling;
|
using FluentAvalonia.Styling;
|
||||||
|
|
||||||
namespace CRD.Downloader;
|
namespace CRD.Downloader;
|
||||||
|
|
@ -66,22 +68,42 @@ public partial class ProgramManager : ObservableObject{
|
||||||
|
|
||||||
private readonly FluentAvaloniaTheme? _faTheme;
|
private readonly FluentAvaloniaTheme? _faTheme;
|
||||||
|
|
||||||
private Queue<Func<Task>> taskQueue = new Queue<Func<Task>>();
|
#region Startup Param Variables
|
||||||
|
|
||||||
|
private Queue<Func<Task>> taskQueue = new Queue<Func<Task>>();
|
||||||
|
bool historyRefreshAdded = false;
|
||||||
private bool exitOnTaskFinish;
|
private bool exitOnTaskFinish;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
public IStorageProvider StorageProvider;
|
public IStorageProvider StorageProvider;
|
||||||
|
|
||||||
public ProgramManager(){
|
public ProgramManager(){
|
||||||
_faTheme = Application.Current?.Styles[0] as FluentAvaloniaTheme;
|
_faTheme = Application.Current?.Styles[0] as FluentAvaloniaTheme;
|
||||||
|
|
||||||
foreach (var arg in Environment.GetCommandLineArgs()){
|
foreach (var arg in Environment.GetCommandLineArgs()){
|
||||||
if (arg == "--historyRefreshAll"){
|
switch (arg){
|
||||||
taskQueue.Enqueue(RefreshAll);
|
case "--historyRefreshAll":
|
||||||
} else if (arg == "--historyAddToQueue"){
|
if (!historyRefreshAdded){
|
||||||
|
taskQueue.Enqueue(() => RefreshHistory(FilterType.All));
|
||||||
|
historyRefreshAdded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case "--historyRefreshActive":
|
||||||
|
if (!historyRefreshAdded){
|
||||||
|
taskQueue.Enqueue(() => RefreshHistory(FilterType.Active));
|
||||||
|
historyRefreshAdded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case "--historyAddToQueue":
|
||||||
taskQueue.Enqueue(AddMissingToQueue);
|
taskQueue.Enqueue(AddMissingToQueue);
|
||||||
} else if (arg == "--exit"){
|
break;
|
||||||
|
case "--exit":
|
||||||
exitOnTaskFinish = true;
|
exitOnTaskFinish = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,16 +112,54 @@ public partial class ProgramManager : ObservableObject{
|
||||||
CleanUpOldUpdater();
|
CleanUpOldUpdater();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RefreshAll(){
|
private async Task RefreshHistory(FilterType filterType){
|
||||||
FetchingData = true;
|
FetchingData = true;
|
||||||
|
|
||||||
foreach (var item in CrunchyrollManager.Instance.HistoryList){
|
|
||||||
|
List<HistorySeries> filteredItems;
|
||||||
|
var historyList = CrunchyrollManager.Instance.HistoryList;
|
||||||
|
|
||||||
|
switch (filterType){
|
||||||
|
case FilterType.All:
|
||||||
|
filteredItems = historyList.ToList();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FilterType.MissingEpisodes:
|
||||||
|
filteredItems = historyList.Where(item => item.NewEpisodes > 0).ToList();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FilterType.MissingEpisodesSonarr:
|
||||||
|
filteredItems = historyList.Where(historySeries =>
|
||||||
|
!string.IsNullOrEmpty(historySeries.SonarrSeriesId) &&
|
||||||
|
historySeries.Seasons.Any(season =>
|
||||||
|
season.EpisodesList.Any(historyEpisode =>
|
||||||
|
!string.IsNullOrEmpty(historyEpisode.SonarrEpisodeId) && !historyEpisode.SonarrHasFile &&
|
||||||
|
(!CrunchyrollManager.Instance.CrunOptions.HistorySkipUnmonitored || historyEpisode.SonarrIsMonitored))))
|
||||||
|
.ToList();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FilterType.ContinuingOnly:
|
||||||
|
filteredItems = historyList.Where(item => !string.IsNullOrEmpty(item.SonarrNextAirDate)).ToList();
|
||||||
|
break;
|
||||||
|
case FilterType.Active:
|
||||||
|
filteredItems = historyList.Where(item => !item.IsInactive).ToList();
|
||||||
|
break;
|
||||||
|
case FilterType.Inactive:
|
||||||
|
filteredItems = historyList.Where(item => item.IsInactive).ToList();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
filteredItems = new List<HistorySeries>();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var item in filteredItems){
|
||||||
item.SetFetchingData();
|
item.SetFetchingData();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < CrunchyrollManager.Instance.HistoryList.Count; i++){
|
for (int i = 0; i < filteredItems.Count; i++){
|
||||||
await CrunchyrollManager.Instance.HistoryList[i].FetchData("");
|
await filteredItems[i].FetchData("");
|
||||||
CrunchyrollManager.Instance.HistoryList[i].UpdateNewEpisodes();
|
filteredItems[i].UpdateNewEpisodes();
|
||||||
}
|
}
|
||||||
|
|
||||||
FetchingData = false;
|
FetchingData = false;
|
||||||
|
|
@ -115,10 +175,16 @@ public partial class ProgramManager : ObservableObject{
|
||||||
|
|
||||||
while (QueueManager.Instance.Queue.Any(e => e.DownloadProgress.Done != true)){
|
while (QueueManager.Instance.Queue.Any(e => e.DownloadProgress.Done != true)){
|
||||||
Console.WriteLine("Waiting for downloads to complete...");
|
Console.WriteLine("Waiting for downloads to complete...");
|
||||||
await Task.Delay(2000); // Wait for 2 second before checking again
|
await Task.Delay(2000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetBackgroundImage(){
|
||||||
|
if (!string.IsNullOrEmpty(CrunchyrollManager.Instance.CrunOptions.BackgroundImagePath)){
|
||||||
|
Helpers.SetBackgroundImage(CrunchyrollManager.Instance.CrunOptions.BackgroundImagePath, CrunchyrollManager.Instance.CrunOptions.BackgroundImageOpacity,
|
||||||
|
CrunchyrollManager.Instance.CrunOptions.BackgroundImageBlurRadius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task Init(){
|
private async Task Init(){
|
||||||
CrunchyrollManager.Instance.InitOptions();
|
CrunchyrollManager.Instance.InitOptions();
|
||||||
|
|
@ -143,11 +209,6 @@ public partial class ProgramManager : ObservableObject{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(CrunchyrollManager.Instance.CrunOptions.BackgroundImagePath)){
|
|
||||||
Helpers.SetBackgroundImage(CrunchyrollManager.Instance.CrunOptions.BackgroundImagePath, CrunchyrollManager.Instance.CrunOptions.BackgroundImageOpacity,
|
|
||||||
CrunchyrollManager.Instance.CrunOptions.BackgroundImageBlurRadius);
|
|
||||||
}
|
|
||||||
|
|
||||||
await CrunchyrollManager.Instance.Init();
|
await CrunchyrollManager.Instance.Init();
|
||||||
|
|
||||||
FinishedLoading = true;
|
FinishedLoading = true;
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,6 @@ public partial class QueueManager : ObservableObject{
|
||||||
}
|
}
|
||||||
|
|
||||||
HasFailedItem = Queue.Any(item => item.DownloadProgress.Error);
|
HasFailedItem = Queue.Any(item => item.DownloadProgress.Error);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -190,6 +189,7 @@ public partial class QueueManager : ObservableObject{
|
||||||
|
|
||||||
Queue.Add(selected);
|
Queue.Add(selected);
|
||||||
|
|
||||||
|
|
||||||
if (selected.Data.Count < dubLang.Count && !CrunchyrollManager.Instance.CrunOptions.DownloadFirstAvailableDub){
|
if (selected.Data.Count < dubLang.Count && !CrunchyrollManager.Instance.CrunOptions.DownloadFirstAvailableDub){
|
||||||
Console.WriteLine("Added Episode to Queue but couldn't find all selected dubs");
|
Console.WriteLine("Added Episode to Queue but couldn't find all selected dubs");
|
||||||
Console.Error.WriteLine("Added Episode to Queue but couldn't find all selected dubs - Available dubs/subs: ");
|
Console.Error.WriteLine("Added Episode to Queue but couldn't find all selected dubs - Available dubs/subs: ");
|
||||||
|
|
@ -214,7 +214,10 @@ public partial class QueueManager : ObservableObject{
|
||||||
Console.Error.WriteLine($"{selected.SeasonTitle} - Season {selected.Season} - {selected.EpisodeTitle} dubs - [{string.Join(", ", languages)}] subs - [{string.Join(", ", selected.AvailableSubs ??[])}]");
|
Console.Error.WriteLine($"{selected.SeasonTitle} - Season {selected.Season} - {selected.EpisodeTitle} dubs - [{string.Join(", ", languages)}] subs - [{string.Join(", ", selected.AvailableSubs ??[])}]");
|
||||||
MessageBus.Current.SendMessage(new ToastMessage($"Couldn't add episode to the queue with current dub settings", ToastType.Error, 2));
|
MessageBus.Current.SendMessage(new ToastMessage($"Couldn't add episode to the queue with current dub settings", ToastType.Error, 2));
|
||||||
}
|
}
|
||||||
} else{
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Console.WriteLine("Couldn't find episode trying to find movie with id");
|
Console.WriteLine("Couldn't find episode trying to find movie with id");
|
||||||
|
|
||||||
var movie = await CrunchyrollManager.Instance.CrMovies.ParseMovieById(epId, crLocale);
|
var movie = await CrunchyrollManager.Instance.CrMovies.ParseMovieById(epId, crLocale);
|
||||||
|
|
@ -243,9 +246,12 @@ public partial class QueueManager : ObservableObject{
|
||||||
|
|
||||||
Console.WriteLine("Added Movie to Queue");
|
Console.WriteLine("Added Movie to Queue");
|
||||||
MessageBus.Current.SendMessage(new ToastMessage($"Added Movie to Queue", ToastType.Information, 1));
|
MessageBus.Current.SendMessage(new ToastMessage($"Added Movie to Queue", ToastType.Information, 1));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
Console.Error.WriteLine($"No episode or movie found with the id: {epId}");
|
||||||
|
MessageBus.Current.SendMessage(new ToastMessage($"Couldn't add episode to the queue - No episode or movie found with the id: {epId}", ToastType.Error, 3));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,24 +2,37 @@
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
using CRD.Utils.JsonConv;
|
using CRD.Utils.JsonConv;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Converters;
|
||||||
|
|
||||||
namespace CRD.Utils;
|
namespace CRD.Utils;
|
||||||
|
|
||||||
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public enum StreamingService{
|
public enum StreamingService{
|
||||||
|
[EnumMember(Value = "Crunchyroll")]
|
||||||
Crunchyroll,
|
Crunchyroll,
|
||||||
|
[EnumMember(Value = "Unknown")]
|
||||||
Unknown
|
Unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public enum EpisodeType{
|
public enum EpisodeType{
|
||||||
|
[EnumMember(Value = "MusicVideo")]
|
||||||
MusicVideo,
|
MusicVideo,
|
||||||
|
[EnumMember(Value = "Concert")]
|
||||||
Concert,
|
Concert,
|
||||||
|
[EnumMember(Value = "Episode")]
|
||||||
Episode,
|
Episode,
|
||||||
|
[EnumMember(Value = "Unknown")]
|
||||||
Unknown
|
Unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public enum SeriesType{
|
public enum SeriesType{
|
||||||
|
[EnumMember(Value = "Artist")]
|
||||||
Artist,
|
Artist,
|
||||||
|
[EnumMember(Value = "Series")]
|
||||||
Series,
|
Series,
|
||||||
|
[EnumMember(Value = "Unknown")]
|
||||||
Unknown
|
Unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,17 +184,25 @@ public enum DownloadMediaType{
|
||||||
Description,
|
Description,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public enum ScaledBorderAndShadowSelection{
|
public enum ScaledBorderAndShadowSelection{
|
||||||
|
[EnumMember(Value = "Dont Add")]
|
||||||
DontAdd,
|
DontAdd,
|
||||||
|
[EnumMember(Value = "ScaledBorderAndShadow Yes")]
|
||||||
ScaledBorderAndShadowYes,
|
ScaledBorderAndShadowYes,
|
||||||
|
[EnumMember(Value = "ScaledBorderAndShadow No")]
|
||||||
ScaledBorderAndShadowNo,
|
ScaledBorderAndShadowNo,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public enum HistoryViewType{
|
public enum HistoryViewType{
|
||||||
|
[EnumMember(Value = "Posters")]
|
||||||
Posters,
|
Posters,
|
||||||
|
[EnumMember(Value = "Table")]
|
||||||
Table,
|
Table,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public enum SortingType{
|
public enum SortingType{
|
||||||
[EnumMember(Value = "Series Title")]
|
[EnumMember(Value = "Series Title")]
|
||||||
SeriesTitle,
|
SeriesTitle,
|
||||||
|
|
@ -193,6 +214,7 @@ public enum SortingType{
|
||||||
HistorySeriesAddDate,
|
HistorySeriesAddDate,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public enum FilterType{
|
public enum FilterType{
|
||||||
[EnumMember(Value = "All")]
|
[EnumMember(Value = "All")]
|
||||||
All,
|
All,
|
||||||
|
|
@ -205,14 +227,27 @@ public enum FilterType{
|
||||||
|
|
||||||
[EnumMember(Value = "Continuing Only")]
|
[EnumMember(Value = "Continuing Only")]
|
||||||
ContinuingOnly,
|
ContinuingOnly,
|
||||||
|
|
||||||
|
[EnumMember(Value = "Active")]
|
||||||
|
Active,
|
||||||
|
|
||||||
|
[EnumMember(Value = "Inactive")]
|
||||||
|
Inactive,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public enum CrunchyUrlType{
|
public enum CrunchyUrlType{
|
||||||
|
[EnumMember(Value = "Artist")]
|
||||||
Artist,
|
Artist,
|
||||||
|
[EnumMember(Value = "MusicVideo")]
|
||||||
MusicVideo,
|
MusicVideo,
|
||||||
|
[EnumMember(Value = "Concert")]
|
||||||
Concert,
|
Concert,
|
||||||
|
[EnumMember(Value = "Episode")]
|
||||||
Episode,
|
Episode,
|
||||||
|
[EnumMember(Value = "Series")]
|
||||||
Series,
|
Series,
|
||||||
|
[EnumMember(Value = "Unknown")]
|
||||||
Unknown
|
Unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -361,7 +361,7 @@ public class HlsDownloader{
|
||||||
throw new Exception("Failed to download key");
|
throw new Exception("Failed to download key");
|
||||||
_data.Keys[kUri] = rkey;
|
_data.Keys[kUri] = rkey;
|
||||||
} catch (Exception ex){
|
} catch (Exception ex){
|
||||||
throw new Exception($"Error at segment {p}: {ex.Message}", ex);
|
throw new Exception($"Key Error at segment {p}: {ex.Message}", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -431,7 +431,7 @@ public class HlsDownloader{
|
||||||
|
|
||||||
// Set default user-agent if not provided
|
// Set default user-agent if not provided
|
||||||
if (!request.Headers.Contains("User-Agent")){
|
if (!request.Headers.Contains("User-Agent")){
|
||||||
request.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:70.0) Gecko/20100101 Firefox/70.0");
|
request.Headers.Add("User-Agent", ApiUrls.FirefoxUserAgent);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await SendRequestWithRetry(request, partIndex, segOffset, isKey, retryCount);
|
return await SendRequestWithRetry(request, partIndex, segOffset, isKey, retryCount);
|
||||||
|
|
@ -445,7 +445,7 @@ public class HlsDownloader{
|
||||||
response = await HttpClientReq.Instance.GetHttpClient().SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
|
response = await HttpClientReq.Instance.GetHttpClient().SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
|
||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
return await ReadContentAsByteArrayAsync(response.Content);
|
return await ReadContentAsByteArrayAsync(response.Content);
|
||||||
} catch (HttpRequestException ex){
|
} catch (Exception ex) when (ex is HttpRequestException or IOException){
|
||||||
// Log retry attempts
|
// Log retry attempts
|
||||||
string partType = isKey ? "Key" : "Part";
|
string partType = isKey ? "Key" : "Part";
|
||||||
int partIndx = partIndex + 1 + segOffset;
|
int partIndx = partIndex + 1 + segOffset;
|
||||||
|
|
@ -455,6 +455,12 @@ public class HlsDownloader{
|
||||||
throw; // rethrow after last retry
|
throw; // rethrow after last retry
|
||||||
|
|
||||||
await Task.Delay(_data.WaitTime);
|
await Task.Delay(_data.WaitTime);
|
||||||
|
}catch (Exception ex) {
|
||||||
|
|
||||||
|
Console.WriteLine($"Unexpected exception at part {partIndex + 1 + segOffset}:");
|
||||||
|
Console.WriteLine($"\tType: {ex.GetType()}");
|
||||||
|
Console.WriteLine($"\tMessage: {ex.Message}");
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -406,6 +406,7 @@ public class Helpers{
|
||||||
// If something went wrong, delete the temporary output file
|
// If something went wrong, delete the temporary output file
|
||||||
File.Delete(tempOutputFilePath);
|
File.Delete(tempOutputFilePath);
|
||||||
Console.Error.WriteLine("FFmpeg processing failed.");
|
Console.Error.WriteLine("FFmpeg processing failed.");
|
||||||
|
Console.Error.WriteLine($"Command: {ffmpegCommand}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return (IsOk: isSuccess, ErrorCode: process.ExitCode);
|
return (IsOk: isSuccess, ErrorCode: process.ExitCode);
|
||||||
|
|
@ -774,7 +775,7 @@ public class Helpers{
|
||||||
AutoDownload = yaml.AutoDownload,
|
AutoDownload = yaml.AutoDownload,
|
||||||
RemoveFinishedDownload = yaml.RemoveFinishedDownload,
|
RemoveFinishedDownload = yaml.RemoveFinishedDownload,
|
||||||
Timeout = yaml.Timeout,
|
Timeout = yaml.Timeout,
|
||||||
FsRetryTime = yaml.FsRetryTime,
|
RetryDelay = yaml.FsRetryTime,
|
||||||
Force = yaml.Force,
|
Force = yaml.Force,
|
||||||
SimultaneousDownloads = yaml.SimultaneousDownloads,
|
SimultaneousDownloads = yaml.SimultaneousDownloads,
|
||||||
Theme = yaml.Theme,
|
Theme = yaml.Theme,
|
||||||
|
|
|
||||||
|
|
@ -271,5 +271,6 @@ public static class ApiUrls{
|
||||||
|
|
||||||
public static string authBasicMob = "Basic eHVuaWh2ZWRidDNtYmlzdWhldnQ6MWtJUzVkeVR2akUwX3JxYUEzWWVBaDBiVVhVbXhXMTE=";
|
public static string authBasicMob = "Basic eHVuaWh2ZWRidDNtYmlzdWhldnQ6MWtJUzVkeVR2akUwX3JxYUEzWWVBaDBiVVhVbXhXMTE=";
|
||||||
|
|
||||||
public static readonly string MobileUserAgent = "Crunchyroll/3.78.3 Android/15 okhttp/4.12.0";
|
public static readonly string MobileUserAgent = "Crunchyroll/3.79.0 Android/15 okhttp/4.12.0";
|
||||||
|
public static readonly string FirefoxUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0";
|
||||||
}
|
}
|
||||||
|
|
@ -23,7 +23,7 @@ public class LocaleConverter : JsonConverter{
|
||||||
return locale;
|
return locale;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Locale.Unknown; // Default to defaulT if no match is found
|
return Locale.Unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer){
|
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer){
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
@ -312,6 +313,7 @@ public class Merger{
|
||||||
return -100;
|
return -100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Load frames from start of the videos
|
// Load frames from start of the videos
|
||||||
var baseFramesStart = Directory.GetFiles(baseFramesDir).Select(fp => new FrameData{
|
var baseFramesStart = Directory.GetFiles(baseFramesDir).Select(fp => new FrameData{
|
||||||
FilePath = fp,
|
FilePath = fp,
|
||||||
|
|
@ -401,9 +403,10 @@ public class Merger{
|
||||||
var result = await Helpers.ExecuteCommandAsync(type, bin, command);
|
var result = await Helpers.ExecuteCommandAsync(type, bin, command);
|
||||||
|
|
||||||
if (!result.IsOk && type == "mkvmerge" && result.ErrorCode == 1){
|
if (!result.IsOk && type == "mkvmerge" && result.ErrorCode == 1){
|
||||||
Console.WriteLine($"[{type}] Mkvmerge finished with at least one warning");
|
Console.Error.WriteLine($"[{type}] Mkvmerge finished with at least one warning");
|
||||||
} else if (!result.IsOk){
|
} else if (!result.IsOk){
|
||||||
Console.Error.WriteLine($"[{type}] Merging failed with exit code {result.ErrorCode}");
|
Console.Error.WriteLine($"[{type}] Merging failed with exit code {result.ErrorCode}");
|
||||||
|
Console.Error.WriteLine($"[{type}] Merging failed command: {command}");
|
||||||
return false;
|
return false;
|
||||||
} else{
|
} else{
|
||||||
Console.WriteLine($"[{type} Done]");
|
Console.WriteLine($"[{type} Done]");
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using CRD.Downloader.Crunchyroll;
|
||||||
using CRD.Utils.Files;
|
using CRD.Utils.Files;
|
||||||
using CRD.Utils.Structs;
|
using CRD.Utils.Structs;
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
|
|
@ -17,7 +18,8 @@ namespace CRD.Utils.Muxing;
|
||||||
public class SyncingHelper{
|
public class SyncingHelper{
|
||||||
public static async Task<(bool IsOk, int ErrorCode, double frameRate)> ExtractFrames(string videoPath, string outputDir, double offset, double duration){
|
public static async Task<(bool IsOk, int ErrorCode, double frameRate)> ExtractFrames(string videoPath, string outputDir, double offset, double duration){
|
||||||
var ffmpegPath = CfgManager.PathFFMPEG;
|
var ffmpegPath = CfgManager.PathFFMPEG;
|
||||||
var arguments = $"-i \"{videoPath}\" -vf \"select='gt(scene,0.1)',showinfo\" -fps_mode vfr -frame_pts true -t {duration} -ss {offset} \"{outputDir}\\frame%05d.png\"";
|
var arguments =
|
||||||
|
$"{CrunchyrollManager.Instance.CrunOptions.FfmpegHwAccelFlag}-ss {offset} -t {duration} -i \"{videoPath}\" -vf \"select='gt(scene,0.1)',showinfo\" -vsync vfr -frame_pts true \"{outputDir}\\frame%05d.jpg\"";
|
||||||
|
|
||||||
var output = "";
|
var output = "";
|
||||||
|
|
||||||
|
|
@ -86,8 +88,8 @@ public class SyncingHelper{
|
||||||
var2 /= count - 1;
|
var2 /= count - 1;
|
||||||
covariance /= count - 1;
|
covariance /= count - 1;
|
||||||
|
|
||||||
double c1 = 0.01 * 0.01 * 255 * 255;
|
double c1 = 0.01 * 0.01;
|
||||||
double c2 = 0.03 * 0.03 * 255 * 255;
|
double c2 = 0.03 * 0.03;
|
||||||
|
|
||||||
double ssim = ((2 * mean1 * mean2 + c1) * (2 * covariance + c2)) /
|
double ssim = ((2 * mean1 * mean2 + c1) * (2 * covariance + c2)) /
|
||||||
((mean1 * mean1 + mean2 * mean2 + c1) * (var1 + var2 + c2));
|
((mean1 * mean1 + mean2 * mean2 + c1) * (var1 + var2 + c2));
|
||||||
|
|
@ -103,7 +105,8 @@ public class SyncingHelper{
|
||||||
for (int y = 0; y < accessor.Height; y++){
|
for (int y = 0; y < accessor.Height; y++){
|
||||||
Span<Rgba32> row = accessor.GetRowSpan(y);
|
Span<Rgba32> row = accessor.GetRowSpan(y);
|
||||||
for (int x = 0; x < row.Length; x++){
|
for (int x = 0; x < row.Length; x++){
|
||||||
pixels[index++] = row[x].R;
|
pixels[index++] = row[x].R / 255f;
|
||||||
|
;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -130,13 +133,14 @@ public class SyncingHelper{
|
||||||
float[] pixels2 = ExtractPixels(image2, targetWidth, targetHeight);
|
float[] pixels2 = ExtractPixels(image2, targetWidth, targetHeight);
|
||||||
|
|
||||||
// Check if any frame is completely black, if so, skip SSIM calculation
|
// Check if any frame is completely black, if so, skip SSIM calculation
|
||||||
if (IsBlackFrame(pixels1) || IsBlackFrame(pixels2)){
|
if (IsBlackFrame(pixels1) || IsBlackFrame(pixels2) ||
|
||||||
|
IsMonochromaticFrame(pixels1) || IsMonochromaticFrame(pixels2)){
|
||||||
// Return a negative value or zero to indicate no SSIM comparison for black frames.
|
// Return a negative value or zero to indicate no SSIM comparison for black frames.
|
||||||
return (-1.0,99);
|
return (-1.0, 99);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute SSIM
|
// Compute SSIM
|
||||||
return (CalculateSSIM(pixels1, pixels2),CalculatePixelDifference(pixels1,pixels2));
|
return (CalculateSSIM(pixels1, pixels2), CalculatePixelDifference(pixels1, pixels2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -151,40 +155,84 @@ public class SyncingHelper{
|
||||||
return totalDifference / count; // Average difference
|
return totalDifference / count; // Average difference
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsBlackFrame(float[] pixels, float threshold = 1.0f){
|
private static bool IsBlackFrame(float[] pixels, float threshold = 0.02f){
|
||||||
// Check if all pixel values are below the threshold, indicating a black frame.
|
// Check if all pixel values are below the threshold, indicating a black frame.
|
||||||
return pixels.All(p => p <= threshold);
|
return pixels.All(p => p <= threshold);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsMonochromaticFrame(float[] pixels, float stdDevThreshold = 0.05f){
|
||||||
|
float avg = pixels.Average();
|
||||||
|
double variance = pixels.Average(p => Math.Pow(p - avg, 2));
|
||||||
|
double stdDev = Math.Sqrt(variance);
|
||||||
|
return stdDev < stdDevThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
public static bool AreFramesSimilar(string imagePath1, string imagePath2, double ssimThreshold){
|
public static bool AreFramesSimilar(string imagePath1, string imagePath2, double ssimThreshold){
|
||||||
var (ssim, pixelDiff) = ComputeSSIM(imagePath1, imagePath2, 256, 256);
|
var (ssim, pixelDiff) = ComputeSSIM(imagePath1, imagePath2, 256, 144);
|
||||||
// Console.WriteLine($"SSIM: {ssim}");
|
// Console.WriteLine($"SSIM: {ssim}");
|
||||||
// Console.WriteLine(pixelDiff);
|
// Console.WriteLine(pixelDiff);
|
||||||
|
|
||||||
return ssim > ssimThreshold && pixelDiff < 10;
|
return ssim > ssimThreshold && pixelDiff < 0.04;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static float[] GetPixelsArray(string imagePath, int targetWidth = 256, int targetHeight = 144){
|
||||||
|
using var image = Image.Load<Rgba32>(imagePath);
|
||||||
|
image.Mutate(x => x.Resize(new ResizeOptions{
|
||||||
|
Size = new Size(targetWidth, targetHeight),
|
||||||
|
Mode = ResizeMode.Max
|
||||||
|
}).Grayscale());
|
||||||
|
return ExtractPixels(image, targetWidth, targetHeight);
|
||||||
|
}
|
||||||
|
|
||||||
public static double CalculateOffset(List<FrameData> baseFrames, List<FrameData> compareFrames,bool reverseCompare = false, double ssimThreshold = 0.9){
|
public static bool AreFramesSimilarPreprocessed(float[] image1, float[] image2, double ssimThreshold){
|
||||||
|
if (IsBlackFrame(image1) || IsBlackFrame(image2) ||
|
||||||
|
IsMonochromaticFrame(image1) || IsMonochromaticFrame(image2)){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pixelDiff = CalculatePixelDifference(image1, image2);
|
||||||
|
|
||||||
|
if (pixelDiff > 0.04){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ssim = CalculateSSIM(image1, image2);
|
||||||
|
|
||||||
|
return ssim > ssimThreshold && pixelDiff < 0.04;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double CalculateOffset(List<FrameData> baseFrames, List<FrameData> compareFrames, bool reverseCompare = false, double ssimThreshold = 0.9){
|
||||||
if (reverseCompare){
|
if (reverseCompare){
|
||||||
baseFrames.Reverse();
|
baseFrames.Reverse();
|
||||||
compareFrames.Reverse();
|
compareFrames.Reverse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var preprocessedCompareFrames = compareFrames.Select(f => new{
|
||||||
|
Frame = f,
|
||||||
|
Pixels = GetPixelsArray(f.FilePath)
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
var delay = 0.0;
|
||||||
|
|
||||||
foreach (var baseFrame in baseFrames){
|
foreach (var baseFrame in baseFrames){
|
||||||
var matchingFrame = compareFrames.FirstOrDefault(f => AreFramesSimilar(baseFrame.FilePath, f.FilePath, ssimThreshold));
|
var baseFramePixels = GetPixelsArray(baseFrame.FilePath);
|
||||||
|
var matchingFrame = preprocessedCompareFrames.AsParallel()
|
||||||
|
.WithExecutionMode(ParallelExecutionMode.ForceParallelism).FirstOrDefault(f => AreFramesSimilarPreprocessed(baseFramePixels, f.Pixels, ssimThreshold));
|
||||||
if (matchingFrame != null){
|
if (matchingFrame != null){
|
||||||
Console.WriteLine($"Matched Frame:");
|
Console.WriteLine($"Matched Frame:");
|
||||||
Console.WriteLine($"\t Base Frame Path: {baseFrame.FilePath} Time: {baseFrame.Time},");
|
Console.WriteLine($"\t Base Frame Path: {baseFrame.FilePath} Time: {baseFrame.Time},");
|
||||||
Console.WriteLine($"\t Compare Frame Path: {matchingFrame.FilePath} Time: {matchingFrame.Time}");
|
Console.WriteLine($"\t Compare Frame Path: {matchingFrame.Frame.FilePath} Time: {matchingFrame.Frame.Time}");
|
||||||
return baseFrame.Time - matchingFrame.Time;
|
delay = baseFrame.Time - matchingFrame.Frame.Time;
|
||||||
|
break;
|
||||||
} else{
|
} else{
|
||||||
// Console.WriteLine($"No Match Found for Base Frame Time: {baseFrame.Time}");
|
// Console.WriteLine($"No Match Found for Base Frame Time: {baseFrame.Time}");
|
||||||
Debug.WriteLine($"No Match Found for Base Frame Time: {baseFrame.Time}");
|
Debug.WriteLine($"No Match Found for Base Frame Time: {baseFrame.Time}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
preprocessedCompareFrames.Clear();
|
||||||
|
GC.Collect(); // Segment float arrays to avoid calling GC.Collect ?
|
||||||
|
|
||||||
|
return delay;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -18,8 +18,11 @@ public class CrDownloadOptions{
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public int Timeout{ get; set; }
|
public int Timeout{ get; set; }
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonProperty("retry_delay")]
|
||||||
public int FsRetryTime{ get; set; }
|
public int RetryDelay{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("retry_attempts")]
|
||||||
|
public int RetryAttempts{ get; set; }
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string Force{ get; set; } = "";
|
public string Force{ get; set; } = "";
|
||||||
|
|
@ -233,6 +236,9 @@ public class CrDownloadOptions{
|
||||||
[JsonProperty("mux_sync_dubs")]
|
[JsonProperty("mux_sync_dubs")]
|
||||||
public bool SyncTiming{ get; set; }
|
public bool SyncTiming{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("mux_sync_hwaccel")]
|
||||||
|
public string? FfmpegHwAccelFlag{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("encode_enabled")]
|
[JsonProperty("encode_enabled")]
|
||||||
public bool IsEncodeEnabled{ get; set; }
|
public bool IsEncodeEnabled{ get; set; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -95,4 +95,8 @@ public class CrunchyMovie{
|
||||||
|
|
||||||
[JsonProperty("premium_date")]
|
[JsonProperty("premium_date")]
|
||||||
public DateTime PremiumDate{ get; set; }
|
public DateTime PremiumDate{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("type")]
|
||||||
|
public string type{ get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -108,6 +108,11 @@ public class StringItem{
|
||||||
public string stringValue{ get; set; }
|
public string stringValue{ get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class StringItemWithDisplayName{
|
||||||
|
public string DisplayName{ get; set; }
|
||||||
|
public string value{ get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class WindowSettings{
|
public class WindowSettings{
|
||||||
public double Width{ get; set; }
|
public double Width{ get; set; }
|
||||||
public double Height{ get; set; }
|
public double Height{ get; set; }
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,9 @@ public class HistoryEpisode : INotifyPropertyChanged{
|
||||||
[JsonProperty("episode_special_episode")]
|
[JsonProperty("episode_special_episode")]
|
||||||
public bool SpecialEpisode{ get; set; }
|
public bool SpecialEpisode{ get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("episode_available_on_streaming_service")]
|
||||||
|
public bool IsEpisodeAvailableOnStreamingService{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("episode_type")]
|
[JsonProperty("episode_type")]
|
||||||
public EpisodeType EpisodeType{ get; set; } = EpisodeType.Unknown;
|
public EpisodeType EpisodeType{ get; set; } = EpisodeType.Unknown;
|
||||||
|
|
||||||
|
|
@ -61,6 +64,22 @@ public class HistoryEpisode : INotifyPropertyChanged{
|
||||||
[JsonProperty("history_episode_available_dub_lang")]
|
[JsonProperty("history_episode_available_dub_lang")]
|
||||||
public List<string> HistoryEpisodeAvailableDubLang{ get; set; } =[];
|
public List<string> HistoryEpisodeAvailableDubLang{ get; set; } =[];
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public string ReleaseDateFormated{
|
||||||
|
get{
|
||||||
|
if (!EpisodeCrPremiumAirDate.HasValue ||
|
||||||
|
EpisodeCrPremiumAirDate.Value == DateTime.MinValue ||
|
||||||
|
EpisodeCrPremiumAirDate.Value.Date == new DateTime(1970, 1, 1))
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
|
||||||
|
var cultureInfo = System.Globalization.CultureInfo.InvariantCulture;
|
||||||
|
string monthAbbreviation = cultureInfo.DateTimeFormat.GetAbbreviatedMonthName(EpisodeCrPremiumAirDate.Value.Month);
|
||||||
|
|
||||||
|
return string.Format("{0:00}.{1}.{2}", EpisodeCrPremiumAirDate.Value.Day, monthAbbreviation, EpisodeCrPremiumAirDate.Value.Year);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public event PropertyChangedEventHandler? PropertyChanged;
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
public void ToggleWasDownloaded(){
|
public void ToggleWasDownloaded(){
|
||||||
|
|
|
||||||
|
|
@ -34,10 +34,10 @@ public class HistorySeason : INotifyPropertyChanged{
|
||||||
public string HistorySeasonVideoQualityOverride{ get; set; } = "";
|
public string HistorySeasonVideoQualityOverride{ get; set; } = "";
|
||||||
|
|
||||||
[JsonProperty("history_season_soft_subs_override")]
|
[JsonProperty("history_season_soft_subs_override")]
|
||||||
public List<string> HistorySeasonSoftSubsOverride{ get; set; } =[];
|
public ObservableCollection<string> HistorySeasonSoftSubsOverride{ get; set; } =[];
|
||||||
|
|
||||||
[JsonProperty("history_season_dub_lang_override")]
|
[JsonProperty("history_season_dub_lang_override")]
|
||||||
public List<string> HistorySeasonDubLangOverride{ get; set; } =[];
|
public ObservableCollection<string> HistorySeasonDubLangOverride{ get; set; } =[];
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string CombinedProperty => SpecialSeason ?? false ? $"Specials {SeasonNum}" : $"Season {SeasonNum}";
|
public string CombinedProperty => SpecialSeason ?? false ? $"Specials {SeasonNum}" : $"Season {SeasonNum}";
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@ using Avalonia.Media.Imaging;
|
||||||
using CRD.Downloader.Crunchyroll;
|
using CRD.Downloader.Crunchyroll;
|
||||||
using CRD.Utils.CustomList;
|
using CRD.Utils.CustomList;
|
||||||
using CRD.Utils.Files;
|
using CRD.Utils.Files;
|
||||||
|
using CRD.Views;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace CRD.Utils.Structs.History;
|
namespace CRD.Utils.Structs.History;
|
||||||
|
|
||||||
|
|
@ -21,6 +23,9 @@ public class HistorySeries : INotifyPropertyChanged{
|
||||||
[JsonProperty("series_type")]
|
[JsonProperty("series_type")]
|
||||||
public SeriesType SeriesType{ get; set; } = SeriesType.Unknown;
|
public SeriesType SeriesType{ get; set; } = SeriesType.Unknown;
|
||||||
|
|
||||||
|
[JsonProperty("series_is_inactive")]
|
||||||
|
public bool IsInactive{ get; set; }
|
||||||
|
|
||||||
[JsonProperty("series_title")]
|
[JsonProperty("series_title")]
|
||||||
public string? SeriesTitle{ get; set; }
|
public string? SeriesTitle{ get; set; }
|
||||||
|
|
||||||
|
|
@ -67,10 +72,10 @@ public class HistorySeries : INotifyPropertyChanged{
|
||||||
public List<string> HistorySeriesAvailableDubLang{ get; set; } =[];
|
public List<string> HistorySeriesAvailableDubLang{ get; set; } =[];
|
||||||
|
|
||||||
[JsonProperty("history_series_soft_subs_override")]
|
[JsonProperty("history_series_soft_subs_override")]
|
||||||
public List<string> HistorySeriesSoftSubsOverride{ get; set; } =[];
|
public ObservableCollection<string> HistorySeriesSoftSubsOverride{ get; set; } =[];
|
||||||
|
|
||||||
[JsonProperty("history_series_dub_lang_override")]
|
[JsonProperty("history_series_dub_lang_override")]
|
||||||
public List<string> HistorySeriesDubLangOverride{ get; set; } =[];
|
public ObservableCollection<string> HistorySeriesDubLangOverride{ get; set; } =[];
|
||||||
|
|
||||||
public event PropertyChangedEventHandler? PropertyChanged;
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
|
|
@ -460,10 +465,11 @@ public class HistorySeries : INotifyPropertyChanged{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task FetchData(string? seasonId){
|
public async Task<bool> FetchData(string? seasonId){
|
||||||
Console.WriteLine($"Fetching Data for: {SeriesTitle}");
|
Console.WriteLine($"Fetching Data for: {SeriesTitle}");
|
||||||
FetchingData = true;
|
FetchingData = true;
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FetchingData)));
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FetchingData)));
|
||||||
|
var isOk = true;
|
||||||
|
|
||||||
switch (SeriesType){
|
switch (SeriesType){
|
||||||
case SeriesType.Artist:
|
case SeriesType.Artist:
|
||||||
|
|
@ -471,6 +477,7 @@ public class HistorySeries : INotifyPropertyChanged{
|
||||||
await CrunchyrollManager.Instance.CrMusic.ParseArtistVideosByIdAsync(SeriesId,
|
await CrunchyrollManager.Instance.CrMusic.ParseArtistVideosByIdAsync(SeriesId,
|
||||||
string.IsNullOrEmpty(CrunchyrollManager.Instance.CrunOptions.HistoryLang) ? CrunchyrollManager.Instance.DefaultLocale : CrunchyrollManager.Instance.CrunOptions.HistoryLang, true, true);
|
string.IsNullOrEmpty(CrunchyrollManager.Instance.CrunOptions.HistoryLang) ? CrunchyrollManager.Instance.DefaultLocale : CrunchyrollManager.Instance.CrunOptions.HistoryLang, true, true);
|
||||||
} catch (Exception e){
|
} catch (Exception e){
|
||||||
|
isOk = false;
|
||||||
Console.Error.WriteLine("Failed to update History artist");
|
Console.Error.WriteLine("Failed to update History artist");
|
||||||
Console.Error.WriteLine(e);
|
Console.Error.WriteLine(e);
|
||||||
}
|
}
|
||||||
|
|
@ -480,8 +487,9 @@ public class HistorySeries : INotifyPropertyChanged{
|
||||||
case SeriesType.Unknown:
|
case SeriesType.Unknown:
|
||||||
default:
|
default:
|
||||||
try{
|
try{
|
||||||
await CrunchyrollManager.Instance.History.CrUpdateSeries(SeriesId, seasonId);
|
isOk = await CrunchyrollManager.Instance.History.CrUpdateSeries(SeriesId, seasonId);
|
||||||
} catch (Exception e){
|
} catch (Exception e){
|
||||||
|
isOk = false;
|
||||||
Console.Error.WriteLine("Failed to update History series");
|
Console.Error.WriteLine("Failed to update History series");
|
||||||
Console.Error.WriteLine(e);
|
Console.Error.WriteLine(e);
|
||||||
}
|
}
|
||||||
|
|
@ -495,6 +503,8 @@ public class HistorySeries : INotifyPropertyChanged{
|
||||||
UpdateNewEpisodes();
|
UpdateNewEpisodes();
|
||||||
FetchingData = false;
|
FetchingData = false;
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FetchingData)));
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FetchingData)));
|
||||||
|
|
||||||
|
return isOk;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveSeason(string? season){
|
public void RemoveSeason(string? season){
|
||||||
|
|
|
||||||
139
CRD/Utils/UI/EpisodeHighlightTextBlock.cs
Normal file
139
CRD/Utils/UI/EpisodeHighlightTextBlock.cs
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using CRD.Downloader.Crunchyroll;
|
||||||
|
using CRD.Utils.Structs.History;
|
||||||
|
|
||||||
|
namespace CRD.Utils.UI;
|
||||||
|
|
||||||
|
public class EpisodeHighlightTextBlock : TextBlock{
|
||||||
|
public static readonly StyledProperty<HistorySeries?> SeriesProperty =
|
||||||
|
AvaloniaProperty.Register<EpisodeHighlightTextBlock, HistorySeries?>(nameof(Series));
|
||||||
|
|
||||||
|
public static readonly StyledProperty<HistorySeason?> SeasonProperty =
|
||||||
|
AvaloniaProperty.Register<EpisodeHighlightTextBlock, HistorySeason?>(nameof(Season));
|
||||||
|
|
||||||
|
public static readonly StyledProperty<HistoryEpisode?> EpisodeProperty =
|
||||||
|
AvaloniaProperty.Register<EpisodeHighlightTextBlock, HistoryEpisode?>(nameof(Episode));
|
||||||
|
|
||||||
|
public static readonly StyledProperty<StreamingService> StreamingServiceProperty =
|
||||||
|
AvaloniaProperty.Register<EpisodeHighlightTextBlock, StreamingService>(nameof(StreamingService));
|
||||||
|
|
||||||
|
|
||||||
|
private HistorySeries? _lastSeries;
|
||||||
|
private HistorySeason? _lastSeason;
|
||||||
|
|
||||||
|
public HistorySeries? Series{
|
||||||
|
get => GetValue(SeriesProperty);
|
||||||
|
set => SetValue(SeriesProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HistorySeason? Season{
|
||||||
|
get => GetValue(SeasonProperty);
|
||||||
|
set => SetValue(SeasonProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HistoryEpisode? Episode{
|
||||||
|
get => GetValue(EpisodeProperty);
|
||||||
|
set => SetValue(EpisodeProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StreamingService StreamingService{
|
||||||
|
get => GetValue(StreamingServiceProperty);
|
||||||
|
set => SetValue(StreamingServiceProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SubscribeSeries(HistorySeries series){
|
||||||
|
series.HistorySeriesDubLangOverride.CollectionChanged += OnCollectionChanged;
|
||||||
|
series.HistorySeriesSoftSubsOverride.CollectionChanged += OnCollectionChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnsubscribeSeries(HistorySeries series){
|
||||||
|
series.HistorySeriesDubLangOverride.CollectionChanged -= OnCollectionChanged;
|
||||||
|
series.HistorySeriesSoftSubsOverride.CollectionChanged -= OnCollectionChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SubscribeSeason(HistorySeason season){
|
||||||
|
season.HistorySeasonDubLangOverride.CollectionChanged += OnCollectionChanged;
|
||||||
|
season.HistorySeasonSoftSubsOverride.CollectionChanged += OnCollectionChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnsubscribeSeason(HistorySeason season){
|
||||||
|
season.HistorySeasonDubLangOverride.CollectionChanged -= OnCollectionChanged;
|
||||||
|
season.HistorySeasonSoftSubsOverride.CollectionChanged -= OnCollectionChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e){
|
||||||
|
base.OnDetachedFromVisualTree(e);
|
||||||
|
|
||||||
|
if (_lastSeries != null)
|
||||||
|
UnsubscribeSeries(_lastSeries);
|
||||||
|
|
||||||
|
if (_lastSeason != null)
|
||||||
|
UnsubscribeSeason(_lastSeason);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e){
|
||||||
|
UpdateText();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change){
|
||||||
|
base.OnPropertyChanged(change);
|
||||||
|
|
||||||
|
if (change.Property == SeriesProperty){
|
||||||
|
if (_lastSeries != null)
|
||||||
|
UnsubscribeSeries(_lastSeries);
|
||||||
|
|
||||||
|
_lastSeries = change.NewValue as HistorySeries;
|
||||||
|
|
||||||
|
if (_lastSeries != null)
|
||||||
|
SubscribeSeries(_lastSeries);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (change.Property == SeasonProperty){
|
||||||
|
if (_lastSeason != null)
|
||||||
|
UnsubscribeSeason(_lastSeason);
|
||||||
|
|
||||||
|
_lastSeason = change.NewValue as HistorySeason;
|
||||||
|
|
||||||
|
if (_lastSeason != null)
|
||||||
|
SubscribeSeason(_lastSeason);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (change.Property == SeriesProperty ||
|
||||||
|
change.Property == SeasonProperty ||
|
||||||
|
change.Property == StreamingServiceProperty){
|
||||||
|
UpdateText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateText(){
|
||||||
|
|
||||||
|
Text = "E" + Episode?.Episode + " - " + Episode?.EpisodeTitle;
|
||||||
|
|
||||||
|
var streamingService = Series?.SeriesStreamingService ?? StreamingService;
|
||||||
|
|
||||||
|
var dubSet =
|
||||||
|
Season?.HistorySeasonDubLangOverride.Any() == true ? new HashSet<string>(Season.HistorySeasonDubLangOverride) :
|
||||||
|
Series?.HistorySeriesDubLangOverride.Any() == true ? new HashSet<string>(Series.HistorySeriesDubLangOverride) :
|
||||||
|
streamingService == StreamingService.Crunchyroll ? new HashSet<string>(CrunchyrollManager.Instance.CrunOptions.DubLang) :
|
||||||
|
new HashSet<string>();
|
||||||
|
|
||||||
|
var subSet =
|
||||||
|
Season?.HistorySeasonSoftSubsOverride.Any() == true ? new HashSet<string>(Season.HistorySeasonSoftSubsOverride) :
|
||||||
|
Series?.HistorySeriesSoftSubsOverride.Any() == true ? new HashSet<string>(Series.HistorySeriesSoftSubsOverride) :
|
||||||
|
streamingService == StreamingService.Crunchyroll ? new HashSet<string>(CrunchyrollManager.Instance.CrunOptions.DlSubs) :
|
||||||
|
new HashSet<string>();
|
||||||
|
|
||||||
|
var higlight = dubSet.IsSubsetOf(Episode?.HistoryEpisodeAvailableDubLang ?? []) &&
|
||||||
|
subSet.IsSubsetOf(Episode?.HistoryEpisodeAvailableSoftSubs ?? []);
|
||||||
|
|
||||||
|
if (higlight){
|
||||||
|
Foreground = Brushes.Orange;
|
||||||
|
} else{
|
||||||
|
ClearValue(ForegroundProperty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
170
CRD/Utils/UI/HighlightingTextBlock.cs
Normal file
170
CRD/Utils/UI/HighlightingTextBlock.cs
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Documents;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using CRD.Downloader.Crunchyroll;
|
||||||
|
using CRD.Utils.Structs.History;
|
||||||
|
|
||||||
|
namespace CRD.Utils.UI;
|
||||||
|
|
||||||
|
public class HighlightingTextBlock : TextBlock{
|
||||||
|
public static readonly StyledProperty<IEnumerable<string>?> ItemsProperty =
|
||||||
|
AvaloniaProperty.Register<HighlightingTextBlock, IEnumerable<string>?>(nameof(Items));
|
||||||
|
|
||||||
|
public static readonly StyledProperty<HistorySeries?> SeriesProperty =
|
||||||
|
AvaloniaProperty.Register<HighlightingTextBlock, HistorySeries?>(nameof(Series));
|
||||||
|
|
||||||
|
public static readonly StyledProperty<HistorySeason?> SeasonProperty =
|
||||||
|
AvaloniaProperty.Register<HighlightingTextBlock, HistorySeason?>(nameof(Season));
|
||||||
|
|
||||||
|
public static readonly StyledProperty<StreamingService> StreamingServiceProperty =
|
||||||
|
AvaloniaProperty.Register<HighlightingTextBlock, StreamingService>(nameof(StreamingService));
|
||||||
|
|
||||||
|
public static readonly StyledProperty<bool> CheckDubsProperty =
|
||||||
|
AvaloniaProperty.Register<HighlightingTextBlock, bool>(nameof(CheckDubs));
|
||||||
|
|
||||||
|
public static readonly StyledProperty<bool> HighlightEntireTextProperty =
|
||||||
|
AvaloniaProperty.Register<HighlightingTextBlock, bool>(nameof(HighlightEntireText));
|
||||||
|
|
||||||
|
private HistorySeries? _lastSeries;
|
||||||
|
private HistorySeason? _lastSeason;
|
||||||
|
|
||||||
|
public bool HighlightEntireText{
|
||||||
|
get => GetValue(HighlightEntireTextProperty);
|
||||||
|
set => SetValue(HighlightEntireTextProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CheckDubs{
|
||||||
|
get => GetValue(CheckDubsProperty);
|
||||||
|
set => SetValue(CheckDubsProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<string>? Items{
|
||||||
|
get => GetValue(ItemsProperty);
|
||||||
|
set => SetValue(ItemsProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HistorySeries? Series{
|
||||||
|
get => GetValue(SeriesProperty);
|
||||||
|
set => SetValue(SeriesProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HistorySeason? Season{
|
||||||
|
get => GetValue(SeasonProperty);
|
||||||
|
set => SetValue(SeasonProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StreamingService StreamingService{
|
||||||
|
get => GetValue(StreamingServiceProperty);
|
||||||
|
set => SetValue(StreamingServiceProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SubscribeSeries(HistorySeries series){
|
||||||
|
series.HistorySeriesDubLangOverride.CollectionChanged += OnCollectionChanged;
|
||||||
|
series.HistorySeriesSoftSubsOverride.CollectionChanged += OnCollectionChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnsubscribeSeries(HistorySeries series){
|
||||||
|
series.HistorySeriesDubLangOverride.CollectionChanged -= OnCollectionChanged;
|
||||||
|
series.HistorySeriesSoftSubsOverride.CollectionChanged -= OnCollectionChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SubscribeSeason(HistorySeason season){
|
||||||
|
season.HistorySeasonDubLangOverride.CollectionChanged += OnCollectionChanged;
|
||||||
|
season.HistorySeasonSoftSubsOverride.CollectionChanged += OnCollectionChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnsubscribeSeason(HistorySeason season){
|
||||||
|
season.HistorySeasonDubLangOverride.CollectionChanged -= OnCollectionChanged;
|
||||||
|
season.HistorySeasonSoftSubsOverride.CollectionChanged -= OnCollectionChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e){
|
||||||
|
UpdateText();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e){
|
||||||
|
base.OnDetachedFromVisualTree(e);
|
||||||
|
|
||||||
|
if (_lastSeries != null)
|
||||||
|
UnsubscribeSeries(_lastSeries);
|
||||||
|
|
||||||
|
if (_lastSeason != null)
|
||||||
|
UnsubscribeSeason(_lastSeason);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change){
|
||||||
|
base.OnPropertyChanged(change);
|
||||||
|
|
||||||
|
if (change.Property == SeriesProperty){
|
||||||
|
if (_lastSeries != null)
|
||||||
|
UnsubscribeSeries(_lastSeries);
|
||||||
|
|
||||||
|
_lastSeries = change.NewValue as HistorySeries;
|
||||||
|
|
||||||
|
if (_lastSeries != null)
|
||||||
|
SubscribeSeries(_lastSeries);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (change.Property == SeasonProperty){
|
||||||
|
if (_lastSeason != null)
|
||||||
|
UnsubscribeSeason(_lastSeason);
|
||||||
|
|
||||||
|
_lastSeason = change.NewValue as HistorySeason;
|
||||||
|
|
||||||
|
if (_lastSeason != null)
|
||||||
|
SubscribeSeason(_lastSeason);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (change.Property == ItemsProperty ||
|
||||||
|
change.Property == SeriesProperty ||
|
||||||
|
change.Property == SeasonProperty ||
|
||||||
|
change.Property == StreamingServiceProperty ||
|
||||||
|
change.Property == CheckDubsProperty){
|
||||||
|
UpdateText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateText(){
|
||||||
|
Inlines?.Clear();
|
||||||
|
if (Items == null) return;
|
||||||
|
|
||||||
|
var streamingService = Series?.SeriesStreamingService ?? StreamingService;
|
||||||
|
|
||||||
|
IEnumerable<string> source;
|
||||||
|
|
||||||
|
if (CheckDubs){
|
||||||
|
source =
|
||||||
|
Season?.HistorySeasonDubLangOverride?.Any() == true ? Season.HistorySeasonDubLangOverride :
|
||||||
|
Series?.HistorySeriesDubLangOverride?.Any() == true ? Series.HistorySeriesDubLangOverride :
|
||||||
|
streamingService == StreamingService.Crunchyroll ? CrunchyrollManager.Instance.CrunOptions.DubLang :
|
||||||
|
Enumerable.Empty<string>();
|
||||||
|
} else{
|
||||||
|
source =
|
||||||
|
Season?.HistorySeasonSoftSubsOverride?.Any() == true ? Season.HistorySeasonSoftSubsOverride :
|
||||||
|
Series?.HistorySeriesSoftSubsOverride?.Any() == true ? Series.HistorySeriesSoftSubsOverride :
|
||||||
|
streamingService == StreamingService.Crunchyroll ? CrunchyrollManager.Instance.CrunOptions.DlSubs :
|
||||||
|
Enumerable.Empty<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var highlightSet = new HashSet<string>(source);
|
||||||
|
|
||||||
|
|
||||||
|
foreach (var item in Items){
|
||||||
|
var run = new Run(item);
|
||||||
|
|
||||||
|
if (highlightSet.Contains(item)){
|
||||||
|
run.Foreground = Brushes.Orange;
|
||||||
|
// run.FontWeight = FontWeight.Bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
Inlines?.Add(run);
|
||||||
|
Inlines?.Add(new Run(", "));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Inlines?.Count > 0)
|
||||||
|
Inlines.RemoveAt(Inlines.Count - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,7 @@ using CRD.Utils.Files;
|
||||||
using CRD.Utils.Sonarr;
|
using CRD.Utils.Sonarr;
|
||||||
using CRD.Utils.Structs;
|
using CRD.Utils.Structs;
|
||||||
using CRD.Utils.Structs.History;
|
using CRD.Utils.Structs.History;
|
||||||
|
using CRD.Views;
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
|
|
@ -265,7 +266,7 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
||||||
ApplyFilter();
|
ApplyFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ApplyFilter(){
|
public void ApplyFilter(){
|
||||||
List<HistorySeries> filteredItems;
|
List<HistorySeries> filteredItems;
|
||||||
|
|
||||||
switch (currentFilterType){
|
switch (currentFilterType){
|
||||||
|
|
@ -282,13 +283,20 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
||||||
!string.IsNullOrEmpty(historySeries.SonarrSeriesId) &&
|
!string.IsNullOrEmpty(historySeries.SonarrSeriesId) &&
|
||||||
historySeries.Seasons.Any(season =>
|
historySeries.Seasons.Any(season =>
|
||||||
season.EpisodesList.Any(historyEpisode =>
|
season.EpisodesList.Any(historyEpisode =>
|
||||||
!string.IsNullOrEmpty(historyEpisode.SonarrEpisodeId) && !historyEpisode.SonarrHasFile)))
|
!string.IsNullOrEmpty(historyEpisode.SonarrEpisodeId) && !historyEpisode.SonarrHasFile &&
|
||||||
|
(!CrunchyrollManager.Instance.CrunOptions.HistorySkipUnmonitored || historyEpisode.SonarrIsMonitored))))
|
||||||
.ToList();
|
.ToList();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FilterType.ContinuingOnly:
|
case FilterType.ContinuingOnly:
|
||||||
filteredItems = Items.Where(item => !string.IsNullOrEmpty(item.SonarrNextAirDate)).ToList();
|
filteredItems = Items.Where(item => !string.IsNullOrEmpty(item.SonarrNextAirDate)).ToList();
|
||||||
break;
|
break;
|
||||||
|
case FilterType.Active:
|
||||||
|
filteredItems = Items.Where(item => !item.IsInactive).ToList();
|
||||||
|
break;
|
||||||
|
case FilterType.Inactive:
|
||||||
|
filteredItems = Items.Where(item => item.IsInactive).ToList();
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
filteredItems = new List<HistorySeries>();
|
filteredItems = new List<HistorySeries>();
|
||||||
|
|
@ -532,6 +540,20 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
||||||
seriesArgs.Series?.UpdateNewEpisodes();
|
seriesArgs.Series?.UpdateNewEpisodes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public async Task UpdateData(SeasonDialogArgs seriesArgs){
|
||||||
|
if (seriesArgs.Series != null){
|
||||||
|
var result = await seriesArgs.Series.FetchData(seriesArgs.Season?.SeasonId);
|
||||||
|
|
||||||
|
MessageBus.Current.SendMessage(result
|
||||||
|
? new ToastMessage(string.IsNullOrEmpty(seriesArgs.Season?.SeasonId) ? $"Series Refreshed" : $"Season Refreshed", ToastType.Information, 2)
|
||||||
|
: new ToastMessage(string.IsNullOrEmpty(seriesArgs.Season?.SeasonId) ? $"Series Refresh Failed" : $"Season Refreshed Failed", ToastType.Error, 2));
|
||||||
|
} else{
|
||||||
|
MessageBus.Current.SendMessage(new ToastMessage(string.IsNullOrEmpty(seriesArgs.Season?.SeasonId) ? $"Refresh Failed" : $"Season Refresh Failed", ToastType.Error, 2));
|
||||||
|
Console.Error.WriteLine("Failed to get Series Data from View Tree - report issue");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
public void OpenFolderPath(HistorySeries? series){
|
public void OpenFolderPath(HistorySeries? series){
|
||||||
try{
|
try{
|
||||||
|
|
@ -544,6 +566,11 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
||||||
Console.Error.WriteLine($"An error occurred while opening the folder: {ex.Message}");
|
Console.Error.WriteLine($"An error occurred while opening the folder: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public void ToggleInactive(){
|
||||||
|
CfgManager.UpdateHistoryFile();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class HistoryPageProperties{
|
public class HistoryPageProperties{
|
||||||
|
|
|
||||||
|
|
@ -38,12 +38,6 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
||||||
|
|
||||||
private IStorageProvider? _storageProvider;
|
private IStorageProvider? _storageProvider;
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private string _availableDubs;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private string _availableSubs;
|
|
||||||
|
|
||||||
public SeriesPageViewModel(){
|
public SeriesPageViewModel(){
|
||||||
_storageProvider = ProgramManager.Instance.StorageProvider ?? throw new ArgumentNullException(nameof(ProgramManager.Instance.StorageProvider));
|
_storageProvider = ProgramManager.Instance.StorageProvider ?? throw new ArgumentNullException(nameof(ProgramManager.Instance.StorageProvider));
|
||||||
|
|
||||||
|
|
@ -62,7 +56,6 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
||||||
if (SonarrAvailable){
|
if (SonarrAvailable){
|
||||||
ShowMonitoredBookmark = CrunchyrollManager.Instance.CrunOptions.HistorySkipUnmonitored;
|
ShowMonitoredBookmark = CrunchyrollManager.Instance.CrunOptions.HistorySkipUnmonitored;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else{
|
} else{
|
||||||
SonarrAvailable = false;
|
SonarrAvailable = false;
|
||||||
}
|
}
|
||||||
|
|
@ -70,14 +63,10 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
||||||
SonarrConnected = SonarrAvailable = false;
|
SonarrConnected = SonarrAvailable = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
AvailableDubs = "Available Dubs: " + string.Join(", ", SelectedSeries.HistorySeriesAvailableDubLang);
|
|
||||||
AvailableSubs = "Available Subs: " + string.Join(", ", SelectedSeries.HistorySeriesAvailableSoftSubs);
|
|
||||||
|
|
||||||
SelectedSeries.UpdateSeriesFolderPath();
|
SelectedSeries.UpdateSeriesFolderPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
public async Task OpenFolderDialogAsync(HistorySeason? season){
|
public async Task OpenFolderDialogAsync(HistorySeason? season){
|
||||||
if (_storageProvider == null){
|
if (_storageProvider == null){
|
||||||
|
|
@ -188,13 +177,14 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
public async Task UpdateData(string? season){
|
public async Task UpdateData(string? season){
|
||||||
await SelectedSeries.FetchData(season);
|
var result = await SelectedSeries.FetchData(season);
|
||||||
|
|
||||||
|
MessageBus.Current.SendMessage(result
|
||||||
|
? new ToastMessage(string.IsNullOrEmpty(season) ? $"Series Refreshed" : $"Season Refreshed", ToastType.Information, 2)
|
||||||
|
: new ToastMessage(string.IsNullOrEmpty(season) ? $"Series Refresh Failed" : $"Season Refresh Failed", ToastType.Error, 2));
|
||||||
|
|
||||||
SelectedSeries.Seasons.Refresh();
|
SelectedSeries.Seasons.Refresh();
|
||||||
|
|
||||||
AvailableDubs = "Available Dubs: " + string.Join(", ", SelectedSeries.HistorySeriesAvailableDubLang);
|
|
||||||
AvailableSubs = "Available Subs: " + string.Join(", ", SelectedSeries.HistorySeriesAvailableSoftSubs);
|
|
||||||
|
|
||||||
// MessageBus.Current.SendMessage(new NavigationMessage(typeof(SeriesPageViewModel), false, true));
|
// MessageBus.Current.SendMessage(new NavigationMessage(typeof(SeriesPageViewModel), false, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -210,6 +200,11 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public void ToggleInactive(){
|
||||||
|
CfgManager.UpdateHistoryFile();
|
||||||
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
public void NavBack(){
|
public void NavBack(){
|
||||||
SelectedSeries.UpdateNewEpisodes();
|
SelectedSeries.UpdateNewEpisodes();
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,12 @@ public partial class GeneralSettingsViewModel : ViewModelBase{
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private double? _downloadSpeed;
|
private double? _downloadSpeed;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private double? _retryAttempts;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private double? _retryDelay;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private ComboBoxItem _selectedHistoryLang;
|
private ComboBoxItem _selectedHistoryLang;
|
||||||
|
|
||||||
|
|
@ -258,6 +264,8 @@ public partial class GeneralSettingsViewModel : ViewModelBase{
|
||||||
HistorySkipUnmonitored = options.HistorySkipUnmonitored;
|
HistorySkipUnmonitored = options.HistorySkipUnmonitored;
|
||||||
HistoryCountSonarr = options.HistoryCountSonarr;
|
HistoryCountSonarr = options.HistoryCountSonarr;
|
||||||
DownloadSpeed = options.DownloadSpeedLimit;
|
DownloadSpeed = options.DownloadSpeedLimit;
|
||||||
|
RetryAttempts = Math.Clamp((options.RetryAttempts), 1, 10);
|
||||||
|
RetryDelay = Math.Clamp((options.RetryDelay), 1, 30);
|
||||||
DownloadToTempFolder = options.DownloadToTempFolder;
|
DownloadToTempFolder = options.DownloadToTempFolder;
|
||||||
SimultaneousDownloads = options.SimultaneousDownloads;
|
SimultaneousDownloads = options.SimultaneousDownloads;
|
||||||
LogMode = options.LogMode;
|
LogMode = options.LogMode;
|
||||||
|
|
@ -284,6 +292,9 @@ public partial class GeneralSettingsViewModel : ViewModelBase{
|
||||||
CrunchyrollManager.Instance.CrunOptions.BackgroundImageBlurRadius = Math.Clamp((BackgroundImageBlurRadius ?? 0), 0, 40);
|
CrunchyrollManager.Instance.CrunOptions.BackgroundImageBlurRadius = Math.Clamp((BackgroundImageBlurRadius ?? 0), 0, 40);
|
||||||
CrunchyrollManager.Instance.CrunOptions.BackgroundImageOpacity = Math.Clamp((BackgroundImageOpacity ?? 0), 0, 1);
|
CrunchyrollManager.Instance.CrunOptions.BackgroundImageOpacity = Math.Clamp((BackgroundImageOpacity ?? 0), 0, 1);
|
||||||
|
|
||||||
|
CrunchyrollManager.Instance.CrunOptions.RetryAttempts = Math.Clamp((int)(RetryAttempts ?? 0), 1, 10);
|
||||||
|
CrunchyrollManager.Instance.CrunOptions.RetryDelay = Math.Clamp((int)(RetryDelay ?? 0), 1, 30);
|
||||||
|
|
||||||
CrunchyrollManager.Instance.CrunOptions.DownloadToTempFolder = DownloadToTempFolder;
|
CrunchyrollManager.Instance.CrunOptions.DownloadToTempFolder = DownloadToTempFolder;
|
||||||
CrunchyrollManager.Instance.CrunOptions.HistoryAddSpecials = HistoryAddSpecials;
|
CrunchyrollManager.Instance.CrunOptions.HistoryAddSpecials = HistoryAddSpecials;
|
||||||
CrunchyrollManager.Instance.CrunOptions.HistoryIncludeCrArtists = HistoryIncludeCrArtists;
|
CrunchyrollManager.Instance.CrunOptions.HistoryIncludeCrArtists = HistoryIncludeCrArtists;
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@
|
||||||
<Popup IsLightDismissEnabled="True"
|
<Popup IsLightDismissEnabled="True"
|
||||||
MaxWidth="{Binding Bounds.Width, ElementName=SearchBar}"
|
MaxWidth="{Binding Bounds.Width, ElementName=SearchBar}"
|
||||||
MaxHeight="{Binding Bounds.Height, ElementName=Grid}"
|
MaxHeight="{Binding Bounds.Height, ElementName=Grid}"
|
||||||
|
Width="{Binding Bounds.Width, ElementName=SearchBar}"
|
||||||
IsOpen="{Binding SearchPopupVisible}"
|
IsOpen="{Binding SearchPopupVisible}"
|
||||||
Placement="Bottom"
|
Placement="Bottom"
|
||||||
PlacementTarget="{Binding ElementName=SearchBar}"
|
PlacementTarget="{Binding ElementName=SearchBar}"
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
<ui:UiSonarrIdToVisibilityConverter x:Key="UiSonarrIdToVisibilityConverter" />
|
<ui:UiSonarrIdToVisibilityConverter x:Key="UiSonarrIdToVisibilityConverter" />
|
||||||
<ui:UiListToStringConverter x:Key="UiListToStringConverter" />
|
<ui:UiListToStringConverter x:Key="UiListToStringConverter" />
|
||||||
<ui:UiListHasElementsConverter x:Key="UiListHasElementsConverter" />
|
<ui:UiListHasElementsConverter x:Key="UiListHasElementsConverter" />
|
||||||
<ui:UiSeriesSeasonConverter x:Key="UiSeriesSeasonConverter"/>
|
<ui:UiSeriesSeasonConverter x:Key="UiSeriesSeasonConverter" />
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -402,16 +402,38 @@
|
||||||
|
|
||||||
<TextBlock Grid.Row="0" FontSize="25" Text="{Binding SeriesTitle}" TextTrimming="CharacterEllipsis"></TextBlock>
|
<TextBlock Grid.Row="0" FontSize="25" Text="{Binding SeriesTitle}" TextTrimming="CharacterEllipsis"></TextBlock>
|
||||||
<TextBlock Grid.Row="1" FontSize="15" Margin="0 0 0 5" TextWrapping="Wrap"
|
<TextBlock Grid.Row="1" FontSize="15" Margin="0 0 0 5" TextWrapping="Wrap"
|
||||||
Text="{Binding SeriesDescription}">
|
Text="{Binding SeriesDescription}" MinWidth="200">
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
<StackPanel Grid.Row="3" Orientation="Horizontal" IsVisible="{Binding HistorySeriesAvailableDubLang, Converter={StaticResource UiListHasElementsConverter}}">
|
<StackPanel Grid.Row="3" Orientation="Horizontal" VerticalAlignment="Center"
|
||||||
<TextBlock FontSize="15" Opacity="0.8" Text="Available Dubs: " />
|
IsVisible="{Binding HistorySeriesAvailableDubLang, Converter={StaticResource UiListHasElementsConverter}}">
|
||||||
<TextBlock FontSize="15" Opacity="0.8" TextWrapping="Wrap" Text="{Binding HistorySeriesAvailableDubLang, Converter={StaticResource UiListToStringConverter}}"></TextBlock>
|
<TextBlock FontSize="15" Opacity="0.8" TextWrapping="Wrap" Text="Available Dubs: "></TextBlock>
|
||||||
|
|
||||||
|
<ui:HighlightingTextBlock
|
||||||
|
Items="{Binding HistorySeriesAvailableDubLang}"
|
||||||
|
Series="{Binding .}"
|
||||||
|
Season=""
|
||||||
|
StreamingService="Crunchyroll"
|
||||||
|
CheckDubs="True"
|
||||||
|
FontSize="15"
|
||||||
|
Opacity="0.8"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<StackPanel Grid.Row="4" Orientation="Horizontal" IsVisible="{Binding HistorySeriesAvailableSoftSubs, Converter={StaticResource UiListHasElementsConverter}}">
|
<StackPanel Grid.Row="4" Orientation="Horizontal" VerticalAlignment="Center"
|
||||||
<TextBlock FontSize="15" Opacity="0.8" Text="Available Subs: " />
|
IsVisible="{Binding HistorySeriesAvailableSoftSubs, Converter={StaticResource UiListHasElementsConverter}}">
|
||||||
<TextBlock FontSize="15" Opacity="0.8" TextWrapping="Wrap" Text="{Binding HistorySeriesAvailableSoftSubs, Converter={StaticResource UiListToStringConverter}}"></TextBlock>
|
<TextBlock FontSize="15" Opacity="0.8" TextWrapping="Wrap" Text="Available Subs: "></TextBlock>
|
||||||
|
|
||||||
|
<ui:HighlightingTextBlock
|
||||||
|
Items="{Binding HistorySeriesAvailableSoftSubs}"
|
||||||
|
Series="{Binding .}"
|
||||||
|
Season=""
|
||||||
|
StreamingService="Crunchyroll"
|
||||||
|
CheckDubs="False"
|
||||||
|
FontSize="15"
|
||||||
|
Opacity="0.8"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -462,13 +484,20 @@
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
<Button Command="{Binding FetchData}" Margin="0 0 5 10">Refresh Series</Button>
|
<Button Content="Refresh Series"
|
||||||
|
Margin="0 0 5 10"
|
||||||
|
Command="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).UpdateData}">
|
||||||
|
<Button.CommandParameter>
|
||||||
|
<MultiBinding Converter="{StaticResource UiSeriesSeasonConverter}">
|
||||||
|
<Binding />
|
||||||
|
</MultiBinding>
|
||||||
|
</Button.CommandParameter>
|
||||||
|
</Button>
|
||||||
<ToggleButton x:Name="SeriesEditModeToggle" IsChecked="{Binding EditModeEnabled}" Margin="0 0 5 10">Edit</ToggleButton>
|
<ToggleButton x:Name="SeriesEditModeToggle" IsChecked="{Binding EditModeEnabled}" Margin="0 0 5 10">Edit</ToggleButton>
|
||||||
|
|
||||||
<Button Margin="0 0 5 10" FontStyle="Italic"
|
<Button Margin="0 0 5 10" FontStyle="Italic"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Command="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).OpenFolderDialogAsync}"
|
Command="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).OpenFolderDialogAsync}">
|
||||||
>
|
|
||||||
<Button.CommandParameter>
|
<Button.CommandParameter>
|
||||||
<MultiBinding Converter="{StaticResource UiSeriesSeasonConverter}">
|
<MultiBinding Converter="{StaticResource UiSeriesSeasonConverter}">
|
||||||
<Binding />
|
<Binding />
|
||||||
|
|
@ -483,6 +512,25 @@
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
<ToggleButton Margin="0 0 5 10" FontStyle="Italic"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
IsChecked="{Binding IsInactive}"
|
||||||
|
Command="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).ToggleInactive}">
|
||||||
|
<ToolTip.Tip>
|
||||||
|
<StackPanel Orientation="Vertical">
|
||||||
|
<TextBlock Text="Set Active"
|
||||||
|
IsVisible="{Binding IsInactive}"
|
||||||
|
FontSize="15" />
|
||||||
|
<TextBlock Text="Set Inactive"
|
||||||
|
IsVisible="{Binding !IsInactive}"
|
||||||
|
FontSize="15" />
|
||||||
|
</StackPanel>
|
||||||
|
</ToolTip.Tip>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<controls:SymbolIcon Symbol="CloudOffline" FontSize="18" />
|
||||||
|
</StackPanel>
|
||||||
|
</ToggleButton>
|
||||||
|
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<ToggleButton x:Name="SeriesOverride" Margin="0 0 5 10" FontStyle="Italic"
|
<ToggleButton x:Name="SeriesOverride" Margin="0 0 5 10" FontStyle="Italic"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
|
|
@ -671,17 +719,40 @@
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<StackPanel Orientation="Horizontal"
|
<StackPanel VerticalAlignment="Center">
|
||||||
VerticalAlignment="Center">
|
<ui:EpisodeHighlightTextBlock
|
||||||
<TextBlock Text="E"></TextBlock>
|
Series="{Binding $parent[ScrollViewer].((history:HistorySeries)DataContext)}"
|
||||||
<TextBlock Text="{Binding Episode}"></TextBlock>
|
Season="{Binding $parent[controls:SettingsExpander].((history:HistorySeason)DataContext)}"
|
||||||
<TextBlock Text=" - "></TextBlock>
|
Episode="{Binding .}"
|
||||||
<TextBlock Text="{Binding EpisodeTitle}"></TextBlock>
|
StreamingService="Crunchyroll"
|
||||||
|
MinWidth="50"
|
||||||
|
TextWrapping="NoWrap"
|
||||||
|
TextTrimming="CharacterEllipsis"/>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"
|
||||||
|
IsVisible="{Binding HistoryEpisodeAvailableDubLang, Converter={StaticResource UiListHasElementsConverter}}">
|
||||||
|
<TextBlock FontStyle="Italic"
|
||||||
|
FontSize="12"
|
||||||
|
Opacity="0.8" Text="Dubs: ">
|
||||||
|
</TextBlock>
|
||||||
|
|
||||||
|
<ui:HighlightingTextBlock
|
||||||
|
Items="{Binding HistoryEpisodeAvailableDubLang}"
|
||||||
|
Series="{Binding $parent[ScrollViewer].((history:HistorySeries)DataContext)}"
|
||||||
|
Season="{Binding $parent[controls:SettingsExpander].((history:HistorySeason)DataContext)}"
|
||||||
|
StreamingService="Crunchyroll"
|
||||||
|
CheckDubs="True"
|
||||||
|
FontSize="12"
|
||||||
|
Opacity="0.8"
|
||||||
|
FontStyle="Italic" />
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<StackPanel Grid.Column="1"
|
<StackPanel Grid.Column="1"
|
||||||
Orientation="Horizontal"
|
Orientation="Horizontal"
|
||||||
VerticalAlignment="Center">
|
VerticalAlignment="Center">
|
||||||
|
<TextBlock Text="{Binding ReleaseDateFormated}" VerticalAlignment="Center" FontSize="15" Opacity="0.8" Margin="0 0 20 0"></TextBlock>
|
||||||
|
|
||||||
<StackPanel VerticalAlignment="Center"
|
<StackPanel VerticalAlignment="Center"
|
||||||
Margin="0 0 5 0"
|
Margin="0 0 5 0"
|
||||||
|
|
@ -768,8 +839,13 @@
|
||||||
|
|
||||||
<Button Margin="10 0 0 0" FontStyle="Italic"
|
<Button Margin="10 0 0 0" FontStyle="Italic"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Command="{Binding $parent[ScrollViewer].((history:HistorySeries)DataContext).FetchData}"
|
Command="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).UpdateData}">
|
||||||
CommandParameter="{Binding SeasonId}">
|
<Button.CommandParameter>
|
||||||
|
<MultiBinding Converter="{StaticResource UiSeriesSeasonConverter}">
|
||||||
|
<Binding Path="DataContext" RelativeSource="{RelativeSource AncestorType=ScrollViewer}" />
|
||||||
|
<Binding Path="SeasonId" />
|
||||||
|
</MultiBinding>
|
||||||
|
</Button.CommandParameter>
|
||||||
<ToolTip.Tip>
|
<ToolTip.Tip>
|
||||||
<TextBlock Text="Refresh Season" FontSize="15" />
|
<TextBlock Text="Refresh Season" FontSize="15" />
|
||||||
</ToolTip.Tip>
|
</ToolTip.Tip>
|
||||||
|
|
@ -825,7 +901,7 @@
|
||||||
Command="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).ToggleDownloadedMark}">
|
Command="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).ToggleDownloadedMark}">
|
||||||
<Button.CommandParameter>
|
<Button.CommandParameter>
|
||||||
<MultiBinding Converter="{StaticResource UiSeriesSeasonConverter}">
|
<MultiBinding Converter="{StaticResource UiSeriesSeasonConverter}">
|
||||||
<Binding Path="DataContext" ElementName="TableViewScrollViewer" />
|
<Binding Path="DataContext" RelativeSource="{RelativeSource AncestorType=ScrollViewer}" />
|
||||||
<Binding />
|
<Binding />
|
||||||
</MultiBinding>
|
</MultiBinding>
|
||||||
</Button.CommandParameter>
|
</Button.CommandParameter>
|
||||||
|
|
@ -838,8 +914,7 @@
|
||||||
|
|
||||||
<Button Margin="10 0 0 0" FontStyle="Italic"
|
<Button Margin="10 0 0 0" FontStyle="Italic"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Command="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).OpenFolderDialogAsync}"
|
Command="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).OpenFolderDialogAsync}">
|
||||||
>
|
|
||||||
<Button.CommandParameter>
|
<Button.CommandParameter>
|
||||||
<MultiBinding Converter="{StaticResource UiSeriesSeasonConverter}">
|
<MultiBinding Converter="{StaticResource UiSeriesSeasonConverter}">
|
||||||
<Binding Path="DataContext" RelativeSource="{RelativeSource AncestorType=ScrollViewer}" />
|
<Binding Path="DataContext" RelativeSource="{RelativeSource AncestorType=ScrollViewer}" />
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ namespace CRD.Views;
|
||||||
public partial class MainWindow : AppWindow{
|
public partial class MainWindow : AppWindow{
|
||||||
private Stack<object> navigationStack = new Stack<object>();
|
private Stack<object> navigationStack = new Stack<object>();
|
||||||
|
|
||||||
|
private static HashSet<string> activeErrors = new HashSet<string>();
|
||||||
|
|
||||||
#region Singelton
|
#region Singelton
|
||||||
|
|
||||||
|
|
@ -78,30 +79,53 @@ public partial class MainWindow : AppWindow{
|
||||||
MessageBus.Current.Listen<NavigationMessage>()
|
MessageBus.Current.Listen<NavigationMessage>()
|
||||||
.Subscribe(message => {
|
.Subscribe(message => {
|
||||||
if (message.Refresh){
|
if (message.Refresh){
|
||||||
|
if (navigationStack.Count > 0){
|
||||||
navigationStack.Pop();
|
navigationStack.Pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
var viewModel = Activator.CreateInstance(message.ViewModelType);
|
var viewModel = Activator.CreateInstance(message.ViewModelType);
|
||||||
|
|
||||||
|
|
||||||
navigationStack.Push(viewModel);
|
navigationStack.Push(viewModel);
|
||||||
nv.Content = viewModel;
|
nv.Content = viewModel;
|
||||||
} else if (!message.Back && message.ViewModelType != null){
|
} catch (Exception ex){
|
||||||
|
Console.Error.WriteLine($"Failed to create or push viewModel: {ex.Message}");
|
||||||
|
}
|
||||||
|
} else if (message is{ Back: false, ViewModelType: not null }){
|
||||||
|
try{
|
||||||
var viewModel = Activator.CreateInstance(message.ViewModelType);
|
var viewModel = Activator.CreateInstance(message.ViewModelType);
|
||||||
|
|
||||||
|
|
||||||
navigationStack.Push(viewModel);
|
navigationStack.Push(viewModel);
|
||||||
|
nv.Content = viewModel;
|
||||||
|
} catch (Exception ex){
|
||||||
|
Console.Error.WriteLine($"Failed to create or push viewModel: {ex.Message}");
|
||||||
|
}
|
||||||
|
} else{
|
||||||
|
if (navigationStack.Count > 0){
|
||||||
|
navigationStack.Pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (navigationStack.Count > 0){
|
||||||
|
var viewModel = navigationStack.Peek();
|
||||||
|
if (viewModel is HistoryPageViewModel historyView){
|
||||||
|
historyView.ApplyFilter();
|
||||||
|
}
|
||||||
|
|
||||||
nv.Content = viewModel;
|
nv.Content = viewModel;
|
||||||
} else{
|
} else{
|
||||||
navigationStack.Pop();
|
Console.Error.WriteLine("Navigation stack is empty. Cannot peek.");
|
||||||
var viewModel = navigationStack.Peek();
|
}
|
||||||
nv.Content = viewModel;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
MessageBus.Current.Listen<ToastMessage>()
|
MessageBus.Current.Listen<ToastMessage>()
|
||||||
.Subscribe(message => ShowToast(message.Message, message.Type, message.Seconds));
|
.Subscribe(message => ShowToast(message.Message ?? string.Empty, message.Type, message.Seconds));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void ShowError(string message,bool githubWikiButton = false){
|
public async void ShowError(string message, bool githubWikiButton = false){
|
||||||
|
if (activeErrors.Contains(message))
|
||||||
|
return;
|
||||||
|
|
||||||
|
activeErrors.Add(message);
|
||||||
|
|
||||||
var dialog = new ContentDialog(){
|
var dialog = new ContentDialog(){
|
||||||
Title = "Error",
|
Title = "Error",
|
||||||
Content = message,
|
Content = message,
|
||||||
|
|
@ -117,11 +141,14 @@ public partial class MainWindow : AppWindow{
|
||||||
if (result == ContentDialogResult.Primary){
|
if (result == ContentDialogResult.Primary){
|
||||||
Helpers.OpenUrl($"https://github.com/Crunchy-DL/Crunchy-Downloader/wiki");
|
Helpers.OpenUrl($"https://github.com/Crunchy-DL/Crunchy-Downloader/wiki");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
activeErrors.Remove(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void ShowToast(string message, ToastType type, int durationInSeconds = 5){
|
public void ShowToast(string message, ToastType type, int durationInSeconds = 5){
|
||||||
this.FindControl<ToastNotification>("Toast").Show(message, type, durationInSeconds);
|
var toastControl = this.FindControl<ToastNotification>("Toast");
|
||||||
|
toastControl?.Show(message, type, durationInSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -193,7 +220,7 @@ public partial class MainWindow : AppWindow{
|
||||||
_restorePosition = new PixelPoint(settings.PosX, settings.PosY);
|
_restorePosition = new PixelPoint(settings.PosX, settings.PosY);
|
||||||
|
|
||||||
// Ensure the window is on the correct screen before maximizing
|
// Ensure the window is on the correct screen before maximizing
|
||||||
Position = new PixelPoint(settings.PosX, settings.PosY );
|
Position = new PixelPoint(settings.PosX, settings.PosY);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.IsMaximized){
|
if (settings.IsMaximized){
|
||||||
|
|
|
||||||
|
|
@ -57,9 +57,42 @@
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<TextBlock Grid.Row="0" FontSize="45" Text="{Binding SelectedSeries.SeriesTitle}" TextTrimming="CharacterEllipsis"></TextBlock>
|
<TextBlock Grid.Row="0" FontSize="45" Text="{Binding SelectedSeries.SeriesTitle}" TextTrimming="CharacterEllipsis"></TextBlock>
|
||||||
<TextBlock Grid.Row="1" FontSize="20" TextWrapping="Wrap" Text="{Binding SelectedSeries.SeriesDescription}"></TextBlock>
|
<TextBlock Grid.Row="1" FontSize="20" TextWrapping="Wrap" Text="{Binding SelectedSeries.SeriesDescription}" MinWidth="250"></TextBlock>
|
||||||
<TextBlock Grid.Row="3" IsVisible="{Binding SelectedSeries.HistorySeriesAvailableDubLang, Converter={StaticResource UiListHasElementsConverter}}" FontSize="15" Opacity="0.8" TextWrapping="Wrap" Text="{Binding AvailableDubs}"></TextBlock>
|
|
||||||
<TextBlock Grid.Row="4" IsVisible="{Binding SelectedSeries.HistorySeriesAvailableSoftSubs, Converter={StaticResource UiListHasElementsConverter}}" FontSize="15" Opacity="0.8" TextWrapping="Wrap" Text="{Binding AvailableSubs}"></TextBlock>
|
<StackPanel Grid.Row="3" Orientation="Horizontal" VerticalAlignment="Center"
|
||||||
|
IsVisible="{Binding SelectedSeries.HistorySeriesAvailableDubLang, Converter={StaticResource UiListHasElementsConverter}}">
|
||||||
|
<TextBlock FontSize="15" Opacity="0.8" TextWrapping="Wrap" Text="Available Dubs: "></TextBlock>
|
||||||
|
|
||||||
|
<ui:HighlightingTextBlock
|
||||||
|
Items="{Binding SelectedSeries.HistorySeriesAvailableDubLang}"
|
||||||
|
Series="{Binding SelectedSeries}"
|
||||||
|
Season=""
|
||||||
|
StreamingService="Crunchyroll"
|
||||||
|
CheckDubs="True"
|
||||||
|
FontSize="15"
|
||||||
|
Opacity="0.8"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="4" Orientation="Horizontal" VerticalAlignment="Center"
|
||||||
|
IsVisible="{Binding SelectedSeries.HistorySeriesAvailableSoftSubs, Converter={StaticResource UiListHasElementsConverter}}">
|
||||||
|
<TextBlock FontSize="15" Opacity="0.8" TextWrapping="Wrap" Text="Available Subs: "></TextBlock>
|
||||||
|
|
||||||
|
<ui:HighlightingTextBlock
|
||||||
|
Items="{Binding SelectedSeries.HistorySeriesAvailableSoftSubs}"
|
||||||
|
Series="{Binding SelectedSeries}"
|
||||||
|
Season=""
|
||||||
|
StreamingService="Crunchyroll"
|
||||||
|
CheckDubs="False"
|
||||||
|
FontSize="15"
|
||||||
|
Opacity="0.8"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<StackPanel Grid.Row="5" Orientation="Vertical">
|
<StackPanel Grid.Row="5" Orientation="Vertical">
|
||||||
|
|
||||||
<StackPanel Orientation="Horizontal" Margin="0 10 10 10">
|
<StackPanel Orientation="Horizontal" Margin="0 10 10 10">
|
||||||
|
|
@ -117,6 +150,24 @@
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
<ToggleButton Margin="0 0 5 10" FontStyle="Italic"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
IsChecked="{Binding SelectedSeries.IsInactive}" Command="{Binding ToggleInactive}">
|
||||||
|
<ToolTip.Tip>
|
||||||
|
<StackPanel Orientation="Vertical">
|
||||||
|
<TextBlock Text="Set Active"
|
||||||
|
IsVisible="{Binding SelectedSeries.IsInactive}"
|
||||||
|
FontSize="15" />
|
||||||
|
<TextBlock Text="Set Inactive"
|
||||||
|
IsVisible="{Binding !SelectedSeries.IsInactive}"
|
||||||
|
FontSize="15" />
|
||||||
|
</StackPanel>
|
||||||
|
</ToolTip.Tip>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<controls:SymbolIcon Symbol="CloudOffline" FontSize="18" />
|
||||||
|
</StackPanel>
|
||||||
|
</ToggleButton>
|
||||||
|
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<ToggleButton x:Name="SeriesOverride" Margin="0 0 5 10" FontStyle="Italic"
|
<ToggleButton x:Name="SeriesOverride" Margin="0 0 5 10" FontStyle="Italic"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
|
|
@ -184,7 +235,7 @@
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0 0 0 10" >
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0 0 0 10">
|
||||||
<TextBlock Text="Dub" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0 0 10 0"></TextBlock>
|
<TextBlock Text="Dub" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0 0 10 0"></TextBlock>
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<ToggleButton x:Name="DropdownButtonDub" Width="210" HorizontalContentAlignment="Stretch">
|
<ToggleButton x:Name="DropdownButtonDub" Width="210" HorizontalContentAlignment="Stretch">
|
||||||
|
|
@ -310,26 +361,53 @@
|
||||||
<Grid VerticalAlignment="Center">
|
<Grid VerticalAlignment="Center">
|
||||||
|
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<StackPanel VerticalAlignment="Center">
|
<controls:SymbolIcon Grid.Column="0" IsVisible="{Binding !IsEpisodeAvailableOnStreamingService}"
|
||||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
Margin="0 0 5 0 "
|
||||||
<TextBlock Text="E"></TextBlock>
|
Symbol="AlertOn"
|
||||||
<TextBlock Text="{Binding Episode}"></TextBlock>
|
FontSize="18"
|
||||||
<TextBlock Text=" - "></TextBlock>
|
HorizontalAlignment="Center"
|
||||||
<TextBlock Text="{Binding EpisodeTitle}"></TextBlock>
|
VerticalAlignment="Center">
|
||||||
</StackPanel>
|
<ToolTip.Tip>
|
||||||
|
<TextBlock Text="Episode unavailable — it might not be available on the streaming service" FontSize="15" />
|
||||||
|
</ToolTip.Tip>
|
||||||
|
|
||||||
|
</controls:SymbolIcon>
|
||||||
|
|
||||||
|
<StackPanel Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Left">
|
||||||
|
|
||||||
|
<ui:EpisodeHighlightTextBlock
|
||||||
|
Series="{Binding $parent[UserControl].((vm:SeriesPageViewModel)DataContext).SelectedSeries}"
|
||||||
|
Season="{Binding $parent[controls:SettingsExpander].((history:HistorySeason)DataContext)}"
|
||||||
|
Episode="{Binding }"
|
||||||
|
StreamingService="Crunchyroll"
|
||||||
|
MinWidth="50"
|
||||||
|
TextWrapping="NoWrap"
|
||||||
|
TextTrimming="CharacterEllipsis" />
|
||||||
|
|
||||||
|
|
||||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"
|
||||||
IsVisible="{Binding HistoryEpisodeAvailableDubLang, Converter={StaticResource UiListHasElementsConverter}}">
|
IsVisible="{Binding HistoryEpisodeAvailableDubLang, Converter={StaticResource UiListHasElementsConverter}}">
|
||||||
<TextBlock FontStyle="Italic"
|
<TextBlock FontStyle="Italic"
|
||||||
FontSize="12"
|
FontSize="12"
|
||||||
Opacity="0.8" Text="Dubs: ">
|
Opacity="0.8" Text="Dubs: ">
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
<TextBlock FontStyle="Italic"
|
|
||||||
|
<ui:HighlightingTextBlock
|
||||||
|
Items="{Binding HistoryEpisodeAvailableDubLang}"
|
||||||
|
Series="{Binding $parent[UserControl].((vm:SeriesPageViewModel)DataContext).SelectedSeries}"
|
||||||
|
Season="{Binding $parent[controls:SettingsExpander].((history:HistorySeason)DataContext)}"
|
||||||
|
StreamingService="Crunchyroll"
|
||||||
|
CheckDubs="True"
|
||||||
FontSize="12"
|
FontSize="12"
|
||||||
Opacity="0.8" Text="{Binding HistoryEpisodeAvailableDubLang, Converter={StaticResource UiListToStringConverter}}" />
|
Opacity="0.8"
|
||||||
|
FontStyle="Italic" />
|
||||||
|
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<!-- <StackPanel Orientation="Horizontal" VerticalAlignment="Center"> -->
|
<!-- <StackPanel Orientation="Horizontal" VerticalAlignment="Center"> -->
|
||||||
<!-- <TextBlock FontStyle="Italic" -->
|
<!-- <TextBlock FontStyle="Italic" -->
|
||||||
|
|
@ -343,7 +421,9 @@
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
|
||||||
<StackPanel Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center">
|
<StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
|
|
||||||
|
<TextBlock Text="{Binding ReleaseDateFormated}" VerticalAlignment="Center" FontSize="15" Opacity="0.8" Margin="0 0 20 0"></TextBlock>
|
||||||
|
|
||||||
<StackPanel VerticalAlignment="Center" Margin="0 0 5 0" Orientation="Horizontal"
|
<StackPanel VerticalAlignment="Center" Margin="0 0 5 0" Orientation="Horizontal"
|
||||||
IsVisible="{Binding $parent[UserControl].((vm:SeriesPageViewModel)DataContext).SonarrAvailable}">
|
IsVisible="{Binding $parent[UserControl].((vm:SeriesPageViewModel)DataContext).SonarrAvailable}">
|
||||||
|
|
|
||||||
|
|
@ -14,30 +14,36 @@ public partial class ToastNotification : UserControl{
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DispatcherTimer? currentTimer;
|
||||||
|
|
||||||
public void Show(string message, ToastType type, int durationInSeconds){
|
public void Show(string message, ToastType type, int durationInSeconds){
|
||||||
this.FindControl<TextBlock>("MessageText").Text = message;
|
var text = this.FindControl<TextBlock>("MessageText");
|
||||||
|
if (text != null) text.Text = message;
|
||||||
SetStyle(type);
|
SetStyle(type);
|
||||||
DispatcherTimer timer = new DispatcherTimer{ Interval = TimeSpan.FromSeconds(durationInSeconds) };
|
|
||||||
timer.Tick += (sender, args) => {
|
currentTimer?.Stop();
|
||||||
timer.Stop();
|
|
||||||
this.IsVisible = false;
|
currentTimer = new DispatcherTimer{ Interval = TimeSpan.FromSeconds(durationInSeconds) };
|
||||||
|
currentTimer.Tick += (sender, args) => {
|
||||||
|
currentTimer?.Stop();
|
||||||
|
IsVisible = false;
|
||||||
};
|
};
|
||||||
timer.Start();
|
currentTimer.Start();
|
||||||
this.IsVisible = true;
|
IsVisible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetStyle(ToastType type){
|
private void SetStyle(ToastType type){
|
||||||
var border = this.FindControl<Border>("MessageBorder");
|
var border = this.FindControl<Border>("MessageBorder");
|
||||||
border.Classes.Clear(); // Clear previous styles
|
border?.Classes.Clear(); // Clear previous styles
|
||||||
switch (type){
|
switch (type){
|
||||||
case ToastType.Information:
|
case ToastType.Information:
|
||||||
border.Classes.Add("info");
|
border?.Classes.Add("info");
|
||||||
break;
|
break;
|
||||||
case ToastType.Error:
|
case ToastType.Error:
|
||||||
border.Classes.Add("error");
|
border?.Classes.Add("error");
|
||||||
break;
|
break;
|
||||||
case ToastType.Warning:
|
case ToastType.Warning:
|
||||||
border.Classes.Add("warning");
|
border?.Classes.Add("warning");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,11 @@
|
||||||
<Border BorderBrush="#4a4a4a" Background="{DynamicResource ControlAltFillColorQuarternary}" BorderThickness="1"
|
<Border BorderBrush="#4a4a4a" Background="{DynamicResource ControlAltFillColorQuarternary}" BorderThickness="1"
|
||||||
CornerRadius="10" Margin="2">
|
CornerRadius="10" Margin="2">
|
||||||
<StackPanel Orientation="Horizontal" Margin="5">
|
<StackPanel Orientation="Horizontal" Margin="5">
|
||||||
<TextBlock Text="{Binding stringValue}" Margin="5,0" />
|
<TextBlock Text="{Binding stringValue}" Margin="5,0" TextTrimming="CharacterEllipsis" MaxWidth="300" TextWrapping="NoWrap">
|
||||||
|
<ToolTip.Tip>
|
||||||
|
<TextBlock Text="{Binding stringValue}" FontSize="15" />
|
||||||
|
</ToolTip.Tip>
|
||||||
|
</TextBlock>
|
||||||
<Button Content="X" FontSize="8" VerticalAlignment="Center" HorizontalAlignment="Center"
|
<Button Content="X" FontSize="8" VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||||
HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
|
HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
|
||||||
Width="15" Height="15" Padding="0"
|
Width="15" Height="15" Padding="0"
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,30 @@
|
||||||
</controls:SettingsExpanderItem.Footer>
|
</controls:SettingsExpanderItem.Footer>
|
||||||
</controls:SettingsExpanderItem>
|
</controls:SettingsExpanderItem>
|
||||||
|
|
||||||
|
<controls:SettingsExpanderItem Content="Download Retry"
|
||||||
|
Description="Sett the number of retry attempts and the delay between each retry">
|
||||||
|
<controls:SettingsExpanderItem.Footer>
|
||||||
|
<StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock Width="100" Text="Retry Attempts" Margin="5 0" VerticalAlignment="Center" HorizontalAlignment="Center"></TextBlock>
|
||||||
|
<controls:NumberBox Minimum="1" Maximum="10"
|
||||||
|
Value="{Binding RetryAttempts}"
|
||||||
|
SpinButtonPlacementMode="Hidden"
|
||||||
|
HorizontalAlignment="Stretch" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0 5">
|
||||||
|
<TextBlock Width="100" Text="Retry Delay (s)" Margin="5 0" VerticalAlignment="Center" HorizontalAlignment="Center"></TextBlock>
|
||||||
|
<controls:NumberBox Minimum="1" Maximum="30"
|
||||||
|
Value="{Binding RetryDelay}"
|
||||||
|
SpinButtonPlacementMode="Hidden"
|
||||||
|
HorizontalAlignment="Stretch" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
|
||||||
|
</controls:SettingsExpanderItem.Footer>
|
||||||
|
</controls:SettingsExpanderItem>
|
||||||
|
|
||||||
<controls:SettingsExpanderItem Content="Use Temp Download Folder">
|
<controls:SettingsExpanderItem Content="Use Temp Download Folder">
|
||||||
<controls:SettingsExpanderItem.Footer>
|
<controls:SettingsExpanderItem.Footer>
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue