From 26d54ceb75fc3346e252741a4fd4a868dacdb77a Mon Sep 17 00:00:00 2001 From: Elwador <75888166+Elwador@users.noreply.github.com> Date: Wed, 15 Jan 2025 03:59:57 +0100 Subject: [PATCH] Add - Added **Shaka Packager support**, which can be used as an alternative to MP4Decrypt Add - Added a **toggle for Downloaded Mark** in the history Add - Proxy username/password and socks Chg - Changed the folder where the **auto-update** is downloaded to Chg - Changed **Encoding presets FPS** to default to 23.976 FPS Chg - Changed the **FPS input field** in Encoding presets to a text field, allowing formats like `24000/1001` for precise 23.976 FPS Fix - Fixed **Encoding presets** not including all available dubs and subs after encoding Fix - Fixed **Encoding presets additional parameters** with spaces, which now work correctly without needing escaped quotes (`\"`) Fix - Fixed **crash ** when adding episode to queue from history --- .../Crunchyroll/CrunchyrollManager.cs | 104 +++++++----- .../Views/CrunchyrollSettingsView.axaml | 2 +- CRD/Downloader/History.cs | 2 +- CRD/Downloader/ProgramManager.cs | 5 +- CRD/Utils/Ffmpeg Encoding/FfmpegEncoding.cs | 36 ++-- CRD/Utils/Files/CfgManager.cs | 1 + CRD/Utils/Helpers.cs | 17 +- CRD/Utils/Http/HttpClientReq.cs | 17 +- .../Structs/Crunchyroll/CrDownloadOptions.cs | 9 + CRD/Utils/Structs/History/HistorySeason.cs | 9 +- CRD/Utils/Structs/History/HistorySeries.cs | 10 +- CRD/Utils/Updater/Updater.cs | 17 +- CRD/ViewModels/HistoryPageViewModel.cs | 4 + CRD/ViewModels/MainWindowViewModel.cs | 1 - CRD/ViewModels/SeriesPageViewModel.cs | 18 +- .../ContentDialogEncodingPresetViewModel.cs | 10 +- .../Utils/GeneralSettingsViewModel.cs | 41 ++++- CRD/Views/HistoryPageView.axaml | 3 +- CRD/Views/MainWindow.axaml | 1 + CRD/Views/MainWindow.axaml.cs | 3 +- CRD/Views/SeriesPageView.axaml | 11 +- .../ContentDialogEncodingPresetView.axaml | 156 +++++++++--------- CRD/Views/Utils/GeneralSettingsView.axaml | 23 +++ 23 files changed, 329 insertions(+), 171 deletions(-) diff --git a/CRD/Downloader/Crunchyroll/CrunchyrollManager.cs b/CRD/Downloader/Crunchyroll/CrunchyrollManager.cs index 2c75036..b0fb9e6 100644 --- a/CRD/Downloader/Crunchyroll/CrunchyrollManager.cs +++ b/CRD/Downloader/Crunchyroll/CrunchyrollManager.cs @@ -164,41 +164,12 @@ public class CrunchyrollManager{ CfgManager.DisableLogMode(); } - if (CfgManager.CheckIfFileExists(CfgManager.PathCrToken)){ - Token = CfgManager.DeserializeFromFile(CfgManager.PathCrToken); - await CrAuth.LoginWithToken(); - } else{ - await CrAuth.AuthAnonymous(); - } - - if (CrunOptions.History){ - if (File.Exists(CfgManager.PathCrHistory)){ - var decompressedJson = CfgManager.DecompressJsonFile(CfgManager.PathCrHistory); - if (!string.IsNullOrEmpty(decompressedJson)){ - HistoryList = Helpers.Deserialize>(decompressedJson, CrunchyrollManager.Instance.SettingsJsonSerializerSettings) ?? new ObservableCollection(); - - foreach (var historySeries in HistoryList){ - historySeries.Init(); - foreach (var historySeriesSeason in historySeries.Seasons){ - historySeriesSeason.Init(); - } - } - } else{ - HistoryList =[]; - } - } - - await SonarrClient.Instance.RefreshSonarr(); - } - var jsonFiles = Directory.Exists(CfgManager.PathENCODING_PRESETS_DIR) ? Directory.GetFiles(CfgManager.PathENCODING_PRESETS_DIR, "*.json") :[]; foreach (var file in jsonFiles){ try{ - // Read the content of the JSON file var jsonContent = File.ReadAllText(file); - // Deserialize the JSON content into a MyClass object var obj = Helpers.Deserialize(jsonContent, null); if (obj != null){ @@ -210,6 +181,48 @@ public class CrunchyrollManager{ Console.Error.WriteLine($"Failed to deserialize file {file}: {ex.Message}"); } } + + if (CfgManager.CheckIfFileExists(CfgManager.PathCrToken)){ + Token = CfgManager.DeserializeFromFile(CfgManager.PathCrToken); + await CrAuth.LoginWithToken(); + } else{ + await CrAuth.AuthAnonymous(); + } + + if (CrunOptions.History){ + if (File.Exists(CfgManager.PathCrHistory)){ + var decompressedJson = CfgManager.DecompressJsonFile(CfgManager.PathCrHistory); + + if (!string.IsNullOrEmpty(decompressedJson)){ + var historyList = Helpers.Deserialize>( + decompressedJson, + SettingsJsonSerializerSettings + ); + + if (historyList != null){ + + HistoryList = historyList; + + Parallel.ForEach(historyList, historySeries => { + historySeries.Init(); + + foreach (var historySeriesSeason in historySeries.Seasons){ + historySeriesSeason.Init(); + } + }); + } else{ + HistoryList =[]; + } + } else{ + HistoryList =[]; + } + } else{ + HistoryList =[]; + } + + + await SonarrClient.Instance.RefreshSonarr(); + } } @@ -678,14 +691,15 @@ public class CrunchyrollManager{ }; } - if (!File.Exists(CfgManager.PathMP4Decrypt)){ - Console.Error.WriteLine("mp4decrypt not found"); - MainWindow.Instance.ShowError($"Can't find mp4decrypt in lib folder at: {CfgManager.PathMP4Decrypt}"); + if (!File.Exists(CfgManager.PathMP4Decrypt) && !File.Exists(CfgManager.PathShakaPackager)){ + Console.Error.WriteLine("mp4decrypt or shaka-packager not found"); + MainWindow.Instance.ShowError($"Either mp4decrypt (expected in lib folder at: {CfgManager.PathMP4Decrypt}) " + + $"or shaka-packager (expected in lib folder at: {CfgManager.PathShakaPackager}) must be available."); return new DownloadResponse{ Data = new List(), Error = true, FileName = "./unknown", - ErrorText = "Missing mp4decrypt" + ErrorText = "Requires either mp4decrypt or shaka-packager" }; } @@ -1308,14 +1322,27 @@ public class CrunchyrollManager{ } - if (Path.Exists(CfgManager.PathMP4Decrypt)){ + if (Path.Exists(CfgManager.PathMP4Decrypt) || Path.Exists(CfgManager.PathShakaPackager)){ var keyId = BitConverter.ToString(encryptionKeys[0].KeyID).Replace("-", "").ToLower(); var key = BitConverter.ToString(encryptionKeys[0].Bytes).Replace("-", "").ToLower(); + + //mp4decrypt var commandBase = $"--show-progress --key {keyId}:{key}"; var tempTsFileName = Path.GetFileName(tempTsFile); var tempTsFileWorkDir = Path.GetDirectoryName(tempTsFile) ?? CfgManager.PathVIDEOS_DIR; var commandVideo = commandBase + $" \"{tempTsFileName}.video.enc.m4s\" \"{tempTsFileName}.video.m4s\""; var commandAudio = commandBase + $" \"{tempTsFileName}.audio.enc.m4s\" \"{tempTsFileName}.audio.m4s\""; + + bool shaka = Path.Exists(CfgManager.PathShakaPackager); + if (shaka){ + commandBase = " --enable_raw_key_decryption " + + string.Join(" ", + encryptionKeys.Select(kb => + $"--keys key_id={BitConverter.ToString(kb.KeyID).Replace("-", "").ToLower()}:key={BitConverter.ToString(kb.Bytes).Replace("-", "").ToLower()}")); + commandVideo = $"input=\"{tempTsFileName}.video.enc.m4s\",stream=video,output=\"{tempTsFileName}.video.m4s\"" + commandBase; + commandAudio = $"input=\"{tempTsFileName}.audio.enc.m4s\",stream=audio,output=\"{tempTsFileName}.audio.m4s\"" + commandBase; + } + if (videoDownloaded){ Console.WriteLine("Started decrypting video"); data.DownloadProgress = new DownloadProgress(){ @@ -1326,7 +1353,8 @@ public class CrunchyrollManager{ Doing = "Decrypting video" }; QueueManager.Instance.Queue.Refresh(); - var decryptVideo = await Helpers.ExecuteCommandAsyncWorkDir("mp4decrypt", CfgManager.PathMP4Decrypt, commandVideo, tempTsFileWorkDir); + var decryptVideo = await Helpers.ExecuteCommandAsyncWorkDir(shaka ? "shaka-packager" : "mp4decrypt", shaka ? CfgManager.PathShakaPackager : CfgManager.PathMP4Decrypt, + commandVideo, tempTsFileWorkDir); if (!decryptVideo.IsOk){ Console.Error.WriteLine($"Decryption failed with exit code {decryptVideo.ErrorCode}"); @@ -1394,7 +1422,8 @@ public class CrunchyrollManager{ Doing = "Decrypting audio" }; QueueManager.Instance.Queue.Refresh(); - var decryptAudio = await Helpers.ExecuteCommandAsyncWorkDir("mp4decrypt", CfgManager.PathMP4Decrypt, commandAudio, tempTsFileWorkDir); + var decryptAudio = await Helpers.ExecuteCommandAsyncWorkDir(shaka ? "shaka-packager" : "mp4decrypt", shaka ? CfgManager.PathShakaPackager : CfgManager.PathMP4Decrypt, + commandAudio, tempTsFileWorkDir); if (!decryptAudio.IsOk){ Console.Error.WriteLine($"Decryption failed with exit code {decryptAudio.ErrorCode}"); @@ -1481,10 +1510,9 @@ public class CrunchyrollManager{ } } } else if (options is{ Novids: true, Noaudio: true }){ - variables.Add(new Variable("height", 360, false)); variables.Add(new Variable("width", 640, false)); - + fileName = Path.Combine(FileNameManager.ParseFileName(options.FileName, variables, options.Numbers, options.Override).ToArray()); } diff --git a/CRD/Downloader/Crunchyroll/Views/CrunchyrollSettingsView.axaml b/CRD/Downloader/Crunchyroll/Views/CrunchyrollSettingsView.axaml index 49a79eb..4fe04b5 100644 --- a/CRD/Downloader/Crunchyroll/Views/CrunchyrollSettingsView.axaml +++ b/CRD/Downloader/Crunchyroll/Views/CrunchyrollSettingsView.axaml @@ -417,7 +417,7 @@ - + diff --git a/CRD/Downloader/History.cs b/CRD/Downloader/History.cs index bebf800..33a1f21 100644 --- a/CRD/Downloader/History.cs +++ b/CRD/Downloader/History.cs @@ -678,7 +678,7 @@ public class History(){ } }); - CfgManager.UpdateHistoryFile(); + } } diff --git a/CRD/Downloader/ProgramManager.cs b/CRD/Downloader/ProgramManager.cs index 56149ed..73ebbfb 100644 --- a/CRD/Downloader/ProgramManager.cs +++ b/CRD/Downloader/ProgramManager.cs @@ -52,6 +52,9 @@ public partial class ProgramManager : ObservableObject{ [ObservableProperty] private bool _finishedLoading = false; + + [ObservableProperty] + private bool _navigationLock = false; #endregion @@ -115,7 +118,7 @@ public partial class ProgramManager : ObservableObject{ } - private async void Init(){ + private async Task Init(){ CrunchyrollManager.Instance.InitOptions(); UpdateAvailable = await Updater.Instance.CheckForUpdatesAsync(); diff --git a/CRD/Utils/Ffmpeg Encoding/FfmpegEncoding.cs b/CRD/Utils/Ffmpeg Encoding/FfmpegEncoding.cs index ced2375..e7b0a50 100644 --- a/CRD/Utils/Ffmpeg Encoding/FfmpegEncoding.cs +++ b/CRD/Utils/Ffmpeg Encoding/FfmpegEncoding.cs @@ -7,25 +7,25 @@ namespace CRD.Utils.Ffmpeg_Encoding; public class FfmpegEncoding{ public static readonly List presets = new List{ // AV1 Software - new(){PresetName = "AV1 1080p24",Codec = "libaom-av1", Resolution = "1920:1080", FrameRate = "24", Crf = 30 } , - new(){PresetName = "AV1 720p24", Codec = "libaom-av1", Resolution = "1280:720", FrameRate = "24", Crf = 30 } , - new(){PresetName = "AV1 480p24", Codec = "libaom-av1", Resolution = "854:480", FrameRate = "24", Crf = 30 } , - new(){PresetName = "AV1 360p24", Codec = "libaom-av1", Resolution = "640:360", FrameRate = "24", Crf = 30 } , - new(){PresetName = "AV1 240p24", Codec = "libaom-av1", Resolution = "426:240", FrameRate = "24", Crf = 30 } , + new(){ PresetName = "AV1 1080p24", Codec = "libaom-av1", Resolution = "1920:1080", FrameRate = "24000/1001", Crf = 30, AdditionalParameters ={ "-map 0" } }, + new(){ PresetName = "AV1 720p24", Codec = "libaom-av1", Resolution = "1280:720", FrameRate = "24000/1001", Crf = 30, AdditionalParameters ={ "-map 0" } }, + new(){ PresetName = "AV1 480p24", Codec = "libaom-av1", Resolution = "854:480", FrameRate = "24000/1001", Crf = 30, AdditionalParameters ={ "-map 0" } }, + new(){ PresetName = "AV1 360p24", Codec = "libaom-av1", Resolution = "640:360", FrameRate = "24000/1001", Crf = 30, AdditionalParameters ={ "-map 0" } }, + new(){ PresetName = "AV1 240p24", Codec = "libaom-av1", Resolution = "426:240", FrameRate = "24000/1001", Crf = 30, AdditionalParameters ={ "-map 0" } }, // H.265 Software - new(){PresetName = "H.265 1080p24", Codec = "libx265", Resolution = "1920:1080", FrameRate = "24", Crf = 28 } , - new(){PresetName = "H.265 720p24", Codec = "libx265", Resolution = "1280:720", FrameRate = "24", Crf = 28 } , - new(){PresetName = "H.265 480p24", Codec = "libx265", Resolution = "854:480", FrameRate = "24", Crf = 28 } , - new(){PresetName = "H.265 360p24", Codec = "libx265", Resolution = "640:360", FrameRate = "24", Crf = 28 } , - new(){PresetName = "H.265 240p24", Codec = "libx265", Resolution = "426:240", FrameRate = "24", Crf = 28 } , + new(){ PresetName = "H.265 1080p24", Codec = "libx265", Resolution = "1920:1080", FrameRate = "24000/1001", Crf = 28, AdditionalParameters ={ "-map 0" } }, + new(){ PresetName = "H.265 720p24", Codec = "libx265", Resolution = "1280:720", FrameRate = "24000/1001", Crf = 28, AdditionalParameters ={ "-map 0" } }, + new(){ PresetName = "H.265 480p24", Codec = "libx265", Resolution = "854:480", FrameRate = "24000/1001", Crf = 28, AdditionalParameters ={ "-map 0" } }, + new(){ PresetName = "H.265 360p24", Codec = "libx265", Resolution = "640:360", FrameRate = "24000/1001", Crf = 28, AdditionalParameters ={ "-map 0" } }, + new(){ PresetName = "H.265 240p24", Codec = "libx265", Resolution = "426:240", FrameRate = "24000/1001", Crf = 28, AdditionalParameters ={ "-map 0" } }, // H.264 Software - new(){ PresetName = "H.264 1080p24",Codec = "libx264", Resolution = "1920:1080", FrameRate = "24", Crf = 23 } , - new(){PresetName = "H.264 720p24", Codec = "libx264", Resolution = "1280:720", FrameRate = "24", Crf = 23 } , - new(){PresetName = "H.264 480p24", Codec = "libx264", Resolution = "854:480", FrameRate = "24", Crf = 23 }, - new(){PresetName = "H.264 360p24", Codec = "libx264", Resolution = "640:360", FrameRate = "24", Crf = 23 } , - new(){PresetName = "H.264 240p24", Codec = "libx264", Resolution = "426:240", FrameRate = "24", Crf = 23 } , + new(){ PresetName = "H.264 1080p24", Codec = "libx264", Resolution = "1920:1080", FrameRate = "24000/1001", Crf = 23, AdditionalParameters ={ "-map 0" } }, + new(){ PresetName = "H.264 720p24", Codec = "libx264", Resolution = "1280:720", FrameRate = "24000/1001", Crf = 23, AdditionalParameters ={ "-map 0" } }, + new(){ PresetName = "H.264 480p24", Codec = "libx264", Resolution = "854:480", FrameRate = "24000/1001", Crf = 23, AdditionalParameters ={ "-map 0" } }, + new(){ PresetName = "H.264 360p24", Codec = "libx264", Resolution = "640:360", FrameRate = "24000/1001", Crf = 23, AdditionalParameters ={ "-map 0" } }, + new(){ PresetName = "H.264 240p24", Codec = "libx264", Resolution = "426:240", FrameRate = "24000/1001", Crf = 23, AdditionalParameters ={ "-map 0" } }, }; public static VideoPreset? GetPreset(string presetName){ @@ -49,13 +49,11 @@ public class FfmpegEncoding{ } public class VideoPreset{ - public string? PresetName{ get; set; } public string? Codec{ get; set; } public string? Resolution{ get; set; } public string? FrameRate{ get; set; } public int Crf{ get; set; } - - public List AdditionalParameters { get; set; } = new List(); - + + public List AdditionalParameters{ get; set; } = new List(); } \ No newline at end of file diff --git a/CRD/Utils/Files/CfgManager.cs b/CRD/Utils/Files/CfgManager.cs index c143d62..a0db102 100644 --- a/CRD/Utils/Files/CfgManager.cs +++ b/CRD/Utils/Files/CfgManager.cs @@ -31,6 +31,7 @@ public class CfgManager{ File.Exists(Path.Combine(WorkingDirectory, "lib", "mkvmerge")) ? Path.Combine(WorkingDirectory, "lib", "mkvmerge") : "mkvmerge"; public static readonly string PathMP4Decrypt = Path.Combine(WorkingDirectory, "lib", "mp4decrypt" + ExecutableExtension); + public static readonly string PathShakaPackager = Path.Combine(WorkingDirectory, "lib", "shaka-packager" + ExecutableExtension); public static readonly string PathWIDEVINE_DIR = Path.Combine(WorkingDirectory, "widevine"); diff --git a/CRD/Utils/Helpers.cs b/CRD/Utils/Helpers.cs index da4ef2f..9532199 100644 --- a/CRD/Utils/Helpers.cs +++ b/CRD/Utils/Helpers.cs @@ -322,7 +322,22 @@ public class Helpers{ string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(inputFilePath); string tempOutputFilePath = Path.Combine(directory, $"{fileNameWithoutExtension}_output{outputExtension}"); - string additionalParams = string.Join(" ", preset.AdditionalParameters); + string additionalParams = string.Join(" ", preset.AdditionalParameters.Select(param => { + var splitIndex = param.IndexOf(' '); + if (splitIndex > 0){ + var prefix = param[..splitIndex]; + var value = param[(splitIndex + 1)..]; + + if (value.Contains(' ') && !(value.StartsWith("\"") && value.EndsWith("\""))){ + value = $"\"{value}\""; + } + + return $"{prefix} {value}"; + } + + return param; + })); + string qualityOption = GetQualityOption(preset); TimeSpan? totalDuration = await GetMediaDurationAsync(CfgManager.PathFFMPEG, inputFilePath); diff --git a/CRD/Utils/Http/HttpClientReq.cs b/CRD/Utils/Http/HttpClientReq.cs index 6fea583..e8c7c54 100644 --- a/CRD/Utils/Http/HttpClientReq.cs +++ b/CRD/Utils/Http/HttpClientReq.cs @@ -48,8 +48,10 @@ public class HttpClientReq{ HttpClientHandler handler = new HttpClientHandler(); if (CrunchyrollManager.Instance.CrunOptions.ProxyEnabled && !string.IsNullOrEmpty(CrunchyrollManager.Instance.CrunOptions.ProxyHost)){ - handler = CreateHandler(true, CrunchyrollManager.Instance.CrunOptions.ProxyHost, CrunchyrollManager.Instance.CrunOptions.ProxyPort); - Console.Error.WriteLine($"Proxy is set: http://{CrunchyrollManager.Instance.CrunOptions.ProxyHost}:{CrunchyrollManager.Instance.CrunOptions.ProxyPort}"); + handler = CreateHandler(true, CrunchyrollManager.Instance.CrunOptions.ProxySocks, CrunchyrollManager.Instance.CrunOptions.ProxyHost, CrunchyrollManager.Instance.CrunOptions.ProxyPort, + CrunchyrollManager.Instance.CrunOptions.ProxyUsername, CrunchyrollManager.Instance.CrunOptions.ProxyPassword); + string scheme = CrunchyrollManager.Instance.CrunOptions.ProxySocks ? "socks5" : "http"; + Console.Error.WriteLine($"Proxy is set: {scheme}://{CrunchyrollManager.Instance.CrunOptions.ProxyHost}:{CrunchyrollManager.Instance.CrunOptions.ProxyPort}"); client = new HttpClient(handler); } else if (systemProxy != null){ Uri testUri = new Uri("https://icanhazip.com"); @@ -73,7 +75,7 @@ public class HttpClientReq{ Console.Error.WriteLine("No proxy is being used."); client = new HttpClient(CreateHttpClientHandler()); } - + client.Timeout = TimeSpan.FromSeconds(100); // client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0"); @@ -85,7 +87,6 @@ public class HttpClientReq{ client.DefaultRequestHeaders.AcceptEncoding.ParseAdd("gzip, deflate, br"); client.DefaultRequestHeaders.AcceptLanguage.ParseAdd("en-US,en;q=0.5"); client.DefaultRequestHeaders.Connection.ParseAdd("keep-alive"); - } private HttpMessageHandler CreateHttpClientHandler(){ @@ -110,7 +111,7 @@ public class HttpClientReq{ }; } - private HttpClientHandler CreateHandler(bool useProxy, string? proxyHost = null, int proxyPort = 0){ + private HttpClientHandler CreateHandler(bool useProxy, bool useSocks = false, string? proxyHost = null, int proxyPort = 0, string? proxyUsername = "", string? proxyPassword = ""){ var handler = new HttpClientHandler{ CookieContainer = new CookieContainer(), UseCookies = true, @@ -119,7 +120,11 @@ public class HttpClientReq{ }; if (useProxy && proxyHost != null){ - handler.Proxy = new WebProxy($"http://{proxyHost}:{proxyPort}"); + string scheme = useSocks ? "socks5" : "http"; + handler.Proxy = new WebProxy($"{scheme}://{proxyHost}:{proxyPort}"); + if (!string.IsNullOrEmpty(proxyUsername) && !string.IsNullOrEmpty(proxyPassword)){ + handler.Proxy.Credentials = new NetworkCredential(proxyUsername, proxyPassword); + } } return handler; diff --git a/CRD/Utils/Structs/Crunchyroll/CrDownloadOptions.cs b/CRD/Utils/Structs/Crunchyroll/CrDownloadOptions.cs index db00c02..8fda39e 100644 --- a/CRD/Utils/Structs/Crunchyroll/CrDownloadOptions.cs +++ b/CRD/Utils/Structs/Crunchyroll/CrDownloadOptions.cs @@ -89,13 +89,22 @@ public class CrDownloadOptions{ [YamlMember(Alias = "proxy_enabled", ApplyNamingConventions = false)] public bool ProxyEnabled{ get; set; } + + [YamlMember(Alias = "proxy_socks", ApplyNamingConventions = false)] + public bool ProxySocks{ get; set; } [YamlMember(Alias = "proxy_host", ApplyNamingConventions = false)] public string? ProxyHost{ get; set; } [YamlMember(Alias = "proxy_port", ApplyNamingConventions = false)] public int ProxyPort{ get; set; } + + [YamlMember(Alias = "proxy_username", ApplyNamingConventions = false)] + public string? ProxyUsername{ get; set; } + [YamlMember(Alias = "proxy_password", ApplyNamingConventions = false)] + public string? ProxyPassword{ get; set; } + #endregion diff --git a/CRD/Utils/Structs/History/HistorySeason.cs b/CRD/Utils/Structs/History/HistorySeason.cs index cd18881..9a7ab0e 100644 --- a/CRD/Utils/Structs/History/HistorySeason.cs +++ b/CRD/Utils/Structs/History/HistorySeason.cs @@ -51,6 +51,9 @@ public class HistorySeason : INotifyPropertyChanged{ [JsonIgnore] public StringItem? _selectedVideoQualityItem; + [JsonIgnore] + private bool Loading = false; + [JsonIgnore] public StringItem? SelectedVideoQualityItem{ get => _selectedVideoQualityItem; @@ -59,7 +62,9 @@ public class HistorySeason : INotifyPropertyChanged{ HistorySeasonVideoQualityOverride = value?.stringValue ?? ""; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedVideoQualityItem))); - CfgManager.UpdateHistoryFile(); + if (!Loading){ + CfgManager.UpdateHistoryFile(); + } } } @@ -127,6 +132,7 @@ public class HistorySeason : INotifyPropertyChanged{ } public void Init(){ + Loading = true; if (!(SubLangList.Count > 2 || DubLangList.Count > 0)){ foreach (var languageItem in Languages.languages){ SubLangList.Add(new StringItem{ stringValue = languageItem.CrLocale }); @@ -154,6 +160,7 @@ public class HistorySeason : INotifyPropertyChanged{ SelectedSubLang.CollectionChanged += Changes; SelectedDubLang.CollectionChanged += Changes; + Loading = false; } #endregion diff --git a/CRD/Utils/Structs/History/HistorySeries.cs b/CRD/Utils/Structs/History/HistorySeries.cs index b74655d..817061c 100644 --- a/CRD/Utils/Structs/History/HistorySeries.cs +++ b/CRD/Utils/Structs/History/HistorySeries.cs @@ -94,6 +94,9 @@ public class HistorySeries : INotifyPropertyChanged{ #region Settings Override + [JsonIgnore] + private bool Loading = false; + [JsonIgnore] public StringItem? _selectedVideoQualityItem; @@ -105,7 +108,10 @@ public class HistorySeries : INotifyPropertyChanged{ HistorySeriesVideoQualityOverride = value?.stringValue ?? ""; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedVideoQualityItem))); - CfgManager.UpdateHistoryFile(); + if (!Loading){ + CfgManager.UpdateHistoryFile(); + } + } } @@ -173,6 +179,7 @@ public class HistorySeries : INotifyPropertyChanged{ } public void Init(){ + Loading = true; if (!(SubLangList.Count > 2 || DubLangList.Count > 0)){ foreach (var languageItem in Languages.languages){ SubLangList.Add(new StringItem{ stringValue = languageItem.CrLocale }); @@ -200,6 +207,7 @@ public class HistorySeries : INotifyPropertyChanged{ SelectedSubLang.CollectionChanged += Changes; SelectedDubLang.CollectionChanged += Changes; + Loading = false; } #endregion diff --git a/CRD/Utils/Updater/Updater.cs b/CRD/Utils/Updater/Updater.cs index 386fee1..6e4c861 100644 --- a/CRD/Utils/Updater/Updater.cs +++ b/CRD/Utils/Updater/Updater.cs @@ -43,12 +43,20 @@ public class Updater : INotifyPropertyChanged{ } private string downloadUrl = ""; - private readonly string tempPath = Path.Combine(Path.GetTempPath(), "Update.zip"); - private readonly string extractPath = Path.Combine(Path.GetTempPath(), "ExtractedUpdate"); + private readonly string tempPath = Path.Combine(CfgManager.PathTEMP_DIR, "Update.zip"); + private readonly string extractPath = Path.Combine(CfgManager.PathTEMP_DIR, "ExtractedUpdate"); private readonly string apiEndpoint = "https://api.github.com/repos/Crunchy-DL/Crunchy-Downloader/releases/latest"; public async Task CheckForUpdatesAsync(){ + if (Directory.Exists(tempPath)){ + Directory.Delete(tempPath, true); + } + + if (Directory.Exists(extractPath)){ + Directory.Delete(extractPath, true); + } + try{ var platformAssetMapping = new Dictionary{ { OSPlatform.Windows, "windows" }, @@ -114,6 +122,8 @@ public class Updater : INotifyPropertyChanged{ public async Task DownloadAndUpdateAsync(){ try{ + Helpers.EnsureDirectoriesExist(tempPath); + // Download the zip file var response = await HttpClientReq.Instance.GetHttpClient().GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead); @@ -160,8 +170,9 @@ public class Updater : INotifyPropertyChanged{ } private void ApplyUpdate(string updateFolder){ + var ExecutableExtension = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : string.Empty; var currentPath = AppDomain.CurrentDomain.BaseDirectory; - var updaterPath = Path.Combine(currentPath, "Updater.exe"); + var updaterPath = Path.Combine(currentPath, "Updater" + ExecutableExtension); var arguments = $"\"{currentPath.Substring(0, currentPath.Length - 1)}\" \"{updateFolder}\""; System.Diagnostics.Process.Start(updaterPath, arguments); diff --git a/CRD/ViewModels/HistoryPageViewModel.cs b/CRD/ViewModels/HistoryPageViewModel.cs index 424385b..ccd751f 100644 --- a/CRD/ViewModels/HistoryPageViewModel.cs +++ b/CRD/ViewModels/HistoryPageViewModel.cs @@ -299,6 +299,7 @@ public partial class HistoryPageViewModel : ViewModelBase{ if (!string.IsNullOrEmpty(value.SonarrSeriesId) && CrunchyrollManager.Instance.CrunOptions.SonarrProperties is{ SonarrEnabled: true }){ CrunchyrollManager.Instance.History.MatchHistoryEpisodesWithSonarr(true, SelectedSeries); + CfgManager.UpdateHistoryFile(); } @@ -370,6 +371,7 @@ public partial class HistoryPageViewModel : ViewModelBase{ SonarrOptionsOpen = false; AddingMissingSonarrSeries = true; ProgramManager.FetchingData = true; + ProgramManager.NavigationLock = true; var crInstance = CrunchyrollManager.Instance; @@ -407,6 +409,7 @@ public partial class HistoryPageViewModel : ViewModelBase{ // Await the CRUpdateSeries task for each seriesId await crInstance.History.CRUpdateSeries(seriesIds[count], ""); + RaisePropertyChanged(nameof(ProgressText)); } // var updateTasks = seriesIds.Select(seriesId => crInstance.History.CRUpdateSeries(seriesId, "")); @@ -416,6 +419,7 @@ public partial class HistoryPageViewModel : ViewModelBase{ ProgressText = ""; AddingMissingSonarrSeries = false; ProgramManager.FetchingData = false; + ProgramManager.NavigationLock = false; if (SelectedFilter != null){ OnSelectedFilterChanged(SelectedFilter); } diff --git a/CRD/ViewModels/MainWindowViewModel.cs b/CRD/ViewModels/MainWindowViewModel.cs index f686f5b..d9b2eec 100644 --- a/CRD/ViewModels/MainWindowViewModel.cs +++ b/CRD/ViewModels/MainWindowViewModel.cs @@ -11,5 +11,4 @@ public partial class MainWindowViewModel : ViewModelBase{ public MainWindowViewModel(ProgramManager manager){ ProgramManager = manager; } - } \ No newline at end of file diff --git a/CRD/ViewModels/SeriesPageViewModel.cs b/CRD/ViewModels/SeriesPageViewModel.cs index e59b1e6..96ced87 100644 --- a/CRD/ViewModels/SeriesPageViewModel.cs +++ b/CRD/ViewModels/SeriesPageViewModel.cs @@ -48,9 +48,8 @@ public partial class SeriesPageViewModel : ViewModelBase{ public bool _seriesFolderPathExists; public SeriesPageViewModel(){ - _storageProvider = ProgramManager.Instance.StorageProvider ?? throw new ArgumentNullException(nameof(ProgramManager.Instance.StorageProvider)); - + _selectedSeries = CrunchyrollManager.Instance.SelectedSeries; if (_selectedSeries.ThumbnailImage == null){ @@ -153,7 +152,7 @@ public partial class SeriesPageViewModel : ViewModelBase{ UpdateSeriesFolderPath(); } - + [RelayCommand] public async Task MatchSonarrSeries_Button(){ var dialog = new ContentDialog(){ @@ -221,7 +220,18 @@ public partial class SeriesPageViewModel : ViewModelBase{ await Task.WhenAll(downloadTasks); } - + + [RelayCommand] + public void ToggleDownloadedMark(HistorySeason season){ + bool allDownloaded = season.EpisodesList.All(ep => ep.WasDownloaded); + + foreach (var historyEpisode in season.EpisodesList){ + if (historyEpisode.WasDownloaded == allDownloaded){ + season.UpdateDownloaded(historyEpisode.EpisodeId); + } + } + } + [RelayCommand] public async Task UpdateData(string? season){ await SelectedSeries.FetchData(season); diff --git a/CRD/ViewModels/Utils/ContentDialogEncodingPresetViewModel.cs b/CRD/ViewModels/Utils/ContentDialogEncodingPresetViewModel.cs index 204921b..e8c0852 100644 --- a/CRD/ViewModels/Utils/ContentDialogEncodingPresetViewModel.cs +++ b/CRD/ViewModels/Utils/ContentDialogEncodingPresetViewModel.cs @@ -34,7 +34,7 @@ public partial class ContentDialogEncodingPresetViewModel : ViewModelBase{ private double? _crf = 23; [ObservableProperty] - private double? _frameRate = 30; + private string _frameRate = ""; [ObservableProperty] private string _additionalParametersString = ""; @@ -82,6 +82,8 @@ public partial class ContentDialogEncodingPresetViewModel : ViewModelBase{ if (dialog is null){ throw new ArgumentNullException(nameof(dialog)); } + + AdditionalParameters.Add(new StringItem(){ stringValue = "-map 0" }); if (editMode){ EditMode = true; @@ -106,7 +108,7 @@ public partial class ContentDialogEncodingPresetViewModel : ViewModelBase{ PresetName = value.PresetName ?? ""; Codec = value.Codec ?? ""; Crf = value.Crf; - FrameRate = double.Parse(value.FrameRate ?? "0"); + FrameRate = value.FrameRate ?? "24"; SelectedResolution = ResolutionList.FirstOrDefault(e => e.Content?.ToString() == value.Resolution) ?? ResolutionList.First(); AdditionalParameters.Clear(); @@ -149,7 +151,7 @@ public partial class ContentDialogEncodingPresetViewModel : ViewModelBase{ SelectedCustomPreset.PresetName = PresetName; SelectedCustomPreset.Codec = Codec; - SelectedCustomPreset.FrameRate = Math.Clamp((int)(FrameRate ?? 1), 1, 999).ToString(); + SelectedCustomPreset.FrameRate = FrameRate; SelectedCustomPreset.Crf = Math.Clamp((int)(Crf ?? 0), 0, 51); SelectedCustomPreset.Resolution = SelectedResolution.Content?.ToString() ?? "1920:1080"; SelectedCustomPreset.AdditionalParameters = AdditionalParameters.Select(additionalParameter => additionalParameter.stringValue).ToList(); @@ -171,7 +173,7 @@ public partial class ContentDialogEncodingPresetViewModel : ViewModelBase{ VideoPreset newPreset = new VideoPreset(){ PresetName = PresetName, Codec = Codec, - FrameRate = Math.Clamp((int)(FrameRate ?? 1), 1, 999).ToString(), + FrameRate = FrameRate, Crf = Math.Clamp((int)(Crf ?? 0), 0, 51), Resolution = SelectedResolution.Content?.ToString() ?? "1920:1080", AdditionalParameters = AdditionalParameters.Select(additionalParameter => additionalParameter.stringValue).ToList() diff --git a/CRD/ViewModels/Utils/GeneralSettingsViewModel.cs b/CRD/ViewModels/Utils/GeneralSettingsViewModel.cs index 366d311..67f1048 100644 --- a/CRD/ViewModels/Utils/GeneralSettingsViewModel.cs +++ b/CRD/ViewModels/Utils/GeneralSettingsViewModel.cs @@ -168,12 +168,20 @@ public partial class GeneralSettingsViewModel : ViewModelBase{ [ObservableProperty] private bool _proxyEnabled; + [ObservableProperty] + private bool _proxySocks; + [ObservableProperty] private string _proxyHost; [ObservableProperty] private double? _proxyPort; + [ObservableProperty] + private string _proxyUsername; + + [ObservableProperty] + private string _proxyPassword; [ObservableProperty] private string _tempDownloadDirPath; @@ -223,7 +231,10 @@ public partial class GeneralSettingsViewModel : ViewModelBase{ } ProxyEnabled = options.ProxyEnabled; + ProxySocks = options.ProxySocks; ProxyHost = options.ProxyHost ?? ""; + ProxyUsername = options.ProxyUsername ?? ""; + ProxyPassword = options.ProxyPassword ?? ""; ProxyPort = options.ProxyPort; HistoryAddSpecials = options.HistoryAddSpecials; HistoryCountSonarr = options.HistoryCountSonarr; @@ -259,8 +270,11 @@ public partial class GeneralSettingsViewModel : ViewModelBase{ CrunchyrollManager.Instance.CrunOptions.SimultaneousDownloads = Math.Clamp((int)(SimultaneousDownloads ?? 0), 1, 10); CrunchyrollManager.Instance.CrunOptions.ProxyEnabled = ProxyEnabled; + CrunchyrollManager.Instance.CrunOptions.ProxySocks = ProxySocks; CrunchyrollManager.Instance.CrunOptions.ProxyHost = ProxyHost; CrunchyrollManager.Instance.CrunOptions.ProxyPort = Math.Clamp((int)(ProxyPort ?? 0), 0, 65535); + CrunchyrollManager.Instance.CrunOptions.ProxyUsername = ProxyUsername; + CrunchyrollManager.Instance.CrunOptions.ProxyPassword = ProxyPassword; string historyLang = SelectedHistoryLang.Content + ""; @@ -470,24 +484,33 @@ public partial class GeneralSettingsViewModel : ViewModelBase{ if (CrunchyrollManager.Instance.CrunOptions.History){ if (File.Exists(CfgManager.PathCrHistory)){ var decompressedJson = CfgManager.DecompressJsonFile(CfgManager.PathCrHistory); - if (!string.IsNullOrEmpty(decompressedJson)){ - CrunchyrollManager.Instance.HistoryList = Helpers.Deserialize>(decompressedJson, CrunchyrollManager.Instance.SettingsJsonSerializerSettings) ?? - new ObservableCollection(); - foreach (var historySeries in CrunchyrollManager.Instance.HistoryList){ + if (!string.IsNullOrEmpty(decompressedJson)){ + var historyList = Helpers.Deserialize>( + decompressedJson, + CrunchyrollManager.Instance.SettingsJsonSerializerSettings + ) ?? new ObservableCollection(); + + CrunchyrollManager.Instance.HistoryList = historyList; + + Parallel.ForEach(historyList, historySeries => { historySeries.Init(); + foreach (var historySeriesSeason in historySeries.Seasons){ historySeriesSeason.Init(); } - } + }); + } else{ - CrunchyrollManager.Instance.HistoryList =[]; + CrunchyrollManager.Instance.HistoryList = new ObservableCollection(); } + } else{ + CrunchyrollManager.Instance.HistoryList = new ObservableCollection(); } - - _ = SonarrClient.Instance.RefreshSonarrLite(); + + _ = Task.Run(() => SonarrClient.Instance.RefreshSonarrLite()); } else{ - CrunchyrollManager.Instance.HistoryList =[]; + CrunchyrollManager.Instance.HistoryList = new ObservableCollection(); } } diff --git a/CRD/Views/HistoryPageView.axaml b/CRD/Views/HistoryPageView.axaml index b702a5a..4f31156 100644 --- a/CRD/Views/HistoryPageView.axaml +++ b/CRD/Views/HistoryPageView.axaml @@ -669,7 +669,8 @@ + diff --git a/CRD/Views/Utils/ContentDialogEncodingPresetView.axaml b/CRD/Views/Utils/ContentDialogEncodingPresetView.axaml index 31cb6b8..264e8eb 100644 --- a/CRD/Views/Utils/ContentDialogEncodingPresetView.axaml +++ b/CRD/Views/Utils/ContentDialogEncodingPresetView.axaml @@ -23,85 +23,87 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + +