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
This commit is contained in:
Elwador 2025-01-15 03:59:57 +01:00
parent 51e3102503
commit 26d54ceb75
23 changed files with 329 additions and 171 deletions

View file

@ -164,41 +164,12 @@ public class CrunchyrollManager{
CfgManager.DisableLogMode();
}
if (CfgManager.CheckIfFileExists(CfgManager.PathCrToken)){
Token = CfgManager.DeserializeFromFile<CrToken>(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<ObservableCollection<HistorySeries>>(decompressedJson, CrunchyrollManager.Instance.SettingsJsonSerializerSettings) ?? new ObservableCollection<HistorySeries>();
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<VideoPreset>(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<CrToken>(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<ObservableCollection<HistorySeries>>(
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<DownloadedMedia>(),
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());
}

View file

@ -417,7 +417,7 @@
</controls:SettingsExpanderItem.Footer>
</controls:SettingsExpanderItem>
<controls:SettingsExpanderItem IsVisible="{Binding !SkipMuxing}" Content="Additional FFMpeg Options">
<controls:SettingsExpanderItem IsVisible="{Binding !SkipMuxing}" Content="Additional FFMpeg Options" Description="Only used for MP4">
<controls:SettingsExpanderItem.Footer>
<StackPanel>
<StackPanel Orientation="Horizontal">

View file

@ -678,7 +678,7 @@ public class History(){
}
});
CfgManager.UpdateHistoryFile();
}
}

View file

@ -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();

View file

@ -7,25 +7,25 @@ namespace CRD.Utils.Ffmpeg_Encoding;
public class FfmpegEncoding{
public static readonly List<VideoPreset> presets = new List<VideoPreset>{
// 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<string> AdditionalParameters { get; set; } = new List<string>();
public List<string> AdditionalParameters{ get; set; } = new List<string>();
}

View file

@ -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");

View file

@ -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);

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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<bool> CheckForUpdatesAsync(){
if (Directory.Exists(tempPath)){
Directory.Delete(tempPath, true);
}
if (Directory.Exists(extractPath)){
Directory.Delete(extractPath, true);
}
try{
var platformAssetMapping = new Dictionary<OSPlatform, string>{
{ 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);

View file

@ -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);
}

View file

@ -11,5 +11,4 @@ public partial class MainWindowViewModel : ViewModelBase{
public MainWindowViewModel(ProgramManager manager){
ProgramManager = manager;
}
}

View file

@ -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);

View file

@ -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()

View file

@ -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<ObservableCollection<HistorySeries>>(decompressedJson, CrunchyrollManager.Instance.SettingsJsonSerializerSettings) ??
new ObservableCollection<HistorySeries>();
foreach (var historySeries in CrunchyrollManager.Instance.HistoryList){
if (!string.IsNullOrEmpty(decompressedJson)){
var historyList = Helpers.Deserialize<ObservableCollection<HistorySeries>>(
decompressedJson,
CrunchyrollManager.Instance.SettingsJsonSerializerSettings
) ?? new ObservableCollection<HistorySeries>();
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<HistorySeries>();
}
} else{
CrunchyrollManager.Instance.HistoryList = new ObservableCollection<HistorySeries>();
}
_ = SonarrClient.Instance.RefreshSonarrLite();
_ = Task.Run(() => SonarrClient.Instance.RefreshSonarrLite());
} else{
CrunchyrollManager.Instance.HistoryList =[];
CrunchyrollManager.Instance.HistoryList = new ObservableCollection<HistorySeries>();
}
}

View file

@ -669,7 +669,8 @@
<Button Margin="0 0 5 0" FontStyle="Italic"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Command="{Binding DownloadEpisode}">
Command="{Binding DownloadEpisode}"
CommandParameter="false">
<StackPanel Orientation="Horizontal">
<controls:SymbolIcon Symbol="Download"
FontSize="18" />

View file

@ -49,6 +49,7 @@
<ui:NavigationView Grid.Row="1"
IsPaneOpen="False"
IsPaneToggleButtonVisible="False"
IsEnabled="{Binding !ProgramManager.NavigationLock}"
IsSettingsVisible="False"
CompactPaneLength="72"
Name="NavView"

View file

@ -45,8 +45,7 @@ public partial class MainWindow : AppWindow{
}
#endregion
private object selectedNavVieItem;
private const int TitleBarHeightAdjustment = 31;

View file

@ -382,7 +382,9 @@
</Button>
<Button Margin="0 0 5 0" FontStyle="Italic" HorizontalAlignment="Right"
VerticalAlignment="Center" Command="{Binding DownloadEpisode}">
VerticalAlignment="Center"
Command="{Binding DownloadEpisode}"
CommandParameter="false">
<StackPanel Orientation="Horizontal">
<controls:SymbolIcon Symbol="Download" FontSize="18" />
</StackPanel>
@ -467,6 +469,13 @@
Command="{Binding $parent[UserControl].((vm:SeriesPageViewModel)DataContext).DownloadSeasonMissingSonarr}"
CommandParameter="{Binding }">
</Button>
<Button Margin="10 5"
VerticalAlignment="Center"
HorizontalAlignment="Stretch"
Content="Toggle Downloaded"
Command="{Binding $parent[UserControl].((vm:SeriesPageViewModel)DataContext).ToggleDownloadedMark}"
CommandParameter="{Binding }">
</Button>
</StackPanel>
</Border>

View file

@ -23,85 +23,87 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
<!-- Preset Name -->
<StackPanel>
<TextBlock Text="Enter Preset Name" Margin="0,0,0,5" />
<TextBox Watermark="H.265 1080p" Text="{Binding PresetName}" />
<TextBlock Text="Preset name already used" FontSize="12" Foreground="{DynamicResource SystemFillColorCaution}"
IsVisible="{Binding FileExists}" />
</StackPanel>
<!-- Codec -->
<StackPanel>
<TextBlock Text="Enter Codec" Margin="0,10,0,5" />
<TextBox Watermark="libx265" Text="{Binding Codec}" />
</StackPanel>
<!-- Resolution ComboBox -->
<StackPanel>
<TextBlock Text="Select Resolution" Margin="0,10,0,5" />
<ComboBox HorizontalContentAlignment="Center" MinWidth="210" MaxDropDownHeight="400"
ItemsSource="{Binding ResolutionList}"
SelectedItem="{Binding SelectedResolution}">
</ComboBox>
</StackPanel>
<!-- Frame Rate NumberBox -->
<StackPanel>
<TextBlock Text="Enter Frame Rate" Margin="0,10,0,5" />
<controls:NumberBox Minimum="1" Maximum="999"
Value="{Binding FrameRate}"
SpinButtonPlacementMode="Inline"
HorizontalAlignment="Stretch" />
</StackPanel>
<!-- CRF NumberBox -->
<StackPanel>
<TextBlock Text="Enter CRF (0-51) - (cq,global_quality,qp)" Margin="0,10,0,5" />
<controls:NumberBox Minimum="0" Maximum="51"
Value="{Binding Crf}"
SpinButtonPlacementMode="Inline"
HorizontalAlignment="Stretch" />
</StackPanel>
<!-- Additional Parameters -->
<StackPanel Margin="0,20,0,0">
<TextBlock Text="Additional Parameters" Margin="0,0,0,5" />
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBox HorizontalAlignment="Left" MinWidth="250"
Text="{Binding AdditionalParametersString}" />
<Button HorizontalAlignment="Center" Command="{Binding AddAdditionalParam}">
<StackPanel Orientation="Horizontal">
<controls:SymbolIcon Symbol="Add" FontSize="18" />
</StackPanel>
</Button>
</StackPanel>
<ItemsControl ItemsSource="{Binding AdditionalParameters}" Margin="0,10,0,0" MaxWidth="350">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="#4a4a4a" Background="{DynamicResource ControlAltFillColorQuarternary}" BorderThickness="1"
CornerRadius="10" Margin="2">
<StackPanel Orientation="Horizontal" Margin="5">
<TextBlock Text="{Binding stringValue}" Margin="5,0" />
<Button Content="X" FontSize="10" VerticalAlignment="Center"
HorizontalAlignment="Center" Width="15" Height="15" Padding="0"
Command="{Binding $parent[ItemsControl].((utils:ContentDialogEncodingPresetViewModel)DataContext).RemoveAdditionalParam}"
CommandParameter="{Binding .}" />
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<!-- Preset Name -->
<StackPanel>
<TextBlock Text="Enter Preset Name" Margin="0,0,0,5" />
<TextBox Watermark="H.265 1080p" Text="{Binding PresetName}" />
<TextBlock Text="Preset name already used" FontSize="12" Foreground="{DynamicResource SystemFillColorCaution}"
IsVisible="{Binding FileExists}" />
</StackPanel>
<!-- Codec -->
<StackPanel>
<TextBlock Text="Enter Codec" Margin="0,10,0,5" />
<TextBox Watermark="libx265" Text="{Binding Codec}" />
</StackPanel>
<!-- Resolution ComboBox -->
<StackPanel>
<TextBlock Text="Select Resolution" Margin="0,10,0,5" />
<ComboBox HorizontalContentAlignment="Center" MinWidth="210" MaxDropDownHeight="400"
ItemsSource="{Binding ResolutionList}"
SelectedItem="{Binding SelectedResolution}">
</ComboBox>
</StackPanel>
<!-- Frame Rate NumberBox -->
<StackPanel>
<TextBlock Text="Enter Frame Rate" Margin="0,10,0,5" />
<!-- <controls:NumberBox Minimum="1" Maximum="999" -->
<!-- Value="{Binding FrameRate}" -->
<!-- SpinButtonPlacementMode="Inline" -->
<!-- HorizontalAlignment="Stretch" /> -->
<TextBox Watermark="24" Text="{Binding FrameRate}" />
</StackPanel>
<!-- CRF NumberBox -->
<StackPanel>
<TextBlock Text="Enter CRF (0-51) - (cq,global_quality,qp)" Margin="0,10,0,5" />
<controls:NumberBox Minimum="0" Maximum="51"
Value="{Binding Crf}"
SpinButtonPlacementMode="Inline"
HorizontalAlignment="Stretch" />
</StackPanel>
<!-- Additional Parameters -->
<StackPanel Margin="0,20,0,0">
<TextBlock Text="Additional Parameters" Margin="0,0,0,5" />
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBox HorizontalAlignment="Left" MinWidth="250"
Text="{Binding AdditionalParametersString}" />
<Button HorizontalAlignment="Center" Command="{Binding AddAdditionalParam}">
<StackPanel Orientation="Horizontal">
<controls:SymbolIcon Symbol="Add" FontSize="18" />
</StackPanel>
</Button>
</StackPanel>
<ItemsControl ItemsSource="{Binding AdditionalParameters}" Margin="0,10,0,0" MaxWidth="350" HorizontalAlignment="Left">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="#4a4a4a" Background="{DynamicResource ControlAltFillColorQuarternary}" BorderThickness="1"
CornerRadius="10" Margin="2">
<StackPanel Orientation="Horizontal" Margin="5">
<TextBlock Text="{Binding stringValue}" Margin="5,0" />
<Button Content="X" FontSize="8" VerticalAlignment="Center" HorizontalAlignment="Center"
HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
Width="15" Height="15" Padding="0"
Command="{Binding $parent[ItemsControl].((utils:ContentDialogEncodingPresetViewModel)DataContext).RemoveAdditionalParam}"
CommandParameter="{Binding .}" />
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</StackPanel>

View file

@ -189,6 +189,13 @@
<CheckBox IsChecked="{Binding ProxyEnabled}"> </CheckBox>
</controls:SettingsExpanderItem.Footer>
</controls:SettingsExpanderItem>
<controls:SettingsExpanderItem Content="Use Socks5 Proxy">
<controls:SettingsExpanderItem.Footer>
<CheckBox IsChecked="{Binding ProxySocks}"> </CheckBox>
</controls:SettingsExpanderItem.Footer>
</controls:SettingsExpanderItem>
<controls:SettingsExpanderItem Content="Host">
<controls:SettingsExpanderItem.Footer>
@ -206,6 +213,22 @@
</controls:SettingsExpanderItem.Footer>
</controls:SettingsExpanderItem>
<controls:SettingsExpanderItem Content="Username">
<controls:SettingsExpanderItem.Footer>
<TextBox HorizontalAlignment="Left" MinWidth="250"
Text="{Binding ProxyUsername}" />
</controls:SettingsExpanderItem.Footer>
</controls:SettingsExpanderItem>
<controls:SettingsExpanderItem Content="Password">
<controls:SettingsExpanderItem.Footer>
<TextBox HorizontalAlignment="Left" MinWidth="250"
Text="{Binding ProxyPassword}" />
</controls:SettingsExpanderItem.Footer>
</controls:SettingsExpanderItem>
</controls:SettingsExpander>