mirror of
https://github.com/Crunchy-DL/Crunchy-Downloader.git
synced 2026-01-11 20:10:26 +00:00
Add - Added encoding option to muxing settings
Add - Added Custom encoding presets Add - Added Skip Muxing to muxing settings Add - Added Dubs to file name settings Add - IP check in settings to check if VPN is being used Add - Dubs to "Add Downloads" Tab Add - Series folder link to history series if it finds the folder Add - Added command line arguments Add - Added proxy settings to the Settings tab (changes require a restart to take effect) Add - Added option to set "Sign" subs forced flag Add - Added option to set "CC" subs "hearing-impaired" flag Add - Added encoding presets editing Add - Added CC subtitles font option to the settings Add - Added available dubs to history episodes Chg - Defaults to system accent color when no color is selected in the settings Chg - Audio only mux to only copy and not encode Chg - Update dialog Chg - Light mode color adjustments Chg - Http Connection change to detect proxy (Clash) Chg - Settings filename description Chg - Changed FPS on encoding presets to 24fps Chg - Adjusted encoding to allow h264_nvenc & hevc_nvenc Chg - Moved sync timing folders from the Windows temp folder to the application root's temp folder Chg - The temp folder will now be deleted automatically when empty Fix - Locale not correctly applied to Urls in the "Add Downloads" Tab Fix - Locale not correctly applied to Search in the "Add Downloads" Tab Fix - Scrolling issue in settings Fix - Fix crash when removing streaming tokens (TOO_MANY_ACTIVE_STREAMS) Fix - Search didn't reset correctly Fix - Clash proxy didn't work Fix - Chapters were always taken from the original version (mainly JP) Fix - Connection issue Fix - Fixed an issue where proxy settings were only available when history was enabled Fix - Fixed scrolling issues with certain series in the "Add Downloads" tab Fix - Fixed an issue where History Series appeared incomplete after being added then deleted and re-added Fix - Fixed a crash related to sync timing
This commit is contained in:
parent
b5e894ba82
commit
5d94025fcc
41 changed files with 1749 additions and 317 deletions
|
|
@ -1,8 +1,14 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using CRD.ViewModels;
|
||||
using MainWindow = CRD.Views.MainWindow;
|
||||
using System.Linq;
|
||||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils.Updater;
|
||||
|
||||
namespace CRD;
|
||||
|
||||
|
|
@ -13,11 +19,22 @@ public partial class App : Application{
|
|||
|
||||
public override void OnFrameworkInitializationCompleted(){
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop){
|
||||
desktop.MainWindow = new MainWindow{
|
||||
DataContext = new MainWindowViewModel(),
|
||||
};
|
||||
var isHeadless = Environment.GetCommandLineArgs().Contains("--headless");
|
||||
|
||||
var manager = ProgramManager.Instance;
|
||||
|
||||
if (!isHeadless){
|
||||
desktop.MainWindow = new MainWindow{
|
||||
DataContext = new MainWindowViewModel(manager),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -123,7 +123,7 @@ public class CrEpisode(){
|
|||
|
||||
|
||||
if (!serieshasversions){
|
||||
Console.WriteLine("Couldn\'t find versions on episode, fell back to old method.");
|
||||
Console.WriteLine("Couldn\'t find versions on episode, added languages with language array.");
|
||||
}
|
||||
|
||||
return episode;
|
||||
|
|
|
|||
|
|
@ -116,12 +116,12 @@ public class CrSeries(){
|
|||
}
|
||||
|
||||
|
||||
public async Task<CrunchySeriesList?> ListSeriesId(string id, string crLocale, CrunchyMultiDownload? data){
|
||||
public async Task<CrunchySeriesList?> ListSeriesId(string id, string crLocale, CrunchyMultiDownload? data, bool forcedLocale = false){
|
||||
await crunInstance.CrAuth.RefreshToken(true);
|
||||
|
||||
bool serieshasversions = true;
|
||||
|
||||
CrSeriesSearch? parsedSeries = await ParseSeriesById(id, crLocale); // one piece - GRMG8ZQZR
|
||||
CrSeriesSearch? parsedSeries = await ParseSeriesById(id, crLocale,forcedLocale);
|
||||
|
||||
if (parsedSeries == null || parsedSeries.Data == null){
|
||||
Console.Error.WriteLine("Parse Data Invalid");
|
||||
|
|
@ -142,7 +142,7 @@ public class CrSeries(){
|
|||
if (data?.S != null && s.Id != data.Value.S) continue;
|
||||
int fallbackIndex = 0;
|
||||
if (cachedSeasonID != s.Id){
|
||||
seasonData = await GetSeasonDataById(s.Id, "");
|
||||
seasonData = await GetSeasonDataById(s.Id, forcedLocale ? crLocale : "");
|
||||
cachedSeasonID = s.Id;
|
||||
}
|
||||
|
||||
|
|
@ -261,7 +261,7 @@ public class CrSeries(){
|
|||
}
|
||||
|
||||
if (!serieshasversions){
|
||||
Console.WriteLine("Couldn\'t find versions on some episodes, fell back to old method.");
|
||||
Console.WriteLine("Couldn\'t find versions on some episodes, added languages with language array.");
|
||||
}
|
||||
|
||||
CrunchySeriesList crunchySeriesList = new CrunchySeriesList();
|
||||
|
|
@ -272,9 +272,12 @@ public class CrSeries(){
|
|||
var value = kvp.Value;
|
||||
var images = (value.Items[0].Images?.Thumbnail ?? new List<List<Image>>{ new List<Image>{ new Image{ Source = "/notFound.png" } } });
|
||||
var seconds = (int)Math.Floor(value.Items[0].DurationMs / 1000.0);
|
||||
var langList = value.Langs.Select(a => a.CrLocale).ToList();
|
||||
Languages.SortListByLangList(langList);
|
||||
|
||||
return new Episode{
|
||||
E = key.StartsWith("E") ? key.Substring(1) : key,
|
||||
Lang = value.Langs.Select(a => a.Code).ToList(),
|
||||
Lang = langList,
|
||||
Name = value.Items[0].Title,
|
||||
Season = Helpers.ExtractNumberAfterS(value.Items[0].Identifier) ?? value.Items[0].SeasonNumber.ToString(),
|
||||
SeriesTitle = Regex.Replace(value.Items[0].SeriesTitle, @"\(\w+ Dub\)", "").TrimEnd(),
|
||||
|
|
@ -439,12 +442,15 @@ public class CrSeries(){
|
|||
}
|
||||
|
||||
|
||||
public async Task<CrSearchSeriesBase?> Search(string searchString, string? crLocale){
|
||||
public async Task<CrSearchSeriesBase?> Search(string searchString, string? crLocale, bool forced = false){
|
||||
await crunInstance.CrAuth.RefreshToken(true);
|
||||
NameValueCollection query = HttpUtility.ParseQueryString(new UriBuilder().Query);
|
||||
|
||||
if (!string.IsNullOrEmpty(crLocale)){
|
||||
query["locale"] = crLocale;
|
||||
if (forced){
|
||||
query["force_locale"] = crLocale;
|
||||
}
|
||||
}
|
||||
|
||||
query["q"] = searchString;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ using System.Xml;
|
|||
using Avalonia.Media;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.DRM;
|
||||
using CRD.Utils.Ffmpeg_Encoding;
|
||||
using CRD.Utils.Files;
|
||||
using CRD.Utils.HLS;
|
||||
using CRD.Utils.Muxing;
|
||||
|
|
@ -99,18 +100,19 @@ public class CrunchyrollManager{
|
|||
options.FileName = "${seriesTitle} - S${season}E${episode} [${height}p]";
|
||||
options.Partsize = 10;
|
||||
options.DlSubs = new List<string>{ "en-US" };
|
||||
options.Skipmux = false;
|
||||
options.SkipMuxing = false;
|
||||
options.MkvmergeOptions = new List<string>{ "--no-date", "--disable-track-statistics-tags", "--engage no_variable_data" };
|
||||
options.FfmpegOptions = new();
|
||||
options.DefaultAudio = "ja-JP";
|
||||
options.DefaultSub = "en-US";
|
||||
options.CcTag = "CC";
|
||||
options.CcSubsFont = "Trebuchet MS";
|
||||
options.FsRetryTime = 5;
|
||||
options.Numbers = 2;
|
||||
options.Timeout = 15000;
|
||||
options.DubLang = new List<string>(){ "ja-JP" };
|
||||
options.SimultaneousDownloads = 2;
|
||||
options.AccentColor = Colors.SlateBlue.ToString();
|
||||
// options.AccentColor = Colors.SlateBlue.ToString();
|
||||
options.Theme = "System";
|
||||
options.SelectedCalendarLanguage = "en-us";
|
||||
options.CalendarDubFilter = "none";
|
||||
|
|
@ -145,7 +147,7 @@ public class CrunchyrollManager{
|
|||
HasPremium = false,
|
||||
};
|
||||
|
||||
Console.WriteLine($"Can Decrypt: {_widevine.canDecrypt}");
|
||||
Console.WriteLine($"CDM available: {_widevine.canDecrypt}");
|
||||
}
|
||||
|
||||
public async Task Init(){
|
||||
|
|
@ -157,7 +159,7 @@ public class CrunchyrollManager{
|
|||
|
||||
if (CfgManager.CheckIfFileExists(CfgManager.PathCrToken)){
|
||||
Token = CfgManager.DeserializeFromFile<CrToken>(CfgManager.PathCrToken);
|
||||
CrAuth.LoginWithToken();
|
||||
await CrAuth.LoginWithToken();
|
||||
} else{
|
||||
await CrAuth.AuthAnonymous();
|
||||
}
|
||||
|
|
@ -181,6 +183,26 @@ public class CrunchyrollManager{
|
|||
|
||||
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){
|
||||
FfmpegEncoding.AddPreset(obj);
|
||||
} else{
|
||||
Console.Error.WriteLine("Failed to add Preset to Available Presets List");
|
||||
}
|
||||
} catch (Exception ex){
|
||||
Console.Error.WriteLine($"Failed to deserialize file {file}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -212,7 +234,7 @@ public class CrunchyrollManager{
|
|||
return false;
|
||||
}
|
||||
|
||||
if (options.Skipmux == false){
|
||||
if (options.SkipMuxing == false){
|
||||
data.DownloadProgress = new DownloadProgress(){
|
||||
IsDownloading = true,
|
||||
Percent = 100,
|
||||
|
|
@ -257,6 +279,21 @@ public class CrunchyrollManager{
|
|||
|
||||
foreach (var merger in mergers){
|
||||
merger.CleanUp();
|
||||
|
||||
if (CrunOptions.IsEncodeEnabled){
|
||||
data.DownloadProgress = new DownloadProgress(){
|
||||
IsDownloading = true,
|
||||
Percent = 100,
|
||||
Time = 0,
|
||||
DownloadSpeed = 0,
|
||||
Doing = "Encoding"
|
||||
};
|
||||
|
||||
QueueManager.Instance.Queue.Refresh();
|
||||
|
||||
await Helpers.RunFFmpegWithPresetAsync(merger?.options.Output, FfmpegEncoding.GetPreset(CrunOptions.EncodingPresetName));
|
||||
}
|
||||
|
||||
if (CrunOptions.DownloadToTempFolder){
|
||||
await MoveFromTempFolder(merger, data, res.TempFolderPath, res.Data.Where(e => e.Type == DownloadMediaType.Subtitle));
|
||||
}
|
||||
|
|
@ -286,6 +323,20 @@ public class CrunchyrollManager{
|
|||
result.merger.CleanUp();
|
||||
}
|
||||
|
||||
if (CrunOptions.IsEncodeEnabled){
|
||||
data.DownloadProgress = new DownloadProgress(){
|
||||
IsDownloading = true,
|
||||
Percent = 100,
|
||||
Time = 0,
|
||||
DownloadSpeed = 0,
|
||||
Doing = "Encoding"
|
||||
};
|
||||
|
||||
QueueManager.Instance.Queue.Refresh();
|
||||
|
||||
await Helpers.RunFFmpegWithPresetAsync(result.merger?.options.Output, FfmpegEncoding.GetPreset(CrunOptions.EncodingPresetName));
|
||||
}
|
||||
|
||||
if (CrunOptions.DownloadToTempFolder){
|
||||
await MoveFromTempFolder(result.merger, data, res.TempFolderPath, res.Data.Where(e => e.Type == DownloadMediaType.Subtitle));
|
||||
}
|
||||
|
|
@ -306,8 +357,33 @@ public class CrunchyrollManager{
|
|||
}
|
||||
} else{
|
||||
Console.WriteLine("Skipping mux");
|
||||
res.Data.ForEach(file => Helpers.DeleteFile(file.Path + ".resume"));
|
||||
if (CrunOptions.DownloadToTempFolder){
|
||||
if (string.IsNullOrEmpty(res.TempFolderPath) || !Directory.Exists(res.TempFolderPath)){
|
||||
Console.WriteLine("Invalid or non-existent temp folder path.");
|
||||
} else{
|
||||
// Move files
|
||||
foreach (var downloadedMedia in res.Data){
|
||||
await MoveFile(downloadedMedia.Path ?? string.Empty, res.TempFolderPath, data.DownloadPath ?? CfgManager.PathVIDEOS_DIR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.DownloadProgress = new DownloadProgress(){
|
||||
IsDownloading = true,
|
||||
Done = true,
|
||||
Percent = 100,
|
||||
Time = 0,
|
||||
DownloadSpeed = 0,
|
||||
Doing = "Done - Skipped muxing"
|
||||
};
|
||||
|
||||
if (CrunOptions.RemoveFinishedDownload){
|
||||
QueueManager.Instance.Queue.Remove(data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QueueManager.Instance.ActiveDownloads--;
|
||||
QueueManager.Instance.Queue.Refresh();
|
||||
|
||||
|
|
@ -668,7 +744,12 @@ public class CrunchyrollManager{
|
|||
List<string> compiledChapters = new List<string>();
|
||||
|
||||
if (options.Chapters){
|
||||
await ParseChapters(primaryVersion.Guid ?? mediaGuid, compiledChapters);
|
||||
await ParseChapters(mediaGuid, compiledChapters);
|
||||
|
||||
if (compiledChapters.Count == 0 && primaryVersion.MediaGuid != null && mediaGuid != primaryVersion.MediaGuid){
|
||||
Console.Error.WriteLine("Chapters empty trying to get original version chapters - might not match with video");
|
||||
await ParseChapters(primaryVersion.MediaGuid, compiledChapters);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
@ -703,7 +784,7 @@ public class CrunchyrollManager{
|
|||
var pbData = fetchPlaybackData.pbData;
|
||||
|
||||
List<string> hsLangs = new List<string>();
|
||||
var pbStreams = pbData.Data?[0];
|
||||
var pbStreams = pbData.Data;
|
||||
var streams = new List<StreamDetailsPop>();
|
||||
|
||||
variables.Add(new Variable("title", data.EpisodeTitle ?? string.Empty, true));
|
||||
|
|
@ -712,34 +793,30 @@ public class CrunchyrollManager{
|
|||
variables.Add(new Variable("seriesTitle", data.SeriesTitle ?? string.Empty, true));
|
||||
variables.Add(new Variable("seasonTitle", data.SeasonTitle ?? string.Empty, true));
|
||||
variables.Add(new Variable("season", !string.IsNullOrEmpty(data.Season) ? Math.Round(double.Parse(data.Season, CultureInfo.InvariantCulture), 1) : 0, false));
|
||||
variables.Add(new Variable("dubs", string.Join(", ", data.SelectedDubs ??[]), true));
|
||||
|
||||
|
||||
if (pbStreams?.Keys != null){
|
||||
foreach (var key in pbStreams.Keys){
|
||||
if ((key.Contains("hls") || key.Contains("dash")) &&
|
||||
!(key.Contains("hls") && key.Contains("drm")) &&
|
||||
!((!_widevine.canDecrypt || !File.Exists(CfgManager.PathMP4Decrypt)) && key.Contains("drm")) &&
|
||||
!key.Contains("trailer")){
|
||||
var pb = pbStreams[key].Select(v => {
|
||||
v.Value.HardsubLang = v.Value.HardsubLocale != null
|
||||
? Languages.FixAndFindCrLc(v.Value.HardsubLocale.GetEnumMemberValue()).Locale
|
||||
: null;
|
||||
if (v.Value.HardsubLocale != null && v.Value.HardsubLang != null && !hsLangs.Contains(v.Value.HardsubLocale.GetEnumMemberValue())){
|
||||
hsLangs.Add(v.Value.HardsubLang);
|
||||
}
|
||||
|
||||
return new StreamDetailsPop{
|
||||
Url = v.Value.Url,
|
||||
HardsubLocale = v.Value.HardsubLocale,
|
||||
HardsubLang = v.Value.HardsubLang,
|
||||
AudioLang = v.Value.AudioLang,
|
||||
Type = v.Value.Type,
|
||||
Format = key,
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
streams.AddRange(pb);
|
||||
var pb = pbStreams.Select(v => {
|
||||
v.Value.HardsubLang = v.Value.HardsubLocale != null
|
||||
? Languages.FixAndFindCrLc(v.Value.HardsubLocale.GetEnumMemberValue()).Locale
|
||||
: null;
|
||||
if (v.Value.HardsubLocale != null && v.Value.HardsubLang != null && !hsLangs.Contains(v.Value.HardsubLocale.GetEnumMemberValue())){
|
||||
hsLangs.Add(v.Value.HardsubLang);
|
||||
}
|
||||
}
|
||||
|
||||
return new StreamDetailsPop{
|
||||
Url = v.Value.Url,
|
||||
HardsubLocale = v.Value.HardsubLocale,
|
||||
HardsubLang = v.Value.HardsubLang,
|
||||
AudioLang = v.Value.AudioLang,
|
||||
Type = v.Value.Type,
|
||||
Format = "drm_adaptive_dash",
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
streams.AddRange(pb);
|
||||
|
||||
|
||||
if (streams.Count < 1){
|
||||
Console.WriteLine("No full streams found!");
|
||||
|
|
@ -773,19 +850,29 @@ public class CrunchyrollManager{
|
|||
Console.WriteLine($"Selecting stream with {Languages.Locale2language(options.Hslang).Language} hardsubs");
|
||||
streams = streams.Where((s) => s.HardsubLang != "-" && s.HardsubLang == options.Hslang).ToList();
|
||||
} else{
|
||||
Console.Error.WriteLine($"Selected stream with {Languages.Locale2language(options.Hslang).Language} hardsubs not available");
|
||||
Console.Error.WriteLine($"Selected stream with {Languages.Locale2language(options.Hslang).CrLocale} hardsubs not available");
|
||||
if (hsLangs.Count > 0){
|
||||
Console.Error.WriteLine("Try hardsubs stream: " + string.Join(", ", hsLangs));
|
||||
}
|
||||
|
||||
dlFailed = true;
|
||||
if (dlVideoOnce && CrunOptions.DlVideoOnce){
|
||||
streams = streams.Where((s) => {
|
||||
if (s.HardsubLang != "-"){
|
||||
return false;
|
||||
}
|
||||
|
||||
return new DownloadResponse{
|
||||
Data = new List<DownloadedMedia>(),
|
||||
Error = dlFailed,
|
||||
FileName = "./unknown",
|
||||
ErrorText = "Hardsubs not available"
|
||||
};
|
||||
return true;
|
||||
}).ToList();
|
||||
} else{
|
||||
dlFailed = true;
|
||||
|
||||
return new DownloadResponse{
|
||||
Data = new List<DownloadedMedia>(),
|
||||
Error = dlFailed,
|
||||
FileName = "./unknown",
|
||||
ErrorText = "Hardsubs not available"
|
||||
};
|
||||
}
|
||||
}
|
||||
} else{
|
||||
streams = streams.Where((s) => {
|
||||
|
|
@ -805,7 +892,7 @@ public class CrunchyrollManager{
|
|||
dlFailed = true;
|
||||
}
|
||||
|
||||
Console.WriteLine("Selecting raw stream");
|
||||
Console.WriteLine("Selecting stream");
|
||||
}
|
||||
|
||||
StreamDetailsPop? curStream = null;
|
||||
|
|
@ -816,7 +903,7 @@ public class CrunchyrollManager{
|
|||
|
||||
for (int i = 0; i < streams.Count; i++){
|
||||
string isSelected = options.Kstream == i + 1 ? "+" : " ";
|
||||
Console.WriteLine($"Full stream found! ({isSelected}{i + 1}: {streams[i].Type})");
|
||||
Console.WriteLine($"Full stream: ({isSelected}{i + 1}: {streams[i].Type})");
|
||||
}
|
||||
|
||||
Console.WriteLine("Downloading video...");
|
||||
|
|
@ -983,7 +1070,7 @@ public class CrunchyrollManager{
|
|||
|
||||
|
||||
fileName = Path.Combine(FileNameManager.ParseFileName(options.FileName, variables, options.Numbers, options.Override).ToArray());
|
||||
string outFile = Path.Combine(FileNameManager.ParseFileName(options.FileName + "." + (epMeta.Lang?.Name ?? 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());
|
||||
|
||||
string tempFile = Path.Combine(FileNameManager.ParseFileName($"temp-{(currentVersion.Guid != null ? currentVersion.Guid : currentMediaId)}", variables, options.Numbers, options.Override)
|
||||
.ToArray());
|
||||
|
|
@ -1288,7 +1375,7 @@ public class CrunchyrollManager{
|
|||
try{
|
||||
// Parsing and constructing the file names
|
||||
fileName = Path.Combine(FileNameManager.ParseFileName(options.FileName, variables, options.Numbers, options.Override).ToArray());
|
||||
string outFile = Path.Combine(FileNameManager.ParseFileName(options.FileName + "." + (epMeta.Lang?.Name), variables, options.Numbers, options.Override).ToArray());
|
||||
string outFile = Path.Combine(FileNameManager.ParseFileName(options.FileName + "." + (epMeta.Lang?.CrLocale), variables, options.Numbers, options.Override).ToArray());
|
||||
if (Path.IsPathRooted(outFile)){
|
||||
tsFile = outFile;
|
||||
} else{
|
||||
|
|
@ -1418,8 +1505,8 @@ public class CrunchyrollManager{
|
|||
private static async Task DownloadSubtitles(CrDownloadOptions options, PlaybackData pbData, string audDub, string fileName, List<DownloadedMedia> files, string fileDir, CrunchyEpMeta data, bool needsDelay,
|
||||
DownloadedMedia videoDownloadMedia){
|
||||
if (pbData.Meta != null && (pbData.Meta.Subtitles is{ Count: > 0 } || pbData.Meta.Captions is{ Count: > 0 })){
|
||||
List<SubtitleInfo> subsData = pbData.Meta.Subtitles?.Values.ToList() ?? [];
|
||||
List<Caption> capsData = pbData.Meta.Captions?.Values.ToList() ?? [];
|
||||
List<SubtitleInfo> subsData = pbData.Meta.Subtitles?.Values.ToList() ??[];
|
||||
List<Caption> capsData = pbData.Meta.Captions?.Values.ToList() ??[];
|
||||
var subsDataMapped = subsData.Select(s => {
|
||||
var subLang = Languages.FixAndFindCrLc((s.Locale ?? Locale.DefaulT).GetEnumMemberValue());
|
||||
return new{
|
||||
|
|
@ -1507,7 +1594,7 @@ public class CrunchyrollManager{
|
|||
assBuilder.AppendLine("[V4+ Styles]");
|
||||
assBuilder.AppendLine("Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, "
|
||||
+ "Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding");
|
||||
assBuilder.AppendLine("Style: Default,Trebuchet MS,24,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,1,2,0010,0010,0018,1");
|
||||
assBuilder.AppendLine($"Style: Default,{options.CcSubsFont ?? "Trebuchet MS"},24,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,1,2,0010,0010,0018,1");
|
||||
assBuilder.AppendLine();
|
||||
assBuilder.AppendLine("[Events]");
|
||||
assBuilder.AppendLine("Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text");
|
||||
|
|
@ -1665,7 +1752,7 @@ public class CrunchyrollManager{
|
|||
private async Task<(bool IsOk, PlaybackData pbData, string error)> FetchPlaybackData(string mediaId, string mediaGuidId, bool music){
|
||||
var temppbData = new PlaybackData{
|
||||
Total = 0,
|
||||
Data = new List<Dictionary<string, Dictionary<string, StreamDetails>>>()
|
||||
Data = new Dictionary<string, StreamDetails>()
|
||||
};
|
||||
|
||||
var playbackEndpoint = $"https://cr-play-service.prd.crunchyrollsvc.com/v1/{(music ? "music/" : "")}{mediaGuidId}/{CrunOptions.StreamEndpoint}/play";
|
||||
|
|
@ -1719,7 +1806,7 @@ public class CrunchyrollManager{
|
|||
private async Task<PlaybackData> ProcessPlaybackResponseAsync(string responseContent, string mediaId, string mediaGuidId){
|
||||
var temppbData = new PlaybackData{
|
||||
Total = 0,
|
||||
Data = new List<Dictionary<string, Dictionary<string, StreamDetails>>>()
|
||||
Data = new Dictionary<string, StreamDetails>()
|
||||
};
|
||||
|
||||
var playStream = Helpers.Deserialize<CrunchyStreamData>(responseContent, SettingsJsonSerializerSettings);
|
||||
|
|
@ -1745,9 +1832,7 @@ public class CrunchyrollManager{
|
|||
HardsubLocale = Locale.DefaulT
|
||||
};
|
||||
|
||||
temppbData.Data.Add(new Dictionary<string, Dictionary<string, StreamDetails>>{
|
||||
{ "drm_adaptive_dash", derivedPlayCrunchyStreams }
|
||||
});
|
||||
temppbData.Data = derivedPlayCrunchyStreams;
|
||||
temppbData.Total = 1;
|
||||
|
||||
temppbData.Meta = new PlaybackMeta{
|
||||
|
|
@ -1903,7 +1988,7 @@ public class CrunchyrollManager{
|
|||
return true;
|
||||
}
|
||||
|
||||
Console.WriteLine("Old Chapter API request failed");
|
||||
Console.Error.WriteLine("Old Chapter API request failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,10 +22,10 @@ public class History(){
|
|||
public async Task CRUpdateSeries(string seriesId, string? seasonId){
|
||||
await crunInstance.CrAuth.RefreshToken(true);
|
||||
|
||||
CrSeriesSearch? parsedSeries = await crunInstance.CrSeries.ParseSeriesById(seriesId, "en-US", true);
|
||||
CrSeriesSearch? parsedSeries = await crunInstance.CrSeries.ParseSeriesById(seriesId, "ja-JP", true);
|
||||
|
||||
if (parsedSeries == null){
|
||||
Console.Error.WriteLine("Parse Data Invalid");
|
||||
Console.Error.WriteLine("Parse Data Invalid - series is maybe only available with VPN or got deleted");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -242,17 +242,37 @@ public class History(){
|
|||
var historyEpisode = historySeason.EpisodesList.Find(e => e.EpisodeId == crunchyEpisode.Id);
|
||||
|
||||
if (historyEpisode == null){
|
||||
|
||||
var langList = new List<string>();
|
||||
|
||||
if (crunchyEpisode.Versions != null){
|
||||
langList.AddRange(crunchyEpisode.Versions.Select(version => version.AudioLocale));
|
||||
} else{
|
||||
langList.Add(crunchyEpisode.AudioLocale);
|
||||
}
|
||||
|
||||
var newHistoryEpisode = new HistoryEpisode{
|
||||
EpisodeTitle = GetEpisodeTitle(crunchyEpisode),
|
||||
EpisodeDescription = crunchyEpisode.Description,
|
||||
EpisodeId = crunchyEpisode.Id,
|
||||
Episode = crunchyEpisode.Episode,
|
||||
EpisodeSeasonNum = Helpers.ExtractNumberAfterS(crunchyEpisode.Identifier) ?? crunchyEpisode.SeasonNumber + "",
|
||||
EpisodeSeasonNum = Helpers.ExtractNumberAfterS(firstEpisode.Identifier) ?? firstEpisode.SeasonNumber + "",
|
||||
SpecialEpisode = !int.TryParse(crunchyEpisode.Episode, out _),
|
||||
HistoryEpisodeAvailableDubLang = Languages.SortListByLangList(langList),
|
||||
HistoryEpisodeAvailableSoftSubs = Languages.SortListByLangList(crunchyEpisode.SubtitleLocales),
|
||||
};
|
||||
|
||||
historySeason.EpisodesList.Add(newHistoryEpisode);
|
||||
} else{
|
||||
|
||||
var langList = new List<string>();
|
||||
|
||||
if (crunchyEpisode.Versions != null){
|
||||
langList.AddRange(crunchyEpisode.Versions.Select(version => version.AudioLocale));
|
||||
} else{
|
||||
langList.Add(crunchyEpisode.AudioLocale);
|
||||
}
|
||||
|
||||
//Update existing episode
|
||||
historyEpisode.EpisodeTitle = GetEpisodeTitle(crunchyEpisode);
|
||||
historyEpisode.SpecialEpisode = !int.TryParse(crunchyEpisode.Episode, out _);
|
||||
|
|
@ -260,6 +280,9 @@ public class History(){
|
|||
historyEpisode.EpisodeId = crunchyEpisode.Id;
|
||||
historyEpisode.Episode = crunchyEpisode.Episode;
|
||||
historyEpisode.EpisodeSeasonNum = Helpers.ExtractNumberAfterS(crunchyEpisode.Identifier) ?? crunchyEpisode.SeasonNumber + "";
|
||||
|
||||
historyEpisode.HistoryEpisodeAvailableDubLang = Languages.SortListByLangList(langList);
|
||||
historyEpisode.HistoryEpisodeAvailableSoftSubs = Languages.SortListByLangList(crunchyEpisode.SubtitleLocales);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -335,6 +358,14 @@ public class History(){
|
|||
if (cachedSeries == null || (cachedSeries.Data != null && cachedSeries.Data.First().Id != seriesId)){
|
||||
cachedSeries = await crunInstance.CrSeries.SeriesById(seriesId, string.IsNullOrEmpty(crunInstance.CrunOptions.HistoryLang) ? crunInstance.DefaultLocale : crunInstance.CrunOptions.HistoryLang, true);
|
||||
} else{
|
||||
if (cachedSeries?.Data != null){
|
||||
var series = cachedSeries.Data.First();
|
||||
historySeries.SeriesDescription = series.Description;
|
||||
historySeries.ThumbnailImageUrl = GetSeriesThumbnail(cachedSeries);
|
||||
historySeries.SeriesTitle = series.Title;
|
||||
historySeries.HistorySeriesAvailableDubLang = Languages.SortListByLangList(series.AudioLocales);
|
||||
historySeries.HistorySeriesAvailableSoftSubs = Languages.SortListByLangList(series.SubtitleLocales);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -343,8 +374,8 @@ public class History(){
|
|||
historySeries.SeriesDescription = series.Description;
|
||||
historySeries.ThumbnailImageUrl = GetSeriesThumbnail(cachedSeries);
|
||||
historySeries.SeriesTitle = series.Title;
|
||||
historySeries.HistorySeriesAvailableDubLang = series.AudioLocales;
|
||||
historySeries.HistorySeriesAvailableSoftSubs = series.SubtitleLocales;
|
||||
historySeries.HistorySeriesAvailableDubLang = Languages.SortListByLangList(series.AudioLocales);
|
||||
historySeries.HistorySeriesAvailableSoftSubs = Languages.SortListByLangList(series.SubtitleLocales);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -474,6 +505,16 @@ public class History(){
|
|||
};
|
||||
|
||||
foreach (var crunchyEpisode in seasonData){
|
||||
|
||||
var langList = new List<string>();
|
||||
|
||||
if (crunchyEpisode.Versions != null){
|
||||
langList.AddRange(crunchyEpisode.Versions.Select(version => version.AudioLocale));
|
||||
} else{
|
||||
langList.Add(crunchyEpisode.AudioLocale);
|
||||
}
|
||||
Languages.SortListByLangList(langList);
|
||||
|
||||
var newHistoryEpisode = new HistoryEpisode{
|
||||
EpisodeTitle = GetEpisodeTitle(crunchyEpisode),
|
||||
EpisodeDescription = crunchyEpisode.Description,
|
||||
|
|
@ -481,6 +522,8 @@ public class History(){
|
|||
Episode = crunchyEpisode.Episode,
|
||||
EpisodeSeasonNum = Helpers.ExtractNumberAfterS(firstEpisode.Identifier) ?? firstEpisode.SeasonNumber + "",
|
||||
SpecialEpisode = !int.TryParse(crunchyEpisode.Episode, out _),
|
||||
HistoryEpisodeAvailableDubLang = langList,
|
||||
HistoryEpisodeAvailableSoftSubs = crunchyEpisode.SubtitleLocales,
|
||||
};
|
||||
|
||||
newSeason.EpisodesList.Add(newHistoryEpisode);
|
||||
|
|
|
|||
176
CRD/Downloader/ProgramManager.cs
Normal file
176
CRD/Downloader/ProgramManager.cs
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Styling;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils.Updater;
|
||||
using FluentAvalonia.Styling;
|
||||
|
||||
namespace CRD.Downloader;
|
||||
|
||||
public partial class ProgramManager : ObservableObject{
|
||||
#region Singelton
|
||||
|
||||
private static ProgramManager? _instance;
|
||||
private static readonly object Padlock = new();
|
||||
|
||||
public static ProgramManager Instance{
|
||||
get{
|
||||
if (_instance == null){
|
||||
lock (Padlock){
|
||||
if (_instance == null){
|
||||
_instance = new ProgramManager();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Observables
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _fetchingData;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _updateAvailable = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _finishedLoading = false;
|
||||
|
||||
#endregion
|
||||
|
||||
private readonly FluentAvaloniaTheme? _faTheme;
|
||||
|
||||
private Queue<Func<Task>> taskQueue = new Queue<Func<Task>>();
|
||||
|
||||
private bool exitOnTaskFinish = false;
|
||||
|
||||
public ProgramManager(){
|
||||
_faTheme = Application.Current?.Styles[0] as FluentAvaloniaTheme;
|
||||
|
||||
foreach (var arg in Environment.GetCommandLineArgs()){
|
||||
if (arg == "--historyRefreshAll"){
|
||||
taskQueue.Enqueue(RefreshAll);
|
||||
} else if (arg == "--historyAddToQueue"){
|
||||
taskQueue.Enqueue(AddMissingToQueue);
|
||||
} else if (arg == "--exit"){
|
||||
exitOnTaskFinish = true;
|
||||
}
|
||||
}
|
||||
|
||||
Init();
|
||||
|
||||
CleanUpOldUpdater();
|
||||
}
|
||||
|
||||
private async Task RefreshAll(){
|
||||
FetchingData = true;
|
||||
|
||||
foreach (var item in CrunchyrollManager.Instance.HistoryList){
|
||||
item.SetFetchingData();
|
||||
}
|
||||
|
||||
for (int i = 0; i < CrunchyrollManager.Instance.HistoryList.Count; i++){
|
||||
await CrunchyrollManager.Instance.HistoryList[i].FetchData("");
|
||||
CrunchyrollManager.Instance.HistoryList[i].UpdateNewEpisodes();
|
||||
}
|
||||
|
||||
FetchingData = false;
|
||||
CrunchyrollManager.Instance.History.SortItems();
|
||||
}
|
||||
|
||||
private async Task AddMissingToQueue(){
|
||||
var tasks = CrunchyrollManager.Instance.HistoryList
|
||||
.Select(item => item.AddNewMissingToDownloads());
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
|
||||
|
||||
while (QueueManager.Instance.Queue.Any(e => e.DownloadProgress != null && e.DownloadProgress.Done != true)){
|
||||
Console.WriteLine("Waiting for downloads to complete...");
|
||||
await Task.Delay(2000); // Wait for 2 second before checking again
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async void Init(){
|
||||
CrunchyrollManager.Instance.InitOptions();
|
||||
|
||||
UpdateAvailable = await Updater.Instance.CheckForUpdatesAsync();
|
||||
|
||||
if (CrunchyrollManager.Instance.CrunOptions.AccentColor != null && !string.IsNullOrEmpty(CrunchyrollManager.Instance.CrunOptions.AccentColor)){
|
||||
if (_faTheme != null) _faTheme.CustomAccentColor = Color.Parse(CrunchyrollManager.Instance.CrunOptions.AccentColor);
|
||||
}
|
||||
|
||||
if (_faTheme != null && Application.Current != null){
|
||||
if (CrunchyrollManager.Instance.CrunOptions.Theme == "System"){
|
||||
_faTheme.PreferSystemTheme = true;
|
||||
} else if (CrunchyrollManager.Instance.CrunOptions.Theme == "Dark"){
|
||||
_faTheme.PreferSystemTheme = false;
|
||||
Application.Current.RequestedThemeVariant = ThemeVariant.Dark;
|
||||
} else{
|
||||
_faTheme.PreferSystemTheme = false;
|
||||
Application.Current.RequestedThemeVariant = ThemeVariant.Light;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
await CrunchyrollManager.Instance.Init();
|
||||
|
||||
FinishedLoading = true;
|
||||
|
||||
await WorkOffArgsTasks();
|
||||
}
|
||||
|
||||
private async Task WorkOffArgsTasks(){
|
||||
if (taskQueue.Count == 0){
|
||||
return;
|
||||
}
|
||||
|
||||
while (taskQueue.Count > 0){
|
||||
var task = taskQueue.Dequeue();
|
||||
await task(); // Execute the task asynchronously
|
||||
}
|
||||
|
||||
Console.WriteLine("All tasks are completed.");
|
||||
|
||||
if (exitOnTaskFinish){
|
||||
Console.WriteLine("Exiting...");
|
||||
IClassicDesktopStyleApplicationLifetime? lifetime = (IClassicDesktopStyleApplicationLifetime)Application.Current?.ApplicationLifetime;
|
||||
if (lifetime != null){
|
||||
lifetime.Shutdown();
|
||||
} else{
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void CleanUpOldUpdater(){
|
||||
string backupFilePath = Path.Combine(Directory.GetCurrentDirectory(), "Updater.exe.bak");
|
||||
|
||||
if (File.Exists(backupFilePath)){
|
||||
try{
|
||||
File.Delete(backupFilePath);
|
||||
Console.WriteLine($"Deleted old updater file: {backupFilePath}");
|
||||
} catch (Exception ex){
|
||||
Console.Error.WriteLine($"Failed to delete old updater file: {ex.Message}");
|
||||
}
|
||||
} else{
|
||||
Console.WriteLine("No old updater file found to delete.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using Avalonia;
|
||||
using System.Linq;
|
||||
|
||||
namespace CRD;
|
||||
|
||||
|
|
@ -8,13 +9,29 @@ sealed class Program{
|
|||
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
|
||||
// yet and stuff might break.
|
||||
[STAThread]
|
||||
public static void Main(string[] args) => BuildAvaloniaApp()
|
||||
.StartWithClassicDesktopLifetime(args);
|
||||
public static void Main(string[] args){
|
||||
var isHeadless = args.Contains("--headless");
|
||||
|
||||
BuildAvaloniaApp(isHeadless).StartWithClassicDesktopLifetime(args);
|
||||
}
|
||||
|
||||
// Avalonia configuration, don't remove; also used by visual designer.
|
||||
public static AppBuilder BuildAvaloniaApp()
|
||||
=> AppBuilder.Configure<App>()
|
||||
// public static AppBuilder BuildAvaloniaApp()
|
||||
// => AppBuilder.Configure<App>()
|
||||
// .UsePlatformDetect()
|
||||
// .WithInterFont()
|
||||
// .LogToTrace();
|
||||
|
||||
public static AppBuilder BuildAvaloniaApp(bool isHeadless){
|
||||
var builder = AppBuilder.Configure<App>()
|
||||
.UsePlatformDetect()
|
||||
.WithInterFont()
|
||||
.LogToTrace();
|
||||
|
||||
if (isHeadless){
|
||||
Console.WriteLine("Running in headless mode...");
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
|
@ -73,7 +73,10 @@ public class Widevine{
|
|||
}
|
||||
|
||||
public async Task<List<ContentKey>> getKeys(string? pssh, string licenseServer, Dictionary<string, string> authData){
|
||||
if (pssh == null || !canDecrypt) return new List<ContentKey>();
|
||||
if (pssh == null || !canDecrypt){
|
||||
Console.Error.WriteLine("Missing pssh or cdm files");
|
||||
return new List<ContentKey>();
|
||||
}
|
||||
|
||||
try{
|
||||
byte[] psshBuffer = Convert.FromBase64String(pssh);
|
||||
|
|
|
|||
61
CRD/Utils/Ffmpeg Encoding/FfmpegEncoding.cs
Normal file
61
CRD/Utils/Ffmpeg Encoding/FfmpegEncoding.cs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
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 } ,
|
||||
|
||||
// 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 } ,
|
||||
|
||||
// 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 } ,
|
||||
};
|
||||
|
||||
public static VideoPreset? GetPreset(string presetName){
|
||||
var preset = presets.FirstOrDefault(x => x.PresetName == presetName);
|
||||
if (preset != null){
|
||||
return preset;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine($"Preset {presetName} not found.");
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void AddPreset(VideoPreset preset){
|
||||
if (presets.Exists(x => x.PresetName == preset.PresetName)){
|
||||
Console.Error.WriteLine($"Preset {preset.PresetName} already exists.");
|
||||
return;
|
||||
}
|
||||
|
||||
presets.Add(preset);
|
||||
}
|
||||
}
|
||||
|
||||
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>();
|
||||
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ public class CfgManager{
|
|||
public static readonly string PathWIDEVINE_DIR = Path.Combine(WorkingDirectory, "widevine");
|
||||
|
||||
public static readonly string PathVIDEOS_DIR = Path.Combine(WorkingDirectory, "video");
|
||||
public static readonly string PathENCODING_PRESETS_DIR = Path.Combine(WorkingDirectory, "presets");
|
||||
public static readonly string PathTEMP_DIR = Path.Combine(WorkingDirectory, "temp");
|
||||
public static readonly string PathFONTS_DIR = Path.Combine(WorkingDirectory, "video");
|
||||
|
||||
|
|
@ -215,12 +216,12 @@ public class CfgManager{
|
|||
return;
|
||||
}
|
||||
|
||||
WriteJsonToFile(PathCrHistory, CrunchyrollManager.Instance.HistoryList);
|
||||
WriteJsonToFileCompressed(PathCrHistory, CrunchyrollManager.Instance.HistoryList);
|
||||
}
|
||||
|
||||
private static object fileLock = new object();
|
||||
|
||||
public static void WriteJsonToFile(string pathToFile, object obj){
|
||||
public static void WriteJsonToFileCompressed(string pathToFile, object obj){
|
||||
try{
|
||||
// Check if the directory exists; if not, create it.
|
||||
string directoryPath = Path.GetDirectoryName(pathToFile);
|
||||
|
|
@ -241,6 +242,27 @@ public class CfgManager{
|
|||
Console.Error.WriteLine($"An error occurred: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void WriteJsonToFile(string pathToFile, object obj){
|
||||
try{
|
||||
// Check if the directory exists; if not, create it.
|
||||
string directoryPath = Path.GetDirectoryName(pathToFile);
|
||||
if (!Directory.Exists(directoryPath)){
|
||||
Directory.CreateDirectory(directoryPath);
|
||||
}
|
||||
|
||||
lock (fileLock){
|
||||
using (var fileStream = new FileStream(pathToFile, FileMode.Create, FileAccess.Write))
|
||||
using (var streamWriter = new StreamWriter(fileStream))
|
||||
using (var jsonWriter = new JsonTextWriter(streamWriter){ Formatting = Formatting.Indented }){
|
||||
var serializer = new JsonSerializer();
|
||||
serializer.Serialize(jsonWriter, obj);
|
||||
}
|
||||
}
|
||||
} catch (Exception ex){
|
||||
Console.Error.WriteLine($"An error occurred: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public static string DecompressJsonFile(string pathToFile){
|
||||
try{
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ public class FileNameManager{
|
|||
|
||||
if (variable == null){
|
||||
Console.Error.WriteLine($"[ERROR] Found variable '{match}' in fileName but no values was internally found!");
|
||||
input = input.Replace(match, "");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -110,16 +111,16 @@ public class FileNameManager{
|
|||
}
|
||||
|
||||
|
||||
public static void DeleteEmptyFolders(string rootFolderPath){
|
||||
public static void DeleteEmptyFolders(string rootFolderPath, bool deleteRootIfEmpty = true){
|
||||
if (string.IsNullOrEmpty(rootFolderPath) || !Directory.Exists(rootFolderPath)){
|
||||
Console.WriteLine("Invalid directory path.");
|
||||
return;
|
||||
}
|
||||
|
||||
DeleteEmptyFoldersRecursive(rootFolderPath, isRoot: true);
|
||||
DeleteEmptyFoldersRecursive(rootFolderPath, isRoot: true, deleteRootIfEmpty);
|
||||
}
|
||||
|
||||
private static bool DeleteEmptyFoldersRecursive(string folderPath, bool isRoot = false){
|
||||
private static bool DeleteEmptyFoldersRecursive(string folderPath, bool isRoot = false, bool deleteRootIfEmpty = true){
|
||||
bool isFolderEmpty = true;
|
||||
|
||||
try{
|
||||
|
|
@ -137,6 +138,12 @@ public class FileNameManager{
|
|||
return true;
|
||||
}
|
||||
|
||||
if (isRoot && deleteRootIfEmpty && isFolderEmpty && Directory.GetFiles(folderPath).Length == 0){
|
||||
Directory.Delete(folderPath);
|
||||
Console.WriteLine($"Deleted empty root folder: {folderPath}");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (Exception ex){
|
||||
Console.WriteLine($"An error occurred while deleting folder {folderPath}: {ex.Message}");
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@ using System.Runtime.Serialization;
|
|||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Media.Imaging;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils.Ffmpeg_Encoding;
|
||||
using CRD.Utils.JsonConv;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.Crunchyroll.Music;
|
||||
|
|
@ -288,6 +290,74 @@ public class Helpers{
|
|||
}
|
||||
}
|
||||
|
||||
public static async Task<(bool IsOk, int ErrorCode)> RunFFmpegWithPresetAsync(string inputFilePath, VideoPreset preset){
|
||||
try{
|
||||
string outputExtension = Path.GetExtension(inputFilePath);
|
||||
string directory = Path.GetDirectoryName(inputFilePath);
|
||||
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(inputFilePath);
|
||||
string tempOutputFilePath = Path.Combine(directory, $"{fileNameWithoutExtension}_output{outputExtension}");
|
||||
|
||||
string additionalParams = string.Join(" ", preset.AdditionalParameters);
|
||||
string qualityOption;
|
||||
if (preset.Codec == "h264_nvenc" || preset.Codec == "hevc_nvenc"){
|
||||
qualityOption = $"-cq {preset.Crf}"; // For NVENC
|
||||
} else if (preset.Codec == "h264_qsv" || preset.Codec == "hevc_qsv"){
|
||||
qualityOption = $"-global_quality {preset.Crf}"; // For Intel QSV
|
||||
} else if (preset.Codec == "h264_amf" || preset.Codec == "hevc_amf"){
|
||||
qualityOption = $"-qp {preset.Crf}"; // For AMD VCE
|
||||
} else{
|
||||
qualityOption = $"-crf {preset.Crf}"; // For software codecs like libx264/libx265
|
||||
}
|
||||
|
||||
string ffmpegCommand = $"-loglevel warning -i \"{inputFilePath}\" -c:v {preset.Codec} {qualityOption} -vf \"scale={preset.Resolution},fps={preset.FrameRate}\" {additionalParams} \"{tempOutputFilePath}\"";
|
||||
using (var process = new Process()){
|
||||
process.StartInfo.FileName = CfgManager.PathFFMPEG;
|
||||
process.StartInfo.Arguments = ffmpegCommand;
|
||||
process.StartInfo.RedirectStandardOutput = true;
|
||||
process.StartInfo.RedirectStandardError = true;
|
||||
process.StartInfo.UseShellExecute = false;
|
||||
process.StartInfo.CreateNoWindow = true;
|
||||
|
||||
process.OutputDataReceived += (sender, e) => {
|
||||
if (!string.IsNullOrEmpty(e.Data)){
|
||||
Console.WriteLine(e.Data);
|
||||
}
|
||||
};
|
||||
|
||||
process.ErrorDataReceived += (sender, e) => {
|
||||
if (!string.IsNullOrEmpty(e.Data)){
|
||||
Console.Error.WriteLine($"{e.Data}");
|
||||
}
|
||||
};
|
||||
|
||||
process.Start();
|
||||
process.BeginOutputReadLine();
|
||||
process.BeginErrorReadLine();
|
||||
|
||||
await process.WaitForExitAsync();
|
||||
|
||||
bool isSuccess = process.ExitCode == 0;
|
||||
|
||||
if (isSuccess){
|
||||
// Delete the original input file
|
||||
File.Delete(inputFilePath);
|
||||
|
||||
// Rename the output file to the original name
|
||||
File.Move(tempOutputFilePath, inputFilePath);
|
||||
} else{
|
||||
// If something went wrong, delete the temporary output file
|
||||
File.Delete(tempOutputFilePath);
|
||||
Console.Error.WriteLine("FFmpeg processing failed.");
|
||||
}
|
||||
|
||||
return (IsOk: isSuccess, ErrorCode: process.ExitCode);
|
||||
}
|
||||
} catch (Exception ex){
|
||||
Console.Error.WriteLine($"An error occurred: {ex.Message}");
|
||||
return (IsOk: false, ErrorCode: -1);
|
||||
}
|
||||
}
|
||||
|
||||
public static double CalculateCosineSimilarity(string text1, string text2){
|
||||
var vector1 = ComputeWordFrequency(text1);
|
||||
var vector2 = ComputeWordFrequency(text2);
|
||||
|
|
@ -368,13 +438,25 @@ public class Helpers{
|
|||
}
|
||||
|
||||
|
||||
public static async Task<Bitmap?> LoadImage(string imageUrl){
|
||||
public static async Task<Bitmap?> LoadImage(string imageUrl,int desiredWidth = 0,int desiredHeight = 0){
|
||||
try{
|
||||
using (var client = new HttpClient()){
|
||||
var response = await client.GetAsync(imageUrl);
|
||||
response.EnsureSuccessStatusCode();
|
||||
using (var stream = await response.Content.ReadAsStreamAsync()){
|
||||
return new Bitmap(stream);
|
||||
|
||||
var bitmap = new Bitmap(stream);
|
||||
|
||||
if (desiredWidth != 0 && desiredHeight != 0){
|
||||
var scaledBitmap = bitmap.CreateScaledBitmap(new PixelSize(desiredWidth, desiredHeight));
|
||||
|
||||
bitmap.Dispose();
|
||||
|
||||
return scaledBitmap;
|
||||
}
|
||||
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
} catch (Exception ex){
|
||||
|
|
|
|||
|
|
@ -38,10 +38,42 @@ public class HttpClientReq{
|
|||
private HttpClient client;
|
||||
private Dictionary<string, CookieCollection> cookieStore;
|
||||
|
||||
private HttpClientHandler handler;
|
||||
|
||||
|
||||
public HttpClientReq(){
|
||||
cookieStore = new Dictionary<string, CookieCollection>();
|
||||
|
||||
client = new HttpClient(CreateHttpClientHandler());
|
||||
IWebProxy systemProxy = WebRequest.DefaultWebProxy;
|
||||
|
||||
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}");
|
||||
client = new HttpClient(handler);
|
||||
} else if (systemProxy != null){
|
||||
Uri testUri = new Uri("https://icanhazip.com");
|
||||
Uri? proxyUri = systemProxy.GetProxy(testUri);
|
||||
|
||||
if (proxyUri != null && proxyUri != testUri){
|
||||
if (proxyUri is{ Host: "127.0.0.1", Port: 7890 }){
|
||||
Console.Error.WriteLine($"Proxy is set: {proxyUri}");
|
||||
handler = CreateHandler(true);
|
||||
} else{
|
||||
Console.Error.WriteLine("No proxy will be used.");
|
||||
handler = CreateHandler(false);
|
||||
}
|
||||
|
||||
client = new HttpClient(handler);
|
||||
} else{
|
||||
Console.Error.WriteLine("No proxy is being used.");
|
||||
client = new HttpClient(CreateHttpClientHandler());
|
||||
}
|
||||
} else{
|
||||
Console.Error.WriteLine("No proxy is being used.");
|
||||
client = new HttpClient(CreateHttpClientHandler());
|
||||
}
|
||||
|
||||
// client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0");
|
||||
client.DefaultRequestHeaders.UserAgent.ParseAdd("Crunchyroll/1.9.0 Nintendo Switch/18.1.0.0 UE4/4.27");
|
||||
|
|
@ -70,9 +102,24 @@ public class HttpClientReq{
|
|||
};
|
||||
}
|
||||
|
||||
private HttpClientHandler CreateHandler(bool useProxy, string? proxyHost = null, int proxyPort = 0){
|
||||
var handler = new HttpClientHandler{
|
||||
CookieContainer = new CookieContainer(),
|
||||
UseCookies = true,
|
||||
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate | DecompressionMethods.Brotli,
|
||||
UseProxy = useProxy
|
||||
};
|
||||
|
||||
public void SetETPCookie(string refresh_token){
|
||||
// var cookie = new Cookie("etp_rt", refresh_token){
|
||||
if (useProxy && proxyHost != null){
|
||||
handler.Proxy = new WebProxy($"http://{proxyHost}:{proxyPort}");
|
||||
}
|
||||
|
||||
return handler;
|
||||
}
|
||||
|
||||
|
||||
public void SetETPCookie(string refreshToken){
|
||||
// var cookie = new Cookie("etp_rt", refreshToken){
|
||||
// Domain = "crunchyroll.com",
|
||||
// Path = "/",
|
||||
// };
|
||||
|
|
@ -81,8 +128,11 @@ public class HttpClientReq{
|
|||
// Domain = "crunchyroll.com",
|
||||
// Path = "/",
|
||||
// };
|
||||
//
|
||||
// handler.CookieContainer.Add(cookie);
|
||||
// handler.CookieContainer.Add(cookie2);
|
||||
|
||||
AddCookie("crunchyroll.com", new Cookie("etp_rt", refresh_token));
|
||||
AddCookie("crunchyroll.com", new Cookie("etp_rt", refreshToken));
|
||||
AddCookie("crunchyroll.com", new Cookie("c_locale", "en-US"));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -135,8 +135,7 @@ public class Merger{
|
|||
audioIndex++;
|
||||
}
|
||||
|
||||
args.Add("-acodec libmp3lame");
|
||||
args.Add("-ab 192k");
|
||||
args.Add("-c:a copy");
|
||||
args.Add($"\"{options.Output}\"");
|
||||
return string.Join(" ", args);
|
||||
}
|
||||
|
|
@ -190,6 +189,7 @@ public class Merger{
|
|||
|
||||
if (options.Subtitles.Count > 0){
|
||||
foreach (var subObj in options.Subtitles){
|
||||
bool isForced = false;
|
||||
if (subObj.Delay.HasValue){
|
||||
double delay = subObj.Delay ?? 0;
|
||||
args.Add($"--sync 0:{delay}");
|
||||
|
|
@ -206,10 +206,19 @@ public class Merger{
|
|||
args.Add("--default-track 0");
|
||||
if (CrunchyrollManager.Instance.CrunOptions.DefaultSubForcedDisplay){
|
||||
args.Add("--forced-track 0:yes");
|
||||
isForced = true;
|
||||
}
|
||||
} else{
|
||||
args.Add("--default-track 0:0");
|
||||
}
|
||||
|
||||
if (subObj.ClosedCaption == true && CrunchyrollManager.Instance.CrunOptions.CcSubsMuxingFlag){
|
||||
args.Add("--hearing-impaired-flag 0:yes");
|
||||
}
|
||||
|
||||
if (subObj.Signs == true && CrunchyrollManager.Instance.CrunOptions.SignsSubsAsForced && !isForced){
|
||||
args.Add("--forced-track 0:yes");
|
||||
}
|
||||
|
||||
args.Add($"\"{subObj.File}\"");
|
||||
}
|
||||
|
|
@ -245,13 +254,21 @@ public class Merger{
|
|||
|
||||
|
||||
public async Task<double> ProcessVideo(string baseVideoPath, string compareVideoPath){
|
||||
var tempDir = Path.GetTempPath(); //TODO - maybe move this out of temp
|
||||
var baseFramesDir = Path.Combine(tempDir, "base_frames");
|
||||
var compareFramesDir = Path.Combine(tempDir, "compare_frames");
|
||||
|
||||
Directory.CreateDirectory(baseFramesDir);
|
||||
Directory.CreateDirectory(compareFramesDir);
|
||||
|
||||
string baseFramesDir;
|
||||
string compareFramesDir;
|
||||
try{
|
||||
var tempDir = CfgManager.PathTEMP_DIR;
|
||||
baseFramesDir = Path.Combine(tempDir, "base_frames");
|
||||
compareFramesDir = Path.Combine(tempDir, "compare_frames");
|
||||
|
||||
Directory.CreateDirectory(baseFramesDir);
|
||||
Directory.CreateDirectory(compareFramesDir);
|
||||
|
||||
} catch (Exception e){
|
||||
Console.Error.WriteLine(e);
|
||||
return 0;
|
||||
}
|
||||
|
||||
var extractFramesBase = await SyncingHelper.ExtractFrames(baseVideoPath, baseFramesDir, 0, 60);
|
||||
var extractFramesCompare = await SyncingHelper.ExtractFrames(compareVideoPath, compareFramesDir, 0, 60);
|
||||
|
||||
|
|
|
|||
|
|
@ -69,9 +69,18 @@ public class CrDownloadOptions{
|
|||
[YamlMember(Alias = "include_signs_subs", ApplyNamingConventions = false)]
|
||||
public bool IncludeSignsSubs{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "mux_signs_subs_flag", ApplyNamingConventions = false)]
|
||||
public bool SignsSubsAsForced{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "include_cc_subs", ApplyNamingConventions = false)]
|
||||
public bool IncludeCcSubs{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "cc_subs_font", ApplyNamingConventions = false)]
|
||||
public string? CcSubsFont{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "mux_cc_subs_flag", ApplyNamingConventions = false)]
|
||||
public bool CcSubsMuxingFlag{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "mux_mp4", ApplyNamingConventions = false)]
|
||||
public bool Mp4{ get; set; }
|
||||
|
||||
|
|
@ -117,11 +126,17 @@ public class CrDownloadOptions{
|
|||
[YamlMember(Alias = "keep_dubs_seperate", ApplyNamingConventions = false)]
|
||||
public bool KeepDubsSeperate{ get; set; }
|
||||
|
||||
[YamlIgnore]
|
||||
public bool? Skipmux{ get; set; }
|
||||
[YamlMember(Alias = "mux_skip_muxing", ApplyNamingConventions = false)]
|
||||
public bool SkipMuxing{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "mux_sync_dubs", ApplyNamingConventions = false)]
|
||||
public bool SyncTiming{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "encode_enabled", ApplyNamingConventions = false)]
|
||||
public bool IsEncodeEnabled{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "encode_preset", ApplyNamingConventions = false)]
|
||||
public string? EncodingPresetName{ get; set; }
|
||||
|
||||
[YamlIgnore]
|
||||
public bool Nocleanup{ get; set; }
|
||||
|
|
@ -192,4 +207,12 @@ public class CrDownloadOptions{
|
|||
[YamlMember(Alias = "download_speed_limit", ApplyNamingConventions = false)]
|
||||
public int DownloadSpeedLimit{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "proxy_enabled", ApplyNamingConventions = false)]
|
||||
public bool ProxyEnabled{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "proxy_host", ApplyNamingConventions = false)]
|
||||
public string? ProxyHost{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "proxy_port", ApplyNamingConventions = false)]
|
||||
public int ProxyPort{ get; set; }
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ namespace CRD.Utils.Structs.Crunchyroll;
|
|||
|
||||
public class PlaybackData{
|
||||
public int Total{ get; set; }
|
||||
public List<Dictionary<string, Dictionary<string, StreamDetails>>>? Data{ get; set; }
|
||||
public Dictionary<string, StreamDetails>? Data{ get; set; }
|
||||
public PlaybackMeta? Meta{ get; set; }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ public class StreamError{
|
|||
public string Error{ get; set; }
|
||||
|
||||
[JsonPropertyName("activeStreams")]
|
||||
public List<ActiveStream> ActiveStreams{ get; set; }
|
||||
public List<ActiveStream> ActiveStreams{ get; set; } = new ();
|
||||
|
||||
public static StreamError? FromJson(string json){
|
||||
try{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.ComponentModel;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
|
|
@ -43,6 +44,12 @@ public class HistoryEpisode : INotifyPropertyChanged{
|
|||
|
||||
[JsonProperty("sonarr_absolut_number")]
|
||||
public string? SonarrAbsolutNumber{ get; set; }
|
||||
|
||||
[JsonProperty("history_episode_available_soft_subs")]
|
||||
public List<string> HistoryEpisodeAvailableSoftSubs{ get; set; } =[];
|
||||
|
||||
[JsonProperty("history_episode_available_dub_lang")]
|
||||
public List<string> HistoryEpisodeAvailableDubLang{ get; set; } =[];
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
|
|
|
|||
|
|
@ -317,6 +317,7 @@ public class HistorySeries : INotifyPropertyChanged{
|
|||
}
|
||||
|
||||
public async Task FetchData(string? seasonId){
|
||||
Console.WriteLine($"Fetching Data for: {SeriesTitle}");
|
||||
FetchingData = true;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FetchingData)));
|
||||
await CrunchyrollManager.Instance.History.CRUpdateSeries(SeriesId, seasonId);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ public class Languages{
|
|||
new(){ CrLocale = "en-US", Locale = "en", Code = "eng", Name = "English" },
|
||||
new(){ CrLocale = "de-DE", Locale = "de", Code = "deu", Name = "German" },
|
||||
new(){ CrLocale = "en-IN", Locale = "en-IN", Code = "eng", Name = "English (India)" },
|
||||
new(){ CrLocale = "es-LA", Locale = "es-419", Code = "spa", Name = "Spanish", Language = "Latin American Spanish" },
|
||||
new(){ CrLocale = "es-LA", Locale = "es-LA", Code = "spa", Name = "Spanish", Language = "Latin American Spanish" },
|
||||
new(){ CrLocale = "es-419", Locale = "es-419", Code = "spa-419", Name = "Spanish", Language = "Latin American Spanish" },
|
||||
new(){ CrLocale = "es-ES", Locale = "es-ES", Code = "spa-ES", Name = "Castilian", Language = "European Spanish" },
|
||||
new(){ CrLocale = "pt-BR", Locale = "pt-BR", Code = "por", Name = "Portuguese", Language = "Brazilian Portuguese" },
|
||||
|
|
@ -27,6 +27,7 @@ public class Languages{
|
|||
// new(){ locale = "zh", code = "cmn", name = "Chinese (Mandarin, PRC)" },
|
||||
new(){ CrLocale = "zh-CN", Locale = "zh-CN", Code = "zho", Name = "Chinese (Mainland China)" },
|
||||
new(){ CrLocale = "zh-TW", Locale = "zh-TW", Code = "chi", Name = "Chinese (Taiwan)" },
|
||||
new(){ CrLocale = "zh-HK", Locale = "zh-HK", Code = "zho-HK", Name = "Chinese (Hong Kong)" },
|
||||
new(){ CrLocale = "ko-KR", Locale = "ko", Code = "kor", Name = "Korean" },
|
||||
new(){ CrLocale = "ca-ES", Locale = "ca-ES", Code = "cat", Name = "Catalan" },
|
||||
new(){ CrLocale = "pl-PL", Locale = "pl-PL", Code = "pol", Name = "Polish" },
|
||||
|
|
@ -36,9 +37,28 @@ public class Languages{
|
|||
new(){ CrLocale = "vi-VN", Locale = "vi-VN", Code = "vie", Name = "Vietnamese", Language = "Tiếng Việt" },
|
||||
new(){ CrLocale = "id-ID", Locale = "id-ID", Code = "ind", Name = "Indonesian", Language = "Bahasa Indonesia" },
|
||||
new(){ CrLocale = "te-IN", Locale = "te-IN", Code = "tel", Name = "Telugu (India)", Language = "తెలుగు" },
|
||||
new(){ CrLocale = "id-ID", Locale = "id", Code = "in", Name = "Indonesian " }
|
||||
};
|
||||
|
||||
|
||||
public static List<string> SortListByLangList(List<string> langList){
|
||||
var orderMap = languages.Select((value, index) => new { Value = value.CrLocale, Index = index })
|
||||
.ToDictionary(x => x.Value, x => x.Index);
|
||||
langList.Sort((x, y) =>
|
||||
{
|
||||
bool xExists = orderMap.ContainsKey(x);
|
||||
bool yExists = orderMap.ContainsKey(y);
|
||||
|
||||
if (xExists && yExists)
|
||||
return orderMap[x].CompareTo(orderMap[y]); // Sort by main list order
|
||||
else if (xExists)
|
||||
return -1; // x comes before any missing value
|
||||
else if (yExists)
|
||||
return 1; // y comes before any missing value
|
||||
else
|
||||
return string.Compare(x, y); // Sort alphabetically or by another logic for missing values
|
||||
});
|
||||
|
||||
return langList;
|
||||
}
|
||||
|
||||
public static LanguageItem FixAndFindCrLc(string cr_locale){
|
||||
if (string.IsNullOrEmpty(cr_locale)){
|
||||
|
|
@ -54,7 +74,7 @@ public class Languages{
|
|||
string fileName = $"{fnOutput}";
|
||||
|
||||
if (addIndexAndLangCode){
|
||||
fileName += $".{subsIndex}.{langItem.Code}";
|
||||
fileName += $".{subsIndex}.{langItem.CrLocale}";
|
||||
}
|
||||
|
||||
//removed .{langItem.language} from file name at end
|
||||
|
|
|
|||
26
CRD/Utils/UI/UiListToStringConverter.cs
Normal file
26
CRD/Utils/UI/UiListToStringConverter.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Avalonia.Data.Converters;
|
||||
|
||||
namespace CRD.Utils.UI;
|
||||
|
||||
public class UiListToStringConverter : IValueConverter{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture){
|
||||
if (value is List<string> list){
|
||||
return string.Join(", ", list);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){
|
||||
if (value is string str){
|
||||
return str.Split(new[]{ ", " }, StringSplitOptions.None).ToList();
|
||||
}
|
||||
|
||||
return new List<string>();
|
||||
}
|
||||
}
|
||||
|
|
@ -11,13 +11,14 @@ using Newtonsoft.Json;
|
|||
namespace CRD.Utils.Updater;
|
||||
|
||||
public class Updater : INotifyPropertyChanged{
|
||||
|
||||
public double progress = 0;
|
||||
|
||||
#region Singelton
|
||||
|
||||
|
||||
private static Updater? _instance;
|
||||
private static readonly object Padlock = new();
|
||||
|
||||
public double progress = 0;
|
||||
|
||||
|
||||
public static Updater Instance{
|
||||
get{
|
||||
if (_instance == null){
|
||||
|
|
@ -48,7 +49,9 @@ public class Updater : INotifyPropertyChanged{
|
|||
|
||||
public async Task<bool> CheckForUpdatesAsync(){
|
||||
try{
|
||||
using (var client = new HttpClient()){
|
||||
HttpClientHandler handler = new HttpClientHandler();
|
||||
handler.UseProxy = false;
|
||||
using (var client = new HttpClient(handler)){
|
||||
client.DefaultRequestHeaders.Add("User-Agent", "C# App");
|
||||
var response = await client.GetStringAsync(apiEndpoint);
|
||||
var releaseInfo = Helpers.Deserialize<dynamic>(response,null);
|
||||
|
|
|
|||
|
|
@ -4,9 +4,8 @@ using System.Collections.ObjectModel;
|
|||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Runtime;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media.Imaging;
|
||||
|
|
@ -17,11 +16,7 @@ using CRD.Downloader.Crunchyroll;
|
|||
using CRD.Utils;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.Crunchyroll.Music;
|
||||
using CRD.Views;
|
||||
using DynamicData;
|
||||
using FluentAvalonia.Core;
|
||||
using ReactiveUI;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace CRD.ViewModels;
|
||||
|
||||
|
|
@ -59,9 +54,9 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
[ObservableProperty]
|
||||
private bool _searchPopupVisible = false;
|
||||
|
||||
public ObservableCollection<ItemModel> Items{ get; } = new();
|
||||
public ObservableCollection<ItemModel> Items{ get; set; } = new();
|
||||
public ObservableCollection<CrBrowseSeries> SearchItems{ get; set; } = new();
|
||||
public ObservableCollection<ItemModel> SelectedItems{ get; } = new();
|
||||
public ObservableCollection<ItemModel> SelectedItems{ get; set;} = new();
|
||||
|
||||
[ObservableProperty]
|
||||
public CrBrowseSeries _selectedSearchItem;
|
||||
|
|
@ -69,7 +64,7 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
[ObservableProperty]
|
||||
public ComboBoxItem _currentSelectedSeason;
|
||||
|
||||
public ObservableCollection<ComboBoxItem> SeasonList{ get; } = new();
|
||||
public ObservableCollection<ComboBoxItem> SeasonList{ get;set; } = new();
|
||||
|
||||
private Dictionary<string, List<ItemModel>> episodesBySeason = new();
|
||||
|
||||
|
|
@ -80,9 +75,7 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
private CrunchyMusicVideoList? currentMusicVideoList;
|
||||
|
||||
private bool CurrentSeasonFullySelected = false;
|
||||
|
||||
private readonly SemaphoreSlim _updateSearchSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
|
||||
public AddDownloadPageViewModel(){
|
||||
SelectedItems.CollectionChanged += OnSelectedItemsChanged;
|
||||
}
|
||||
|
|
@ -95,7 +88,7 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
return;
|
||||
}
|
||||
|
||||
var searchResults = await CrunchyrollManager.Instance.CrSeries.Search(value, CrunchyrollManager.Instance.CrunOptions.HistoryLang);
|
||||
var searchResults = await CrunchyrollManager.Instance.CrSeries.Search(value, CrunchyrollManager.Instance.CrunOptions.HistoryLang, true);
|
||||
|
||||
var searchItems = searchResults?.Data?.First().Items;
|
||||
SearchItems.Clear();
|
||||
|
|
@ -250,6 +243,11 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
ButtonEnabled = false;
|
||||
SearchVisible = true;
|
||||
SlectSeasonVisible = false;
|
||||
|
||||
//TODO - find a better way to reduce ram usage
|
||||
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
|
||||
GC.Collect();
|
||||
|
||||
}
|
||||
|
||||
private async Task HandleUrlInputAsync(){
|
||||
|
|
@ -335,7 +333,7 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
|
||||
var list = await CrunchyrollManager.Instance.CrSeries.ListSeriesId(
|
||||
id, DetermineLocale(locale),
|
||||
new CrunchyMultiDownload(CrunchyrollManager.Instance.CrunOptions.DubLang, true));
|
||||
new CrunchyMultiDownload(CrunchyrollManager.Instance.CrunOptions.DubLang, true), true);
|
||||
|
||||
SetLoadingState(false);
|
||||
|
||||
|
|
@ -428,6 +426,7 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
}
|
||||
|
||||
partial void OnCurrentSelectedSeasonChanging(ComboBoxItem? oldValue, ComboBoxItem newValue){
|
||||
if(SelectedItems == null) return;
|
||||
foreach (var selectedItem in SelectedItems){
|
||||
if (!selectedEpisodes.Contains(selectedItem.AbsolutNum)){
|
||||
selectedEpisodes.Add(selectedItem.AbsolutNum);
|
||||
|
|
@ -444,6 +443,8 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
}
|
||||
|
||||
private void OnSelectedItemsChanged(object? sender, NotifyCollectionChangedEventArgs e){
|
||||
if(Items == null) return;
|
||||
|
||||
CurrentSeasonFullySelected = Items.All(item => SelectedItems.Contains(item));
|
||||
|
||||
if (CurrentSeasonFullySelected){
|
||||
|
|
@ -510,7 +511,7 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
return await CrunchyrollManager.Instance.CrSeries.ListSeriesId(
|
||||
seriesId,
|
||||
locale,
|
||||
new CrunchyMultiDownload(CrunchyrollManager.Instance.CrunOptions.DubLang, true));
|
||||
new CrunchyMultiDownload(CrunchyrollManager.Instance.CrunOptions.DubLang, true), true);
|
||||
}
|
||||
|
||||
private void SearchPopulateEpisodesBySeason(){
|
||||
|
|
@ -518,6 +519,16 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
return;
|
||||
}
|
||||
|
||||
Items.Clear();
|
||||
SelectedItems.Clear();
|
||||
episodesBySeason.Clear();
|
||||
SeasonList.Clear();
|
||||
|
||||
//TODO - find a better way to reduce ram usage
|
||||
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
|
||||
GC.Collect();
|
||||
|
||||
|
||||
foreach (var episode in currentSeriesList.Value.List){
|
||||
var seasonKey = "S" + episode.Season;
|
||||
var episodeModel = new ItemModel(
|
||||
|
|
@ -584,6 +595,30 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
ButtonTextSelectSeason = "Select Season";
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose(){
|
||||
|
||||
foreach (var itemModel in Items){
|
||||
itemModel.ImageBitmap?.Dispose(); // Dispose the bitmap if it exists
|
||||
itemModel.ImageBitmap = null; // Nullify the reference to avoid lingering references
|
||||
}
|
||||
|
||||
// Clear collections and other managed resources
|
||||
Items.Clear();
|
||||
Items = null;
|
||||
SearchItems.Clear();
|
||||
SearchItems = null;
|
||||
SelectedItems.Clear();
|
||||
SelectedItems = null;
|
||||
SeasonList.Clear();
|
||||
SeasonList = null;
|
||||
episodesBySeason.Clear();
|
||||
episodesBySeason = null;
|
||||
selectedEpisodes.Clear();
|
||||
selectedEpisodes = null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class ItemModel(string id, string imageUrl, string description, string time, string title, string season, string episode, string absolutNum, List<string> availableAudios) : INotifyPropertyChanged{
|
||||
|
|
@ -605,7 +640,7 @@ public class ItemModel(string id, string imageUrl, string description, string ti
|
|||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
public async void LoadImage(string url){
|
||||
ImageBitmap = await Helpers.LoadImage(url);
|
||||
ImageBitmap = await Helpers.LoadImage(url,208,117);
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ImageBitmap)));
|
||||
}
|
||||
}
|
||||
|
|
@ -89,7 +89,7 @@ public partial class DownloadItemModel : INotifyPropertyChanged{
|
|||
if (epMeta.SelectedDubs == null || epMeta.SelectedDubs.Count < 1){
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
return epMeta.SelectedDubs.Aggregate("Dub: ", (current, crunOptionsDlDub) => current + (crunOptionsDlDub + " "));
|
||||
}
|
||||
|
||||
|
|
@ -196,18 +196,7 @@ public partial class DownloadItemModel : INotifyPropertyChanged{
|
|||
}
|
||||
|
||||
public async Task LoadImage(){
|
||||
try{
|
||||
using (var client = new HttpClient()){
|
||||
var response = await client.GetAsync(ImageUrl);
|
||||
response.EnsureSuccessStatusCode();
|
||||
using (var stream = await response.Content.ReadAsStreamAsync()){
|
||||
ImageBitmap = new Bitmap(stream);
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ImageBitmap)));
|
||||
}
|
||||
}
|
||||
} catch (Exception ex){
|
||||
// Handle exceptions
|
||||
Console.Error.WriteLine("Failed to load image: " + ex.Message);
|
||||
}
|
||||
ImageBitmap = await Helpers.LoadImage(ImageUrl, 208, 117);
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ImageBitmap)));
|
||||
}
|
||||
}
|
||||
|
|
@ -29,10 +29,10 @@ namespace CRD.ViewModels;
|
|||
public partial class HistoryPageViewModel : ViewModelBase{
|
||||
public ObservableCollection<HistorySeries> Items{ get; }
|
||||
public ObservableCollection<HistorySeries> FilteredItems{ get; }
|
||||
|
||||
|
||||
[ObservableProperty]
|
||||
private static bool _fetchingData;
|
||||
|
||||
private ProgramManager _programManager;
|
||||
|
||||
[ObservableProperty]
|
||||
private HistorySeries _selectedSeries;
|
||||
|
||||
|
|
@ -112,6 +112,9 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
private static string _progressText;
|
||||
|
||||
public HistoryPageViewModel(){
|
||||
|
||||
ProgramManager = ProgramManager.Instance;
|
||||
|
||||
if (CrunchyrollManager.Instance.CrunOptions.SonarrProperties != null){
|
||||
SonarrAvailable = CrunchyrollManager.Instance.CrunOptions.SonarrProperties.SonarrEnabled;
|
||||
} else{
|
||||
|
|
@ -317,7 +320,7 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
|
||||
[RelayCommand]
|
||||
public void NavToSeries(){
|
||||
if (FetchingData){
|
||||
if (ProgramManager.FetchingData){
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -326,21 +329,21 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
|
||||
[RelayCommand]
|
||||
public async Task RefreshAll(){
|
||||
FetchingData = true;
|
||||
RaisePropertyChanged(nameof(FetchingData));
|
||||
ProgramManager.FetchingData = true;
|
||||
RaisePropertyChanged(nameof(ProgramManager.FetchingData));
|
||||
foreach (var item in FilteredItems){
|
||||
item.SetFetchingData();
|
||||
}
|
||||
|
||||
for (int i = 0; i < FilteredItems.Count; i++){
|
||||
FetchingData = true;
|
||||
RaisePropertyChanged(nameof(FetchingData));
|
||||
ProgramManager.FetchingData = true;
|
||||
RaisePropertyChanged(nameof(ProgramManager.FetchingData));
|
||||
await FilteredItems[i].FetchData("");
|
||||
FilteredItems[i].UpdateNewEpisodes();
|
||||
}
|
||||
|
||||
FetchingData = false;
|
||||
RaisePropertyChanged(nameof(FetchingData));
|
||||
ProgramManager.FetchingData = false;
|
||||
RaisePropertyChanged(nameof(ProgramManager.FetchingData));
|
||||
CrunchyrollManager.Instance.History.SortItems();
|
||||
}
|
||||
|
||||
|
|
@ -367,7 +370,7 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
public async Task AddMissingSonarrSeriesToHistory(){
|
||||
SonarrOptionsOpen = false;
|
||||
AddingMissingSonarrSeries = true;
|
||||
FetchingData = true;
|
||||
ProgramManager.FetchingData = true;
|
||||
|
||||
var crInstance = CrunchyrollManager.Instance;
|
||||
|
||||
|
|
@ -413,7 +416,7 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
|
||||
ProgressText = "";
|
||||
AddingMissingSonarrSeries = false;
|
||||
FetchingData = false;
|
||||
ProgramManager.FetchingData = false;
|
||||
if (SelectedFilter != null){
|
||||
OnSelectedFilterChanged(SelectedFilter);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,75 +1,15 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.Chrome;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Styling;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils.Updater;
|
||||
using FluentAvalonia.Styling;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CRD.ViewModels;
|
||||
|
||||
public partial class MainWindowViewModel : ViewModelBase{
|
||||
private readonly FluentAvaloniaTheme _faTheme;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _updateAvailable = true;
|
||||
public ProgramManager _programManager;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _finishedLoading = false;
|
||||
|
||||
public MainWindowViewModel(){
|
||||
_faTheme = App.Current.Styles[0] as FluentAvaloniaTheme;
|
||||
|
||||
Init();
|
||||
|
||||
CleanUpOldUpdater();
|
||||
}
|
||||
|
||||
private void CleanUpOldUpdater(){
|
||||
string backupFilePath = Path.Combine(Directory.GetCurrentDirectory(), "Updater.exe.bak");
|
||||
|
||||
if (File.Exists(backupFilePath)){
|
||||
try{
|
||||
File.Delete(backupFilePath);
|
||||
Console.WriteLine($"Deleted old updater file: {backupFilePath}");
|
||||
} catch (Exception ex){
|
||||
Console.Error.WriteLine($"Failed to delete old updater file: {ex.Message}");
|
||||
}
|
||||
} else{
|
||||
Console.WriteLine("No old updater file found to delete.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async void Init(){
|
||||
UpdateAvailable = await Updater.Instance.CheckForUpdatesAsync();
|
||||
|
||||
CrunchyrollManager.Instance.InitOptions();
|
||||
|
||||
if (CrunchyrollManager.Instance.CrunOptions.AccentColor != null){
|
||||
_faTheme.CustomAccentColor = Color.Parse(CrunchyrollManager.Instance.CrunOptions.AccentColor);
|
||||
}
|
||||
|
||||
if (CrunchyrollManager.Instance.CrunOptions.Theme == "System"){
|
||||
_faTheme.PreferSystemTheme = true;
|
||||
} else if (CrunchyrollManager.Instance.CrunOptions.Theme == "Dark"){
|
||||
_faTheme.PreferSystemTheme = false;
|
||||
Application.Current.RequestedThemeVariant = ThemeVariant.Dark;
|
||||
} else{
|
||||
_faTheme.PreferSystemTheme = false;
|
||||
Application.Current.RequestedThemeVariant = ThemeVariant.Light;
|
||||
}
|
||||
|
||||
await CrunchyrollManager.Instance.Init();
|
||||
|
||||
FinishedLoading = true;
|
||||
public MainWindowViewModel(ProgramManager manager){
|
||||
ProgramManager = manager;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,15 +1,18 @@
|
|||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Shapes;
|
||||
using Avalonia.Platform.Storage;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.Files;
|
||||
using CRD.Utils.Sonarr;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.History;
|
||||
|
|
@ -18,6 +21,7 @@ using CRD.Views;
|
|||
using CRD.Views.Utils;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using ReactiveUI;
|
||||
using Path = Avalonia.Controls.Shapes.Path;
|
||||
|
||||
namespace CRD.ViewModels;
|
||||
|
||||
|
|
@ -42,6 +46,12 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
[ObservableProperty]
|
||||
private string _availableSubs;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _seriesFolderPath;
|
||||
|
||||
[ObservableProperty]
|
||||
public bool _seriesFolderPathExists;
|
||||
|
||||
public SeriesPageViewModel(){
|
||||
_selectedSeries = CrunchyrollManager.Instance.SelectedSeries;
|
||||
|
||||
|
|
@ -63,6 +73,58 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
|
||||
AvailableDubs = "Available Dubs: " + string.Join(", ", SelectedSeries.HistorySeriesAvailableDubLang);
|
||||
AvailableSubs = "Available Subs: " + string.Join(", ", SelectedSeries.HistorySeriesAvailableSoftSubs);
|
||||
|
||||
UpdateSeriesFolderPath();
|
||||
}
|
||||
|
||||
private void UpdateSeriesFolderPath(){
|
||||
var season = SelectedSeries.Seasons.FirstOrDefault(season => !string.IsNullOrEmpty(season.SeasonDownloadPath));
|
||||
|
||||
if (!string.IsNullOrEmpty(SelectedSeries.SeriesDownloadPath) && Directory.Exists(SelectedSeries.SeriesDownloadPath)){
|
||||
SeriesFolderPath = SelectedSeries.SeriesDownloadPath;
|
||||
SeriesFolderPathExists = true;
|
||||
}
|
||||
|
||||
if (season is{ SeasonDownloadPath: not null }){
|
||||
try{
|
||||
var seasonPath = season.SeasonDownloadPath;
|
||||
var directoryInfo = new DirectoryInfo(seasonPath);
|
||||
|
||||
string parentFolderPath = directoryInfo.Parent?.FullName;
|
||||
|
||||
if (Directory.Exists(parentFolderPath)){
|
||||
SeriesFolderPath = parentFolderPath;
|
||||
SeriesFolderPathExists = true;
|
||||
}
|
||||
} catch (Exception e){
|
||||
Console.Error.WriteLine($"An error occurred while opening the folder: {e.Message}");
|
||||
}
|
||||
} else{
|
||||
var customPath = string.Empty;
|
||||
|
||||
if (string.IsNullOrEmpty(SelectedSeries.SeriesTitle))
|
||||
return;
|
||||
|
||||
var seriesTitle = FileNameManager.CleanupFilename(SelectedSeries.SeriesTitle);
|
||||
|
||||
if (string.IsNullOrEmpty(seriesTitle))
|
||||
return;
|
||||
|
||||
// Check Crunchyroll download directory
|
||||
var downloadDirPath = CrunchyrollManager.Instance.CrunOptions.DownloadDirPath;
|
||||
if (!string.IsNullOrEmpty(downloadDirPath)){
|
||||
customPath = System.IO.Path.Combine(downloadDirPath, seriesTitle);
|
||||
} else{
|
||||
// Fallback to configured VIDEOS_DIR path
|
||||
customPath = System.IO.Path.Combine(CfgManager.PathVIDEOS_DIR, seriesTitle);
|
||||
}
|
||||
|
||||
// Check if custom path exists
|
||||
if (Directory.Exists(customPath)){
|
||||
SeriesFolderPath = customPath;
|
||||
SeriesFolderPathExists = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
|
@ -90,6 +152,8 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
CfgManager.UpdateHistoryFile();
|
||||
}
|
||||
}
|
||||
|
||||
UpdateSeriesFolderPath();
|
||||
}
|
||||
|
||||
public void SetStorageProvider(IStorageProvider storageProvider){
|
||||
|
|
@ -105,7 +169,7 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
FullSizeDesired = true
|
||||
};
|
||||
|
||||
var viewModel = new ContentDialogSonarrMatchViewModel(dialog, SelectedSeries.SonarrSeriesId,SelectedSeries.SeriesTitle);
|
||||
var viewModel = new ContentDialogSonarrMatchViewModel(dialog, SelectedSeries.SonarrSeriesId, SelectedSeries.SeriesTitle);
|
||||
dialog.Content = new ContentDialogSonarrMatchView(){
|
||||
DataContext = viewModel
|
||||
};
|
||||
|
|
@ -116,10 +180,10 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
SelectedSeries.SonarrSeriesId = viewModel.CurrentSonarrSeries.Id.ToString();
|
||||
SelectedSeries.SonarrTvDbId = viewModel.CurrentSonarrSeries.TvdbId.ToString();
|
||||
SelectedSeries.SonarrSlugTitle = viewModel.CurrentSonarrSeries.TitleSlug;
|
||||
|
||||
|
||||
if (CrunchyrollManager.Instance.CrunOptions.SonarrProperties != null){
|
||||
SonarrConnected = CrunchyrollManager.Instance.CrunOptions.SonarrProperties.SonarrEnabled;
|
||||
|
||||
|
||||
if (!string.IsNullOrEmpty(SelectedSeries.SonarrSeriesId)){
|
||||
SonarrAvailable = SelectedSeries.SonarrSeriesId.Length > 0 && SonarrConnected;
|
||||
} else{
|
||||
|
|
@ -131,7 +195,6 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
|
||||
UpdateData("");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -147,9 +210,13 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
public async Task DownloadSeasonMissing(HistorySeason season){
|
||||
var downloadTasks = season.EpisodesList
|
||||
.Where(episode => !episode.WasDownloaded)
|
||||
.Select(episode => episode.DownloadEpisode());
|
||||
.Select(episode => episode.DownloadEpisode()).ToList();
|
||||
|
||||
await Task.WhenAll(downloadTasks);
|
||||
if (downloadTasks.Count == 0){
|
||||
MessageBus.Current.SendMessage(new ToastMessage($"There are no missing episodes", ToastType.Error, 3));
|
||||
} else{
|
||||
await Task.WhenAll(downloadTasks);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
|
@ -190,4 +257,18 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
SelectedSeries.UpdateNewEpisodes();
|
||||
MessageBus.Current.SendMessage(new NavigationMessage(null, true, false));
|
||||
}
|
||||
|
||||
|
||||
[RelayCommand]
|
||||
public void OpenFolderPath(){
|
||||
try{
|
||||
Process.Start(new ProcessStartInfo{
|
||||
FileName = SeriesFolderPath,
|
||||
UseShellExecute = true,
|
||||
Verb = "open"
|
||||
});
|
||||
} catch (Exception ex){
|
||||
Console.Error.WriteLine($"An error occurred while opening the folder: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ using System.Collections.Specialized;
|
|||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Mime;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
|
|
@ -15,14 +15,18 @@ using Avalonia.Platform.Storage;
|
|||
using Avalonia.Styling;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.CustomList;
|
||||
using CRD.Utils.Ffmpeg_Encoding;
|
||||
using CRD.Utils.Sonarr;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.History;
|
||||
using CRD.ViewModels.Utils;
|
||||
using CRD.Views.Utils;
|
||||
using FluentAvalonia.Styling;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace CRD.ViewModels;
|
||||
|
||||
|
|
@ -40,7 +44,7 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
private bool _downloadChapters = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _addScaledBorderAndShadow = false;
|
||||
private bool _addScaledBorderAndShadow;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _includeSignSubs;
|
||||
|
|
@ -59,6 +63,9 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
new ComboBoxItem(){ Content = "ScaledBorderAndShadow: no" },
|
||||
};
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _skipMuxing;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _muxToMp4;
|
||||
|
||||
|
|
@ -108,7 +115,7 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
private string _fileTitle = "";
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<MuxingParam> _mkvMergeOptions = new();
|
||||
private ObservableCollection<StringItem> _mkvMergeOptions = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private string _mkvMergeOption = "";
|
||||
|
|
@ -117,7 +124,7 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
private string _ffmpegOption = "";
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<MuxingParam> _ffmpegOptions = new();
|
||||
private ObservableCollection<StringItem> _ffmpegOptions = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private string _selectedSubs = "all";
|
||||
|
|
@ -327,12 +334,44 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
new ComboBoxItem(){ Content = "tv/samsung" },
|
||||
};
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isEncodeEnabled;
|
||||
|
||||
[ObservableProperty]
|
||||
private StringItem _selectedEncodingPreset;
|
||||
|
||||
public ObservableCollection<StringItem> EncodingPresetsList{ get; } = new();
|
||||
|
||||
|
||||
[ObservableProperty]
|
||||
private string _downloadDirPath;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _proxyEnabled;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _proxyHost;
|
||||
|
||||
[ObservableProperty]
|
||||
private double? _proxyPort;
|
||||
|
||||
|
||||
[ObservableProperty]
|
||||
private string _tempDownloadDirPath;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _currentIp = "";
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _cCSubsMuxingFlag;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _cCSubsFont;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _signsSubsAsForced;
|
||||
|
||||
|
||||
private readonly FluentAvaloniaTheme _faTheme;
|
||||
|
||||
private bool settingsLoaded;
|
||||
|
|
@ -345,6 +384,12 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
|
||||
_faTheme = App.Current.Styles[0] as FluentAvaloniaTheme;
|
||||
|
||||
if (CrunchyrollManager.Instance.CrunOptions.AccentColor != null && !string.IsNullOrEmpty(CrunchyrollManager.Instance.CrunOptions.AccentColor)){
|
||||
CustomAccentColor = Color.Parse(CrunchyrollManager.Instance.CrunOptions.AccentColor);
|
||||
} else{
|
||||
CustomAccentColor = Application.Current?.PlatformSettings?.GetColorValues().AccentColor1 ?? Colors.SlateBlue;
|
||||
}
|
||||
|
||||
foreach (var languageItem in Languages.languages){
|
||||
HardSubLangList.Add(new ComboBoxItem{ Content = languageItem.CrLocale });
|
||||
SubLangList.Add(new ListBoxItem{ Content = languageItem.CrLocale });
|
||||
|
|
@ -353,11 +398,19 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
DefaultSubLangList.Add(new ComboBoxItem{ Content = languageItem.CrLocale });
|
||||
}
|
||||
|
||||
foreach (var encodingPreset in FfmpegEncoding.presets){
|
||||
EncodingPresetsList.Add(new StringItem{ stringValue = encodingPreset.PresetName ?? "Unknown Preset Name" });
|
||||
}
|
||||
|
||||
|
||||
CrDownloadOptions options = CrunchyrollManager.Instance.CrunOptions;
|
||||
|
||||
DownloadDirPath = string.IsNullOrEmpty(options.DownloadDirPath) ? CfgManager.PathVIDEOS_DIR : options.DownloadDirPath;
|
||||
TempDownloadDirPath = string.IsNullOrEmpty(options.DownloadTempDirPath) ? CfgManager.PathTEMP_DIR : options.DownloadTempDirPath;
|
||||
|
||||
StringItem? encodingPresetSelected = EncodingPresetsList.FirstOrDefault(a => a.stringValue != null && a.stringValue == options.EncodingPresetName) ?? null;
|
||||
SelectedEncodingPreset = encodingPresetSelected ?? EncodingPresetsList[0];
|
||||
|
||||
ComboBoxItem? descriptionLang = DescriptionLangList.FirstOrDefault(a => a.Content != null && (string)a.Content == options.DescriptionLang) ?? null;
|
||||
SelectedDescriptionLang = descriptionLang ?? DescriptionLangList[0];
|
||||
|
||||
|
|
@ -403,6 +456,14 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
AddScaledBorderAndShadow = options.SubsAddScaledBorder is ScaledBorderAndShadowSelection.ScaledBorderAndShadowNo or ScaledBorderAndShadowSelection.ScaledBorderAndShadowYes;
|
||||
SelectedScaledBorderAndShadow = GetScaledBorderAndShadowFromOptions(options);
|
||||
|
||||
CCSubsFont = options.CcSubsFont ?? "";
|
||||
CCSubsMuxingFlag = options.CcSubsMuxingFlag;
|
||||
SignsSubsAsForced = options.SignsSubsAsForced;
|
||||
ProxyEnabled = options.ProxyEnabled;
|
||||
ProxyHost = options.ProxyHost ?? "";
|
||||
ProxyPort = options.ProxyPort;
|
||||
SkipMuxing = options.SkipMuxing;
|
||||
IsEncodeEnabled = options.IsEncodeEnabled;
|
||||
DefaultSubForcedDisplay = options.DefaultSubForcedDisplay;
|
||||
DefaultSubSigns = options.DefaultSubSigns;
|
||||
HistoryAddSpecials = options.HistoryAddSpecials;
|
||||
|
|
@ -435,7 +496,7 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
ComboBoxItem? theme = AppThemes.FirstOrDefault(a => a.Content != null && (string)a.Content == options.Theme) ?? null;
|
||||
CurrentAppTheme = theme ?? AppThemes[0];
|
||||
|
||||
if (options.AccentColor != CustomAccentColor.ToString()){
|
||||
if (!string.IsNullOrEmpty(options.AccentColor) && options.AccentColor != Application.Current?.PlatformSettings?.GetColorValues().AccentColor1.ToString()){
|
||||
UseCustomAccent = true;
|
||||
}
|
||||
|
||||
|
|
@ -444,14 +505,14 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
MkvMergeOptions.Clear();
|
||||
if (options.MkvmergeOptions != null){
|
||||
foreach (var mkvmergeParam in options.MkvmergeOptions){
|
||||
MkvMergeOptions.Add(new MuxingParam(){ ParamValue = mkvmergeParam });
|
||||
MkvMergeOptions.Add(new StringItem(){ stringValue = mkvmergeParam });
|
||||
}
|
||||
}
|
||||
|
||||
FfmpegOptions.Clear();
|
||||
if (options.FfmpegOptions != null){
|
||||
foreach (var ffmpegParam in options.FfmpegOptions){
|
||||
FfmpegOptions.Add(new MuxingParam(){ ParamValue = ffmpegParam });
|
||||
FfmpegOptions.Add(new StringItem(){ stringValue = ffmpegParam });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -475,6 +536,11 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
return;
|
||||
}
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.SignsSubsAsForced = SignsSubsAsForced;
|
||||
CrunchyrollManager.Instance.CrunOptions.CcSubsMuxingFlag = CCSubsMuxingFlag;
|
||||
CrunchyrollManager.Instance.CrunOptions.CcSubsFont = CCSubsFont;
|
||||
CrunchyrollManager.Instance.CrunOptions.EncodingPresetName = SelectedEncodingPreset.stringValue;
|
||||
CrunchyrollManager.Instance.CrunOptions.IsEncodeEnabled = IsEncodeEnabled;
|
||||
CrunchyrollManager.Instance.CrunOptions.DownloadToTempFolder = DownloadToTempFolder;
|
||||
CrunchyrollManager.Instance.CrunOptions.DefaultSubSigns = DefaultSubSigns;
|
||||
CrunchyrollManager.Instance.CrunOptions.DefaultSubForcedDisplay = DefaultSubForcedDisplay;
|
||||
|
|
@ -487,6 +553,7 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
CrunchyrollManager.Instance.CrunOptions.DlVideoOnce = !DownloadVideoForEveryDub;
|
||||
CrunchyrollManager.Instance.CrunOptions.KeepDubsSeperate = KeepDubsSeparate;
|
||||
CrunchyrollManager.Instance.CrunOptions.Chapters = DownloadChapters;
|
||||
CrunchyrollManager.Instance.CrunOptions.SkipMuxing = SkipMuxing;
|
||||
CrunchyrollManager.Instance.CrunOptions.Mp4 = MuxToMp4;
|
||||
CrunchyrollManager.Instance.CrunOptions.SyncTiming = SyncTimings;
|
||||
CrunchyrollManager.Instance.CrunOptions.SkipSubsMux = SkipSubMux;
|
||||
|
|
@ -497,6 +564,10 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
CrunchyrollManager.Instance.CrunOptions.DownloadSpeedLimit = Math.Clamp((int)(DownloadSpeed ?? 0), 0, 1000000000);
|
||||
CrunchyrollManager.Instance.CrunOptions.SimultaneousDownloads = Math.Clamp((int)(SimultaneousDownloads ?? 0), 1, 10);
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.ProxyEnabled = ProxyEnabled;
|
||||
CrunchyrollManager.Instance.CrunOptions.ProxyHost = ProxyHost;
|
||||
CrunchyrollManager.Instance.CrunOptions.ProxyPort = Math.Clamp((int)(ProxyPort ?? 0), 0, 65535);
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.SubsAddScaledBorder = GetScaledBorderAndShadowSelection();
|
||||
|
||||
List<string> softSubs = new List<string>();
|
||||
|
|
@ -535,7 +606,11 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
CrunchyrollManager.Instance.CrunOptions.QualityVideo = SelectedVideoQuality?.Content + "";
|
||||
CrunchyrollManager.Instance.CrunOptions.Theme = CurrentAppTheme?.Content + "";
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.AccentColor = _faTheme.CustomAccentColor.ToString();
|
||||
if (_faTheme.CustomAccentColor != (Application.Current?.PlatformSettings?.GetColorValues().AccentColor1)){
|
||||
CrunchyrollManager.Instance.CrunOptions.AccentColor = _faTheme.CustomAccentColor.ToString();
|
||||
} else{
|
||||
CrunchyrollManager.Instance.CrunOptions.AccentColor = string.Empty;
|
||||
}
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.History = History;
|
||||
|
||||
|
|
@ -560,14 +635,14 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
|
||||
List<string> mkvmergeParams = new List<string>();
|
||||
foreach (var mkvmergeParam in MkvMergeOptions){
|
||||
mkvmergeParams.Add(mkvmergeParam.ParamValue);
|
||||
mkvmergeParams.Add(mkvmergeParam.stringValue);
|
||||
}
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.MkvmergeOptions = mkvmergeParams;
|
||||
|
||||
List<string> ffmpegParams = new List<string>();
|
||||
foreach (var ffmpegParam in FfmpegOptions){
|
||||
ffmpegParams.Add(ffmpegParam.ParamValue);
|
||||
ffmpegParams.Add(ffmpegParam.stringValue);
|
||||
}
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.FfmpegOptions = ffmpegParams;
|
||||
|
|
@ -605,26 +680,26 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
|
||||
[RelayCommand]
|
||||
public void AddMkvMergeParam(){
|
||||
MkvMergeOptions.Add(new MuxingParam(){ ParamValue = MkvMergeOption });
|
||||
MkvMergeOptions.Add(new StringItem(){ stringValue = MkvMergeOption });
|
||||
MkvMergeOption = "";
|
||||
RaisePropertyChanged(nameof(MkvMergeOptions));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void RemoveMkvMergeParam(MuxingParam param){
|
||||
public void RemoveMkvMergeParam(StringItem param){
|
||||
MkvMergeOptions.Remove(param);
|
||||
RaisePropertyChanged(nameof(MkvMergeOptions));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void AddFfmpegParam(){
|
||||
FfmpegOptions.Add(new MuxingParam(){ ParamValue = FfmpegOption });
|
||||
FfmpegOptions.Add(new StringItem(){ stringValue = FfmpegOption });
|
||||
FfmpegOption = "";
|
||||
RaisePropertyChanged(nameof(FfmpegOptions));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void RemoveFfmpegParam(MuxingParam param){
|
||||
public void RemoveFfmpegParam(StringItem param){
|
||||
FfmpegOptions.Remove(param);
|
||||
RaisePropertyChanged(nameof(FfmpegOptions));
|
||||
}
|
||||
|
|
@ -632,7 +707,10 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
[RelayCommand]
|
||||
public async Task OpenFolderDialogAsync(){
|
||||
await OpenFolderDialogAsyncInternal(
|
||||
pathSetter: (path) => CrunchyrollManager.Instance.CrunOptions.DownloadDirPath = path,
|
||||
pathSetter: (path) => {
|
||||
CrunchyrollManager.Instance.CrunOptions.DownloadDirPath = path;
|
||||
DownloadDirPath = string.IsNullOrEmpty(path) ? CfgManager.PathVIDEOS_DIR : path;
|
||||
},
|
||||
pathGetter: () => CrunchyrollManager.Instance.CrunOptions.DownloadDirPath,
|
||||
defaultPath: CfgManager.PathVIDEOS_DIR
|
||||
);
|
||||
|
|
@ -641,7 +719,10 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
[RelayCommand]
|
||||
public async Task OpenFolderDialogTempFolderAsync(){
|
||||
await OpenFolderDialogAsyncInternal(
|
||||
pathSetter: (path) => CrunchyrollManager.Instance.CrunOptions.DownloadTempDirPath = path,
|
||||
pathSetter: (path) => {
|
||||
CrunchyrollManager.Instance.CrunOptions.DownloadTempDirPath = path;
|
||||
TempDownloadDirPath = string.IsNullOrEmpty(path) ? CfgManager.PathTEMP_DIR : path;
|
||||
},
|
||||
pathGetter: () => CrunchyrollManager.Instance.CrunOptions.DownloadTempDirPath,
|
||||
defaultPath: CfgManager.PathTEMP_DIR
|
||||
);
|
||||
|
|
@ -697,7 +778,8 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
} else{
|
||||
CustomAccentColor = default;
|
||||
ListBoxColor = default;
|
||||
UpdateAppAccentColor(Colors.SlateBlue);
|
||||
var color = Application.Current?.PlatformSettings?.GetColorValues().AccentColor1 ?? Colors.SlateBlue;
|
||||
UpdateAppAccentColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -734,7 +816,13 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
protected override void OnPropertyChanged(PropertyChangedEventArgs e){
|
||||
base.OnPropertyChanged(e);
|
||||
|
||||
if (e.PropertyName is nameof(SelectedDubs) or nameof(SelectedSubs) or nameof(CustomAccentColor) or nameof(ListBoxColor) or nameof(CurrentAppTheme) or nameof(UseCustomAccent) or nameof(LogMode)){
|
||||
if (e.PropertyName is nameof(SelectedDubs)
|
||||
or nameof(SelectedSubs)
|
||||
or nameof(CustomAccentColor)
|
||||
or nameof(ListBoxColor)
|
||||
or nameof(CurrentAppTheme)
|
||||
or nameof(UseCustomAccent)
|
||||
or nameof(LogMode)){
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -766,6 +854,45 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task CreateEncodingPresetButtonPress(bool editMode){
|
||||
var dialog = new ContentDialog(){
|
||||
Title = "New Encoding Preset",
|
||||
PrimaryButtonText = "Save",
|
||||
CloseButtonText = "Close",
|
||||
FullSizeDesired = true
|
||||
};
|
||||
|
||||
var viewModel = new ContentDialogEncodingPresetViewModel(dialog, editMode);
|
||||
dialog.Content = new ContentDialogEncodingPresetView(){
|
||||
DataContext = viewModel
|
||||
};
|
||||
|
||||
var dialogResult = await dialog.ShowAsync();
|
||||
|
||||
if (dialogResult == ContentDialogResult.Primary){
|
||||
settingsLoaded = false;
|
||||
EncodingPresetsList.Clear();
|
||||
foreach (var encodingPreset in FfmpegEncoding.presets){
|
||||
EncodingPresetsList.Add(new StringItem{ stringValue = encodingPreset.PresetName ?? "Unknown Preset Name" });
|
||||
}
|
||||
|
||||
settingsLoaded = true;
|
||||
StringItem? encodingPresetSelected = EncodingPresetsList.FirstOrDefault(a => a.stringValue != null && a.stringValue == CrunchyrollManager.Instance.CrunOptions.EncodingPresetName) ?? null;
|
||||
SelectedEncodingPreset = encodingPresetSelected ?? EncodingPresetsList[0];
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async void CheckIp(){
|
||||
var result = await HttpClientReq.Instance.SendHttpRequest(HttpClientReq.CreateRequestMessage("https://icanhazip.com", HttpMethod.Get, false, false, null));
|
||||
Console.Error.WriteLine("Your IP: " + result.ResponseContent);
|
||||
if (result.IsOk){
|
||||
CurrentIp = result.ResponseContent;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
partial void OnLogModeChanged(bool value){
|
||||
UpdateSettings();
|
||||
if (value){
|
||||
|
|
@ -774,8 +901,4 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
|||
CfgManager.DisableLogMode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class MuxingParam{
|
||||
public string ParamValue{ get; set; }
|
||||
}
|
||||
190
CRD/ViewModels/Utils/ContentDialogEncodingPresetViewModel.cs
Normal file
190
CRD/ViewModels/Utils/ContentDialogEncodingPresetViewModel.cs
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Avalonia.Controls;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.Ffmpeg_Encoding;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Views;
|
||||
using DynamicData;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace CRD.ViewModels.Utils;
|
||||
|
||||
public partial class ContentDialogEncodingPresetViewModel : ViewModelBase{
|
||||
private readonly ContentDialog dialog;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _editMode;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _presetName;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _codec;
|
||||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem _selectedResolution = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private double? _crf = 23;
|
||||
|
||||
[ObservableProperty]
|
||||
private double? _frameRate = 30;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _additionalParametersString = "";
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<StringItem> _additionalParameters = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private VideoPreset? _selectedCustomPreset;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _fileExists;
|
||||
|
||||
public ObservableCollection<VideoPreset> CustomPresetsList{ get; } = new(){ };
|
||||
|
||||
public ObservableCollection<ComboBoxItem> ResolutionList{ get; } = new(){
|
||||
new ComboBoxItem(){ Content = "3840:2160" }, // 4K UHD
|
||||
new ComboBoxItem(){ Content = "3440:1440" }, // Ultra-Wide Quad HD
|
||||
new ComboBoxItem(){ Content = "2560:1440" }, // 1440p
|
||||
new ComboBoxItem(){ Content = "2560:1080" }, // Ultra-Wide Full HD
|
||||
new ComboBoxItem(){ Content = "2160:1080" }, // 2:1 Aspect Ratio
|
||||
new ComboBoxItem(){ Content = "1920:1080" }, // 1080p Full HD
|
||||
new ComboBoxItem(){ Content = "1920:800" }, // Cinematic 2.40:1
|
||||
new ComboBoxItem(){ Content = "1600:900" }, // 900p
|
||||
new ComboBoxItem(){ Content = "1366:768" }, // 768p
|
||||
new ComboBoxItem(){ Content = "1280:960" }, // SXGA 4:3
|
||||
new ComboBoxItem(){ Content = "1280:720" }, // 720p HD
|
||||
new ComboBoxItem(){ Content = "1024:576" }, // 576p
|
||||
new ComboBoxItem(){ Content = "960:540" }, // 540p qHD
|
||||
new ComboBoxItem(){ Content = "854:480" }, // 480p
|
||||
new ComboBoxItem(){ Content = "800:600" }, // SVGA
|
||||
new ComboBoxItem(){ Content = "768:432" }, // 432p
|
||||
new ComboBoxItem(){ Content = "720:480" }, // NTSC SD
|
||||
new ComboBoxItem(){ Content = "704:576" }, // PAL SD
|
||||
new ComboBoxItem(){ Content = "640:360" }, // 360p
|
||||
new ComboBoxItem(){ Content = "426:240" }, // 240p
|
||||
new ComboBoxItem(){ Content = "320:240" }, // QVGA
|
||||
new ComboBoxItem(){ Content = "320:180" }, // 180p
|
||||
new ComboBoxItem(){ Content = "256:144" }, // 144p
|
||||
};
|
||||
|
||||
public ContentDialogEncodingPresetViewModel(ContentDialog dialog, bool editMode){
|
||||
this.dialog = dialog;
|
||||
|
||||
if (dialog is null){
|
||||
throw new ArgumentNullException(nameof(dialog));
|
||||
}
|
||||
|
||||
if (editMode){
|
||||
EditMode = true;
|
||||
CustomPresetsList.AddRange(FfmpegEncoding.presets.Skip(15));
|
||||
|
||||
this.dialog.Title = "Edit Encoding Preset";
|
||||
|
||||
if (CustomPresetsList.Count == 0){
|
||||
MessageBus.Current.SendMessage(new ToastMessage($"There are no presets to be edited", ToastType.Warning, 5));
|
||||
EditMode = false;
|
||||
} else{
|
||||
SelectedCustomPreset = CustomPresetsList.First();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dialog.Closed += DialogOnClosed;
|
||||
dialog.PrimaryButtonClick += SaveButton;
|
||||
}
|
||||
|
||||
partial void OnSelectedCustomPresetChanged(VideoPreset value){
|
||||
PresetName = value.PresetName ?? "";
|
||||
Codec = value.Codec ?? "";
|
||||
Crf = value.Crf;
|
||||
FrameRate = double.Parse(value.FrameRate ?? "0");
|
||||
|
||||
SelectedResolution = ResolutionList.FirstOrDefault(e => e.Content?.ToString() == value.Resolution) ?? ResolutionList.First();
|
||||
AdditionalParameters.Clear();
|
||||
|
||||
foreach (var valueAdditionalParameter in value.AdditionalParameters){
|
||||
AdditionalParameters.Add(new StringItem(){ stringValue = valueAdditionalParameter });
|
||||
}
|
||||
|
||||
AdditionalParametersString = "";
|
||||
}
|
||||
|
||||
partial void OnPresetNameChanged(string value){
|
||||
var path = Path.Combine(CfgManager.PathENCODING_PRESETS_DIR, value + ".json");
|
||||
var fileExists = File.Exists(path);
|
||||
|
||||
dialog.IsPrimaryButtonEnabled = !fileExists || EditMode && value == SelectedCustomPreset?.PresetName;
|
||||
|
||||
FileExists = !dialog.IsPrimaryButtonEnabled;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void AddAdditionalParam(){
|
||||
AdditionalParameters.Add(new StringItem(){ stringValue = AdditionalParametersString });
|
||||
AdditionalParametersString = "";
|
||||
RaisePropertyChanged(nameof(AdditionalParametersString));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void RemoveAdditionalParam(StringItem param){
|
||||
AdditionalParameters.Remove(param);
|
||||
RaisePropertyChanged(nameof(AdditionalParameters));
|
||||
}
|
||||
|
||||
private void SaveButton(ContentDialog sender, ContentDialogButtonClickEventArgs args){
|
||||
dialog.PrimaryButtonClick -= SaveButton;
|
||||
|
||||
if (EditMode){
|
||||
if (SelectedCustomPreset != null){
|
||||
var oldName = SelectedCustomPreset.PresetName;
|
||||
|
||||
SelectedCustomPreset.PresetName = PresetName;
|
||||
SelectedCustomPreset.Codec = Codec;
|
||||
SelectedCustomPreset.FrameRate = Math.Clamp((int)(FrameRate ?? 1), 1, 999).ToString();
|
||||
SelectedCustomPreset.Crf = Math.Clamp((int)(Crf ?? 0), 0, 51);
|
||||
SelectedCustomPreset.Resolution = SelectedResolution.Content?.ToString() ?? "1920:1080";
|
||||
SelectedCustomPreset.AdditionalParameters = AdditionalParameters.Select(additionalParameter => additionalParameter.stringValue).ToList();
|
||||
|
||||
try{
|
||||
var oldPath = Path.Combine(CfgManager.PathENCODING_PRESETS_DIR, oldName + ".json");
|
||||
var path = Path.Combine(CfgManager.PathENCODING_PRESETS_DIR, SelectedCustomPreset.PresetName + ".json");
|
||||
|
||||
if (File.Exists(oldPath)){
|
||||
File.Delete(oldPath);
|
||||
}
|
||||
|
||||
CfgManager.WriteJsonToFile(path, SelectedCustomPreset);
|
||||
} catch (Exception e){
|
||||
Console.Error.WriteLine("Error saving preset: " + e);
|
||||
}
|
||||
}
|
||||
} else{
|
||||
VideoPreset newPreset = new VideoPreset(){
|
||||
PresetName = PresetName,
|
||||
Codec = Codec,
|
||||
FrameRate = Math.Clamp((int)(FrameRate ?? 1), 1, 999).ToString(),
|
||||
Crf = Math.Clamp((int)(Crf ?? 0), 0, 51),
|
||||
Resolution = SelectedResolution.Content?.ToString() ?? "1920:1080",
|
||||
AdditionalParameters = AdditionalParameters.Select(additionalParameter => additionalParameter.stringValue).ToList()
|
||||
};
|
||||
|
||||
CfgManager.WriteJsonToFile(Path.Combine(CfgManager.PathENCODING_PRESETS_DIR, newPreset.PresetName + ".json"), newPreset);
|
||||
|
||||
FfmpegEncoding.AddPreset(newPreset);
|
||||
}
|
||||
}
|
||||
|
||||
private void DialogOnClosed(ContentDialog sender, ContentDialogClosedEventArgs args){
|
||||
dialog.Closed -= DialogOnClosed;
|
||||
}
|
||||
}
|
||||
|
|
@ -5,13 +5,21 @@
|
|||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
xmlns:vm="clr-namespace:CRD.ViewModels"
|
||||
xmlns:structs="clr-namespace:CRD.Utils.Structs"
|
||||
xmlns:ui="clr-namespace:CRD.Utils.UI"
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
|
||||
x:DataType="vm:AddDownloadPageViewModel"
|
||||
x:Class="CRD.Views.AddDownloadPageView">
|
||||
x:Class="CRD.Views.AddDownloadPageView"
|
||||
Unloaded="OnUnloaded">
|
||||
|
||||
<Design.DataContext>
|
||||
<vm:AddDownloadPageViewModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<UserControl.Resources>
|
||||
<ui:UiListToStringConverter x:Key="UiListToStringConverter" />
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" /> <!-- For the TextBox -->
|
||||
|
|
@ -115,8 +123,8 @@
|
|||
<CheckBox IsEnabled="{Binding AllButtonEnabled}" IsChecked="{Binding AddAllEpisodes}"
|
||||
Content="All" Margin="5 0 0 0">
|
||||
</CheckBox>
|
||||
|
||||
<Button IsVisible="{Binding SlectSeasonVisible}" IsEnabled="{Binding !ShowLoading}" Width="200" Command="{Binding OnSelectSeasonPressed}"
|
||||
|
||||
<Button IsVisible="{Binding SlectSeasonVisible}" IsEnabled="{Binding !ShowLoading}" Width="200" Command="{Binding OnSelectSeasonPressed}"
|
||||
Content="{Binding ButtonTextSelectSeason}">
|
||||
</Button>
|
||||
|
||||
|
|
@ -135,33 +143,42 @@
|
|||
|
||||
|
||||
<Grid Grid.Row="2">
|
||||
<!-- Spinner Style ProgressBar -->
|
||||
<ProgressBar IsIndeterminate="True"
|
||||
Value="50"
|
||||
Maximum="100"
|
||||
MaxWidth="100"
|
||||
IsVisible="{Binding ShowLoading}">
|
||||
</ProgressBar>
|
||||
<controls:ProgressRing IsVisible="{Binding ShowLoading}" Width="100" Height="100" HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
|
||||
</Grid>
|
||||
|
||||
<!-- ListBox with Custom Elements -->
|
||||
<ListBox Grid.Row="2" Margin="10" SelectionMode="Multiple,Toggle" VerticalAlignment="Stretch"
|
||||
SelectedItems="{Binding SelectedItems}" ItemsSource="{Binding Items}" x:Name="Grid">
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Vertical" />
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type vm:ItemModel}">
|
||||
<DataTemplate>
|
||||
<StackPanel>
|
||||
<Border Padding="10" Margin="5" BorderThickness="1">
|
||||
<Border Padding="10 0" Margin="5" BorderThickness="1">
|
||||
<Grid Margin="10" VerticalAlignment="Top">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" /> <!-- Top content takes available space -->
|
||||
<RowDefinition Height="Auto" /> <!-- Bottom row for AvailableAudios TextBlock -->
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Image -->
|
||||
<Image Grid.Column="0" Width="208" Height="117" Source="{Binding ImageBitmap}"
|
||||
<Image Grid.Column="0" Grid.Row="0" Grid.RowSpan="2" Width="208" Height="117" Source="{Binding ImageBitmap}"
|
||||
Stretch="Fill" />
|
||||
|
||||
<!-- <Image Grid.Column="0" Grid.Row="0" Grid.RowSpan="2" asyncImageLoader:ImageLoader.Source="{Binding ImageUrl}" Width="208" Height="117" Stretch="Fill"></Image> -->
|
||||
|
||||
|
||||
<!-- Text Content -->
|
||||
<Grid Grid.Column="1" Margin="10" VerticalAlignment="Top">
|
||||
<Grid Grid.Column="1" Grid.Row="0" Margin="10" VerticalAlignment="Top">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" /> <!-- Takes up most space for the title -->
|
||||
<ColumnDefinition Width="Auto" />
|
||||
|
|
@ -179,7 +196,19 @@
|
|||
<TextBlock Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2"
|
||||
Text="{Binding Description}"
|
||||
FontStyle="Italic" Opacity="0.8" TextWrapping="Wrap" />
|
||||
|
||||
</Grid>
|
||||
<StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center" Margin="10">
|
||||
<TextBlock FontStyle="Italic"
|
||||
Opacity="0.8" Text="Dubs: ">
|
||||
</TextBlock>
|
||||
<TextBlock Text="{Binding AvailableAudios, Converter={StaticResource UiListToStringConverter}}"
|
||||
FontStyle="Italic"
|
||||
Opacity="0.8"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Bottom"
|
||||
TextWrapping="NoWrap" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border Background="LightGray" Height="1" Margin="0,5" HorizontalAlignment="Stretch" />
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
using System;
|
||||
using Avalonia;
|
||||
using System.Runtime;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Interactivity;
|
||||
using CRD.ViewModels;
|
||||
|
||||
namespace CRD.Views;
|
||||
|
|
@ -11,6 +11,15 @@ public partial class AddDownloadPageView : UserControl{
|
|||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void OnUnloaded(object? sender, RoutedEventArgs e){
|
||||
if (DataContext is AddDownloadPageViewModel viewModel){
|
||||
viewModel.Dispose();
|
||||
DataContext = null;
|
||||
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
|
||||
GC.Collect();
|
||||
}
|
||||
}
|
||||
|
||||
private void Popup_Closed(object? sender, EventArgs e){
|
||||
if (DataContext is AddDownloadPageViewModel viewModel){
|
||||
viewModel.SearchPopupVisible = false;
|
||||
|
|
|
|||
|
|
@ -39,8 +39,15 @@
|
|||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<!-- Image -->
|
||||
<Image Grid.Column="0" Width="208" Height="117" Source="{Binding ImageBitmap}"
|
||||
Stretch="Fill" />
|
||||
<!-- <Image Grid.Column="0" Width="208" Height="117" Source="{Binding ImageBitmap}" -->
|
||||
<!-- Stretch="Fill" /> -->
|
||||
|
||||
<Grid>
|
||||
<Image HorizontalAlignment="Center" Width="208" Height="117" Source="../Assets/coming_soon_ep.jpg" />
|
||||
<Image Grid.Column="0" Width="208" Height="117" Source="{Binding ImageBitmap}"
|
||||
Stretch="Fill" />
|
||||
</Grid>
|
||||
|
||||
|
||||
<!-- Text Content -->
|
||||
<Grid Grid.Column="1" Margin="10" >
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@
|
|||
<Button Width="70" Height="70" Background="Transparent" BorderThickness="0" Margin="5 0"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding RefreshAll}"
|
||||
IsEnabled="{Binding !FetchingData}">
|
||||
IsEnabled="{Binding !ProgramManager.FetchingData}">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<controls:SymbolIcon Symbol="Sync" FontSize="32" />
|
||||
<TextBlock Text="Refresh Filtered" TextWrapping="Wrap" FontSize="12"></TextBlock>
|
||||
|
|
@ -47,7 +47,7 @@
|
|||
<Button Width="70" Height="70" Background="Transparent" BorderThickness="0" Margin="5 0"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding AddMissingToQueue}"
|
||||
IsEnabled="{Binding !FetchingData}">
|
||||
IsEnabled="{Binding !ProgramManager.FetchingData}">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<controls:SymbolIcon Symbol="Import" FontSize="32" />
|
||||
<TextBlock Text="Add To Queue" TextWrapping="Wrap" FontSize="12"></TextBlock>
|
||||
|
|
@ -57,7 +57,7 @@
|
|||
<ToggleButton Width="70" Height="70" Background="Transparent" BorderThickness="0" Margin="5 0"
|
||||
VerticalAlignment="Center"
|
||||
IsChecked="{Binding EditMode}"
|
||||
IsEnabled="{Binding !FetchingData}">
|
||||
IsEnabled="{Binding !ProgramManager.FetchingData}">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<controls:SymbolIcon Symbol="Edit" FontSize="32" />
|
||||
<TextBlock Text="Edit" TextWrapping="Wrap" FontSize="12"></TextBlock>
|
||||
|
|
@ -71,7 +71,7 @@
|
|||
BorderThickness="0" CornerRadius="5"
|
||||
IsVisible="{Binding SonarrAvailable}"
|
||||
IsChecked="{Binding SonarrOptionsOpen}"
|
||||
IsEnabled="{Binding !FetchingData}">
|
||||
IsEnabled="{Binding !ProgramManager.FetchingData}">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<controls:ImageIcon VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0 1 0 0" Source="../Assets/sonarr.png" Width="30" Height="30" />
|
||||
<TextBlock Text="Sonarr" TextWrapping="Wrap" FontSize="12"></TextBlock>
|
||||
|
|
@ -119,7 +119,7 @@
|
|||
<StackPanel>
|
||||
<ToggleButton x:Name="DropdownButtonViews" Width="70" Height="70" Background="Transparent"
|
||||
BorderThickness="0" Margin="5 0" VerticalAlignment="Center"
|
||||
IsEnabled="{Binding !FetchingData}"
|
||||
IsEnabled="{Binding !ProgramManager.FetchingData}"
|
||||
IsChecked="{Binding ViewSelectionOpen}">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<controls:SymbolIcon Symbol="View" FontSize="32" />
|
||||
|
|
@ -143,7 +143,7 @@
|
|||
<StackPanel>
|
||||
<ToggleButton x:Name="DropdownButtonSorting" Width="70" Height="70" Background="Transparent"
|
||||
BorderThickness="0" Margin="5 0" VerticalAlignment="Center"
|
||||
IsEnabled="{Binding !FetchingData}"
|
||||
IsEnabled="{Binding !ProgramManager.FetchingData}"
|
||||
IsChecked="{Binding SortingSelectionOpen}">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<controls:SymbolIcon Symbol="Sort" FontSize="32" />
|
||||
|
|
@ -175,7 +175,7 @@
|
|||
<StackPanel>
|
||||
<ToggleButton x:Name="DropdownButtonFilter" Width="70" Height="70" Background="Transparent" BorderThickness="0" Margin="5 0"
|
||||
VerticalAlignment="Center"
|
||||
IsEnabled="{Binding !FetchingData}">
|
||||
IsEnabled="{Binding !ProgramManager.FetchingData}">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<controls:SymbolIcon Symbol="Filter" FontSize="32" />
|
||||
<TextBlock Text="Filter" FontSize="12"></TextBlock>
|
||||
|
|
@ -273,7 +273,7 @@
|
|||
FontStyle="Italic"
|
||||
Command="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).RemoveSeries}"
|
||||
CommandParameter="{Binding SeriesId}"
|
||||
IsEnabled="{Binding !$parent[UserControl].((vm:HistoryPageViewModel)DataContext).FetchingData}">
|
||||
IsEnabled="{Binding !$parent[UserControl].((vm:HistoryPageViewModel)DataContext).ProgramManager.FetchingData}">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="Remove Series" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
|
|
@ -872,7 +872,7 @@
|
|||
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
|
||||
Command="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).RemoveSeries}"
|
||||
CommandParameter="{Binding SeriesId}"
|
||||
IsEnabled="{Binding !$parent[UserControl].((vm:HistoryPageViewModel)DataContext).FetchingData}">
|
||||
IsEnabled="{Binding !$parent[UserControl].((vm:HistoryPageViewModel)DataContext).ProgramManager.FetchingData}">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="Remove Series" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
|
|
|
|||
|
|
@ -60,12 +60,12 @@
|
|||
IconSource="Add" />
|
||||
<ui:NavigationViewItem Classes="SampleAppNav" Content="Calendar" Tag="Calendar"
|
||||
IconSource="Calendar" />
|
||||
<ui:NavigationViewItem IsEnabled="{Binding FinishedLoading}" Classes="SampleAppNav" Content="History" Tag="History"
|
||||
<ui:NavigationViewItem IsEnabled="{Binding ProgramManager.FinishedLoading}" Classes="SampleAppNav" Content="History" Tag="History"
|
||||
IconSource="Library" />
|
||||
</ui:NavigationView.MenuItems>
|
||||
<ui:NavigationView.FooterMenuItems>
|
||||
<ui:NavigationViewItem Classes="SampleAppNav" Content="Update Available" Tag="UpdateAvailable"
|
||||
IconSource="CloudDownload" Focusable="False" IsEnabled="{Binding UpdateAvailable}" />
|
||||
IconSource="CloudDownload" Focusable="False" IsEnabled="{Binding ProgramManager.UpdateAvailable}" />
|
||||
<ui:NavigationViewItem Classes="SampleAppNav" Content="Account" Tag="Account"
|
||||
IconSource="Contact" />
|
||||
<ui:NavigationViewItem Classes="SampleAppNav" Content="Settings" Tag="Settings"
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ public partial class MainWindow : AppWindow{
|
|||
|
||||
//select first element as default
|
||||
var nv = this.FindControl<NavigationView>("NavView");
|
||||
nv.SelectedItem = IEnumerableExtensions.ElementAt(nv.MenuItems, 0);
|
||||
nv.SelectedItem = nv.MenuItems.ElementAt(0);
|
||||
selectedNavVieItem = nv.SelectedItem;
|
||||
|
||||
MessageBus.Current.Listen<NavigationMessage>()
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@
|
|||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="CRD.Views.SeriesPageView">
|
||||
|
||||
<UserControl.Resources>
|
||||
<ui:UiListToStringConverter x:Key="UiListToStringConverter" />
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid Margin="10">
|
||||
|
|
@ -31,11 +34,11 @@
|
|||
|
||||
<StackPanel Orientation="Vertical">
|
||||
|
||||
<Grid Margin="10" VerticalAlignment="Top" >
|
||||
<Grid Margin="10" VerticalAlignment="Top">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image Grid.Column="0" Margin="10" Source="{Binding SelectedSeries.ThumbnailImage}" Width="240"
|
||||
Height="360">
|
||||
</Image>
|
||||
|
|
@ -63,6 +66,9 @@
|
|||
<Button Width="34" Height="34" Margin="0 0 10 0" Background="Transparent"
|
||||
BorderThickness="0" CornerRadius="50"
|
||||
Command="{Binding SelectedSeries.OpenCrPage}">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="Open Crunchyroll Webpage" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
<Grid>
|
||||
<controls:ImageIcon Source="../Assets/crunchy_icon_round.png" Width="30" Height="30" />
|
||||
</Grid>
|
||||
|
|
@ -72,11 +78,27 @@
|
|||
BorderThickness="0" CornerRadius="50"
|
||||
IsVisible="{Binding SonarrAvailable}"
|
||||
Command="{Binding SelectedSeries.OpenSonarrPage}">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="Open Sonarr Webpage" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
<Grid>
|
||||
<controls:ImageIcon Source="../Assets/sonarr.png" Width="30" Height="30" />
|
||||
</Grid>
|
||||
</Button>
|
||||
|
||||
<Button Width="34" Height="34" Margin="0 0 10 0" Background="Transparent"
|
||||
BorderThickness="0" CornerRadius="50"
|
||||
IsVisible="{Binding SeriesFolderPathExists}"
|
||||
Command="{Binding OpenFolderPath}">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="Open Series Folder" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
<Grid>
|
||||
<!-- <controls:ImageIcon Source="../Assets/sonarr.png" Width="30" Height="30" /> -->
|
||||
<controls:SymbolIcon Symbol="Folder" FontSize="20" Width="30" Height="30" />
|
||||
</Grid>
|
||||
</Button>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal">
|
||||
|
|
@ -202,10 +224,10 @@
|
|||
</Border>
|
||||
</Popup>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
<StackPanel IsVisible="{Binding EditMode}">
|
||||
<Button Width="30" Height="30" Margin="0 0 10 0"
|
||||
BorderThickness="0"
|
||||
<Button Width="30" Height="30" Margin="0 0 10 0"
|
||||
BorderThickness="0"
|
||||
IsVisible="{Binding SonarrConnected}"
|
||||
Command="{Binding MatchSonarrSeries_Button}">
|
||||
<Grid>
|
||||
|
|
@ -213,7 +235,6 @@
|
|||
</Grid>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
|
||||
</StackPanel>
|
||||
|
|
@ -247,15 +268,35 @@
|
|||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<TextBlock Text="E"></TextBlock>
|
||||
<TextBlock Text="{Binding Episode}"></TextBlock>
|
||||
<TextBlock Text=" - "></TextBlock>
|
||||
<TextBlock Text="{Binding EpisodeTitle}"></TextBlock>
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<TextBlock Text="E"></TextBlock>
|
||||
<TextBlock Text="{Binding Episode}"></TextBlock>
|
||||
<TextBlock Text=" - "></TextBlock>
|
||||
<TextBlock Text="{Binding EpisodeTitle}"></TextBlock>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<TextBlock FontStyle="Italic"
|
||||
FontSize="12"
|
||||
Opacity="0.8" Text="Dubs: ">
|
||||
</TextBlock>
|
||||
<TextBlock FontStyle="Italic"
|
||||
FontSize="12"
|
||||
Opacity="0.8" Text="{Binding HistoryEpisodeAvailableDubLang, Converter={StaticResource UiListToStringConverter}}" />
|
||||
</StackPanel>
|
||||
<!-- <StackPanel Orientation="Horizontal" VerticalAlignment="Center"> -->
|
||||
<!-- <TextBlock FontStyle="Italic" -->
|
||||
<!-- FontSize="12" -->
|
||||
<!-- Opacity="0.8" Text="Subs: "> -->
|
||||
<!-- </TextBlock> -->
|
||||
<!-- <TextBlock FontStyle="Italic" -->
|
||||
<!-- FontSize="12" -->
|
||||
<!-- Opacity="0.8" Text="{Binding HistoryEpisodeAvailableSoftSubs, Converter={StaticResource UiListToStringConverter}}" /> -->
|
||||
<!-- </StackPanel> -->
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<StackPanel VerticalAlignment="Center" Margin="0 0 5 0"
|
||||
IsVisible="{Binding $parent[UserControl].((vm:SeriesPageViewModel)DataContext).SonarrAvailable}">
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="clr-namespace:CRD.ViewModels"
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:structs="clr-namespace:CRD.Utils.Structs"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:DataType="vm:SettingsPageViewModel"
|
||||
x:Class="CRD.Views.SettingsPageView"
|
||||
|
|
@ -51,7 +52,8 @@
|
|||
<ListBox x:Name="ListBoxDubsSelection" SelectionMode="Multiple,Toggle" Width="210"
|
||||
MaxHeight="400"
|
||||
ItemsSource="{Binding DubLangList}"
|
||||
SelectedItems="{Binding SelectedDubLang}">
|
||||
SelectedItems="{Binding SelectedDubLang}"
|
||||
PointerWheelChanged="ListBox_PointerWheelChanged">
|
||||
</ListBox>
|
||||
</Border>
|
||||
</Popup>
|
||||
|
|
@ -101,7 +103,8 @@
|
|||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||
<ListBox x:Name="listBoxSubsSelection" SelectionMode="Multiple,Toggle" Width="210"
|
||||
MaxHeight="400"
|
||||
ItemsSource="{Binding SubLangList}" SelectedItems="{Binding SelectedSubLang}">
|
||||
ItemsSource="{Binding SubLangList}" SelectedItems="{Binding SelectedSubLang}"
|
||||
PointerWheelChanged="ListBox_PointerWheelChanged">
|
||||
</ListBox>
|
||||
</Border>
|
||||
</Popup>
|
||||
|
|
@ -120,17 +123,77 @@
|
|||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Include Signs Subtitles ">
|
||||
<controls:SettingsExpanderItem Content="Signs Subtitles " Description="Download Signs (Forced) Subtitles">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding IncludeSignSubs}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<CheckBox HorizontalAlignment="Right" IsChecked="{Binding IncludeSignSubs}"> </CheckBox>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Include CC Subtitles ">
|
||||
<!-- <StackPanel> -->
|
||||
<!-- -->
|
||||
<!-- <StackPanel Orientation="Horizontal" HorizontalAlignment="Right"> -->
|
||||
<!-- <TextBlock VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0 0 5 0" Text="Enabled"></TextBlock> -->
|
||||
<!-- <CheckBox HorizontalAlignment="Right" IsChecked="{Binding IncludeSignSubs}"> </CheckBox> -->
|
||||
<!-- </StackPanel> -->
|
||||
<!-- -->
|
||||
<!-- <StackPanel Orientation="Horizontal" IsVisible="{Binding IncludeSignSubs}"> -->
|
||||
<!-- <TextBlock VerticalAlignment="Center" Margin="0 0 5 0" Text="Mark as forced in mkv muxing"></TextBlock> -->
|
||||
<!-- <CheckBox IsChecked="{Binding SignsSubsAsForced}"> </CheckBox> -->
|
||||
<!-- </StackPanel> -->
|
||||
<!-- -->
|
||||
<!-- </StackPanel> -->
|
||||
|
||||
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
|
||||
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem IsVisible="{Binding IncludeSignSubs}" Content="Signs Subtitles" Description="Mark as forced in mkv muxing">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding IncludeCcSubs}"> </CheckBox>
|
||||
<CheckBox IsChecked="{Binding SignsSubsAsForced}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
|
||||
<controls:SettingsExpanderItem Content="CC Subtitles " Description="Download CC Subtitles">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
|
||||
<CheckBox HorizontalAlignment="Right" IsChecked="{Binding IncludeCcSubs}"> </CheckBox>
|
||||
|
||||
<!-- <StackPanel> -->
|
||||
<!-- <StackPanel Orientation="Horizontal" HorizontalAlignment="Right"> -->
|
||||
<!-- <TextBlock VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0 0 5 0" Text="Enabled"></TextBlock> -->
|
||||
<!-- <CheckBox HorizontalAlignment="Right" IsChecked="{Binding IncludeCcSubs}"> </CheckBox> -->
|
||||
<!-- </StackPanel> -->
|
||||
<!-- <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" IsVisible="{Binding IncludeCcSubs}"> -->
|
||||
<!-- <TextBlock VerticalAlignment="Center" Margin="0 0 5 0" Text="Mark as hearing impaired sub in mkv muxing"></TextBlock> -->
|
||||
<!-- <CheckBox IsChecked="{Binding CCSubsMuxingFlag}"> </CheckBox> -->
|
||||
<!-- </StackPanel> -->
|
||||
<!-- -->
|
||||
<!-- <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" IsVisible="{Binding IncludeCcSubs}"> -->
|
||||
<!-- <TextBlock VerticalAlignment="Center" Margin="0 0 5 0" Text="Font"></TextBlock> -->
|
||||
<!-- <TextBox HorizontalAlignment="Left" MinWidth="250" -->
|
||||
<!-- Text="{Binding CCSubsFont}" /> -->
|
||||
<!-- </StackPanel> -->
|
||||
<!-- </StackPanel> -->
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
|
||||
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem IsVisible="{Binding IncludeCcSubs}" Content="CC Subtitles" Description="Mark as hearing impaired sub in mkv muxing">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding CCSubsMuxingFlag}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem IsVisible="{Binding IncludeCcSubs}" Content="CC Subtitles" Description="Font">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<TextBox HorizontalAlignment="Left" MinWidth="250"
|
||||
Text="{Binding CCSubsFont}" />
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
|
||||
</controls:SettingsExpander>
|
||||
|
||||
|
|
@ -302,7 +365,7 @@
|
|||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Filename"
|
||||
Description="${seriesTitle} ${seasonTitle} ${title} ${season} ${episode} ${height} ${width}">
|
||||
Description="${seriesTitle} ${seasonTitle} ${title} ${season} ${episode} ${height} ${width} ${dubs} - Folder with \\">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<TextBox Name="FileNameTextBox" HorizontalAlignment="Left" MinWidth="250"
|
||||
Text="{Binding FileName}" />
|
||||
|
|
@ -320,19 +383,25 @@
|
|||
Description="MKVMerge and FFMpeg Settings"
|
||||
IsExpanded="False">
|
||||
|
||||
<controls:SettingsExpanderItem Content="MP4" Description="Outputs a mp4 instead of an mkv - not recommended to use this option">
|
||||
<controls:SettingsExpanderItem Content="Skip Muxing">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding SkipMuxing}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem IsVisible="{Binding !SkipMuxing}" Content="MP4" Description="Outputs a mp4 instead of a mkv - not recommended to use this option">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding MuxToMp4}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Keep Subtitles separate">
|
||||
<controls:SettingsExpanderItem IsVisible="{Binding !SkipMuxing}" Content="Keep Subtitles separate">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding SkipSubMux}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Default Audio ">
|
||||
<controls:SettingsExpanderItem IsVisible="{Binding !SkipMuxing}" Content="Default Audio ">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<ComboBox HorizontalContentAlignment="Center" MinWidth="210" MaxDropDownHeight="400"
|
||||
ItemsSource="{Binding DefaultDubLangList}"
|
||||
|
|
@ -342,7 +411,7 @@
|
|||
</controls:SettingsExpanderItem>
|
||||
|
||||
|
||||
<controls:SettingsExpanderItem Content="Default Subtitle ">
|
||||
<controls:SettingsExpanderItem IsVisible="{Binding !SkipMuxing}" Content="Default Subtitle ">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<ComboBox HorizontalContentAlignment="Center" MinWidth="210" MaxDropDownHeight="400"
|
||||
|
|
@ -355,27 +424,27 @@
|
|||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Default Subtitle Signs" Description="Will set the signs subtitle as default instead">
|
||||
<controls:SettingsExpanderItem IsVisible="{Binding !SkipMuxing}" Content="Default Subtitle Signs" Description="Will set the signs subtitle as default instead">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding DefaultSubSigns}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="File title"
|
||||
Description="${seriesTitle} ${seasonTitle} ${title} ${season} ${episode} ${height} ${width}">
|
||||
<controls:SettingsExpanderItem IsVisible="{Binding !SkipMuxing}" Content="File title"
|
||||
Description="${seriesTitle} ${seasonTitle} ${title} ${season} ${episode} ${height} ${width} ${dubs}">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<TextBox HorizontalAlignment="Left" MinWidth="250"
|
||||
Text="{Binding FileTitle}" />
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Include Episode description">
|
||||
<controls:SettingsExpanderItem IsVisible="{Binding !SkipMuxing}" Content="Include Episode description">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding IncludeEpisodeDescription}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Episode description Language">
|
||||
<controls:SettingsExpanderItem IsVisible="{Binding !SkipMuxing}" Content="Episode description Language">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<ComboBox HorizontalContentAlignment="Center" MinWidth="210" MaxDropDownHeight="400"
|
||||
ItemsSource="{Binding DescriptionLangList}"
|
||||
|
|
@ -384,13 +453,13 @@
|
|||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem 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>
|
||||
<CheckBox IsChecked="{Binding SyncTimings}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Additional MKVMerge Options">
|
||||
<controls:SettingsExpanderItem IsVisible="{Binding !SkipMuxing}" Content="Additional MKVMerge Options">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
|
||||
<StackPanel>
|
||||
|
|
@ -412,10 +481,10 @@
|
|||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border BorderBrush="#4a4a4a" Background="#4a4a4a" BorderThickness="1"
|
||||
<Border BorderBrush="#4a4a4a" Background="{DynamicResource ControlAltFillColorQuarternary}" BorderThickness="1"
|
||||
CornerRadius="10" Margin="2">
|
||||
<StackPanel Orientation="Horizontal" Margin="5">
|
||||
<TextBlock Text="{Binding ParamValue}" Margin="5,0" />
|
||||
<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].((vm:SettingsPageViewModel)DataContext).RemoveMkvMergeParam}"
|
||||
|
|
@ -426,12 +495,10 @@
|
|||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Additional FFMpeg Options">
|
||||
<controls:SettingsExpanderItem IsVisible="{Binding !SkipMuxing}" Content="Additional FFMpeg Options">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
|
|
@ -452,10 +519,10 @@
|
|||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border BorderBrush="#4a4a4a" Background="#4a4a4a" BorderThickness="1"
|
||||
<Border BorderBrush="#4a4a4a" Background="{DynamicResource ControlAltFillColorQuarternary}" BorderThickness="1"
|
||||
CornerRadius="10" Margin="2">
|
||||
<StackPanel Orientation="Horizontal" Margin="5">
|
||||
<TextBlock Text="{Binding ParamValue}" Margin="5,0" />
|
||||
<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].((vm:SettingsPageViewModel)DataContext).RemoveFfmpegParam}"
|
||||
|
|
@ -469,6 +536,71 @@
|
|||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
|
||||
<controls:SettingsExpanderItem IsVisible="{Binding !SkipMuxing}" Content="Encoding">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
|
||||
<StackPanel>
|
||||
|
||||
<CheckBox HorizontalAlignment="Right" Content="Enable Encoding?" IsChecked="{Binding IsEncodeEnabled}"> </CheckBox>
|
||||
|
||||
<ToggleButton x:Name="DropdownButtonEncodingPresets" IsVisible="{Binding IsEncodeEnabled}" Width="210" HorizontalContentAlignment="Stretch">
|
||||
<ToggleButton.Content>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock HorizontalAlignment="Center" Text="{Binding SelectedEncodingPreset.stringValue}"
|
||||
VerticalAlignment="Center" />
|
||||
<Path Grid.Column="1" Data="M 0,1 L 4,4 L 8,1" Stroke="White" StrokeThickness="1"
|
||||
VerticalAlignment="Center" Margin="5,0,5,0" Stretch="Uniform" Width="8" />
|
||||
</Grid>
|
||||
</ToggleButton.Content>
|
||||
</ToggleButton>
|
||||
<Popup IsLightDismissEnabled="True"
|
||||
IsOpen="{Binding IsChecked, ElementName=DropdownButtonEncodingPresets, Mode=TwoWay}"
|
||||
Placement="Bottom"
|
||||
PlacementTarget="{Binding ElementName=DropdownButtonEncodingPresets}">
|
||||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||
<ListBox x:Name="ListBoxEncodingPresetSelection" SelectionMode="AlwaysSelected,Single" Width="210"
|
||||
MaxHeight="400"
|
||||
ItemsSource="{Binding EncodingPresetsList}"
|
||||
SelectedItem="{Binding SelectedEncodingPreset}"
|
||||
PointerWheelChanged="ListBox_PointerWheelChanged">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type structs:StringItem}">
|
||||
<TextBlock Text="{Binding stringValue}"></TextBlock>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Border>
|
||||
</Popup>
|
||||
|
||||
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button HorizontalAlignment="Center" Margin="5 10" IsVisible="{Binding IsEncodeEnabled}" Command="{Binding CreateEncodingPresetButtonPress}" CommandParameter="false">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Add" FontSize="18" Margin=" 0 0 5 0" />
|
||||
<TextBlock VerticalAlignment="Center" Text="Create Preset"></TextBlock>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<Button HorizontalAlignment="Center" Margin="5 10" IsVisible="{Binding IsEncodeEnabled}" Command="{Binding CreateEncodingPresetButtonPress}" CommandParameter="true">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Edit" FontSize="18" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
</StackPanel>
|
||||
|
||||
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
|
||||
<controls:SettingsExpander.Footer>
|
||||
|
||||
</controls:SettingsExpander.Footer>
|
||||
|
|
@ -516,6 +648,36 @@
|
|||
|
||||
</controls:SettingsExpander>
|
||||
|
||||
<controls:SettingsExpander Header="Proxy Settings"
|
||||
IconSource="Wifi3"
|
||||
Description="Adjust proxy settings – requires a restart to take effect"
|
||||
IsExpanded="False">
|
||||
|
||||
<controls:SettingsExpanderItem Content="Use Proxy">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding ProxyEnabled}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Host">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<TextBox HorizontalAlignment="Left" MinWidth="250"
|
||||
Text="{Binding ProxyHost}" />
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Port">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<controls:NumberBox Minimum="0" Maximum="65535"
|
||||
Value="{Binding ProxyPort}"
|
||||
SpinButtonPlacementMode="Inline"
|
||||
HorizontalAlignment="Stretch" />
|
||||
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
</controls:SettingsExpander>
|
||||
|
||||
<controls:SettingsExpander Header="App Theme"
|
||||
IconSource="DarkTheme"
|
||||
Description="Change the current app theme">
|
||||
|
|
@ -692,6 +854,27 @@
|
|||
|
||||
</controls:SettingsExpander>
|
||||
|
||||
<controls:SettingsExpander Header="IP"
|
||||
IconSource="Wifi4"
|
||||
Description="Check the current IP address to verify if traffic is being routed through a VPN">
|
||||
<controls:SettingsExpander.Footer>
|
||||
<Grid VerticalAlignment="Center">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Border VerticalAlignment="Center" Height="30"> <!-- Match this to the Button's height -->
|
||||
<TextBlock Text="{Binding CurrentIp}" VerticalAlignment="Center" FontSize="14" />
|
||||
</Border>
|
||||
|
||||
<Button Grid.Column="1" Content="Check" Margin="10 0 0 0" Command="{Binding CheckIp}" />
|
||||
</Grid>
|
||||
|
||||
</controls:SettingsExpander.Footer>
|
||||
|
||||
</controls:SettingsExpander>
|
||||
|
||||
|
||||
<Grid Margin="0 0 0 10"
|
||||
ColumnDefinitions="*,Auto" RowDefinitions="*,Auto">
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
using Avalonia;
|
||||
using System.Linq;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.VisualTree;
|
||||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils.Sonarr;
|
||||
|
|
@ -20,4 +22,19 @@ public partial class SettingsPageView : UserControl{
|
|||
SonarrClient.Instance.RefreshSonarr();
|
||||
}
|
||||
}
|
||||
|
||||
private void ListBox_PointerWheelChanged(object sender, Avalonia.Input.PointerWheelEventArgs e){
|
||||
var listBox = sender as ListBox;
|
||||
var scrollViewer = listBox?.GetVisualDescendants().OfType<ScrollViewer>().FirstOrDefault();
|
||||
|
||||
if (scrollViewer != null){
|
||||
// Determine if the ListBox is at its bounds (top or bottom)
|
||||
bool atTop = scrollViewer.Offset.Y <= 0 && e.Delta.Y > 0;
|
||||
bool atBottom = scrollViewer.Offset.Y >= scrollViewer.Extent.Height - scrollViewer.Viewport.Height && e.Delta.Y < 0;
|
||||
|
||||
if (atTop || atBottom){
|
||||
e.Handled = true; // Stop the event from propagating to the parent
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
109
CRD/Views/Utils/ContentDialogEncodingPresetView.axaml
Normal file
109
CRD/Views/Utils/ContentDialogEncodingPresetView.axaml
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:utils="clr-namespace:CRD.ViewModels.Utils"
|
||||
x:DataType="utils:ContentDialogEncodingPresetViewModel"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="CRD.Views.Utils.ContentDialogEncodingPresetView">
|
||||
|
||||
|
||||
<StackPanel Spacing="10" Margin="10">
|
||||
|
||||
<StackPanel IsVisible="{Binding EditMode}">
|
||||
<TextBlock Text="Edit Preset" Margin="0,0,0,5" />
|
||||
<ComboBox HorizontalContentAlignment="Center" MinWidth="210" MaxDropDownHeight="400"
|
||||
ItemsSource="{Binding CustomPresetsList}"
|
||||
SelectedItem="{Binding SelectedCustomPreset}">
|
||||
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding PresetName}"></TextBlock>
|
||||
</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>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
|
||||
</UserControl>
|
||||
11
CRD/Views/Utils/ContentDialogEncodingPresetView.axaml.cs
Normal file
11
CRD/Views/Utils/ContentDialogEncodingPresetView.axaml.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace CRD.Views.Utils;
|
||||
|
||||
public partial class ContentDialogEncodingPresetView : UserControl{
|
||||
public ContentDialogEncodingPresetView(){
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
|
@ -9,8 +9,7 @@
|
|||
x:Class="CRD.Views.Utils.ContentDialogUpdateView">
|
||||
|
||||
<StackPanel Spacing="10" MinWidth="400">
|
||||
|
||||
<TextBlock Text="Please wait while the update is being downloaded..." HorizontalAlignment="Center" Margin="0,10,0,20"/>
|
||||
<ProgressBar Minimum="0" Maximum="100" Value="{Binding Progress}" HorizontalAlignment="Center" VerticalAlignment="Center" Width="350"/>
|
||||
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
Loading…
Reference in a new issue