diff --git a/CRD/Downloader/Crunchyroll/CRAuth.cs b/CRD/Downloader/Crunchyroll/CRAuth.cs index 7d33e7a..8a23f62 100644 --- a/CRD/Downloader/Crunchyroll/CRAuth.cs +++ b/CRD/Downloader/Crunchyroll/CRAuth.cs @@ -18,8 +18,8 @@ public class CrAuth{ private readonly string authorization = ApiUrls.authBasicMob; private readonly string userAgent = ApiUrls.MobileUserAgent; - private const string DeviceType = "OnePlus CPH2449"; - private const string DeviceName = "CPH2449"; + private readonly string deviceType = "OnePlus CPH2449"; + private readonly string deviceName = "CPH2449"; public async Task AuthAnonymous(){ string uuid = Guid.NewGuid().ToString(); @@ -28,10 +28,13 @@ public class CrAuth{ { "grant_type", "client_id" }, { "scope", "offline_access" }, { "device_id", uuid }, - { "device_name", DeviceName }, - { "device_type", DeviceType }, + { "device_type", deviceType }, }; + if (!string.IsNullOrEmpty(deviceName)){ + formData.Add("device_name", deviceName); + } + var requestContent = new FormUrlEncodedContent(formData); var crunchyAuthHeaders = new Dictionary{ @@ -78,15 +81,18 @@ public class CrAuth{ string uuid = Guid.NewGuid().ToString(); var formData = new Dictionary{ - { "username", data.Username }, // Replace with actual data - { "password", data.Password }, // Replace with actual data + { "username", data.Username }, + { "password", data.Password }, { "grant_type", "password" }, { "scope", "offline_access" }, { "device_id", uuid }, - { "device_name", DeviceName }, - { "device_type", DeviceType }, + { "device_type", deviceType }, }; + if (!string.IsNullOrEmpty(deviceName)){ + formData.Add("device_name", deviceName); + } + var requestContent = new FormUrlEncodedContent(formData); var crunchyAuthHeaders = new Dictionary{ @@ -192,10 +198,13 @@ public class CrAuth{ { "scope", "offline_access" }, { "device_id", uuid }, { "grant_type", "refresh_token" }, - { "device_name", DeviceName }, - { "device_type", DeviceType }, + { "device_type", deviceType }, }; + if (!string.IsNullOrEmpty(deviceName)){ + formData.Add("device_name", deviceName); + } + var requestContent = new FormUrlEncodedContent(formData); var crunchyAuthHeaders = new Dictionary{ @@ -250,10 +259,13 @@ public class CrAuth{ { "grant_type", "refresh_token" }, { "scope", "offline_access" }, { "device_id", uuid }, - { "device_name", DeviceName }, - { "device_type", DeviceType }, + { "device_type", deviceType }, }; + if (!string.IsNullOrEmpty(deviceName)){ + formData.Add("device_name", deviceName); + } + var requestContent = new FormUrlEncodedContent(formData); var crunchyAuthHeaders = new Dictionary{ diff --git a/CRD/Downloader/Crunchyroll/CrEpisode.cs b/CRD/Downloader/Crunchyroll/CrEpisode.cs index 1f7bb2f..051bd07 100644 --- a/CRD/Downloader/Crunchyroll/CrEpisode.cs +++ b/CRD/Downloader/Crunchyroll/CrEpisode.cs @@ -281,4 +281,18 @@ public class CrEpisode(){ return complete; } + + public async Task MarkAsWatched(string episodeId){ + + var request = HttpClientReq.CreateRequestMessage($"{ApiUrls.Content}/discover/{crunInstance.Token?.account_id}/mark_as_watched/{episodeId}", HttpMethod.Post, true,false,null); + + var response = await HttpClientReq.Instance.SendHttpRequest(request); + + if (!response.IsOk){ + Console.Error.WriteLine($"Mark as watched for {episodeId} failed"); + } + + } + + } \ No newline at end of file diff --git a/CRD/Downloader/Crunchyroll/CrunchyrollManager.cs b/CRD/Downloader/Crunchyroll/CrunchyrollManager.cs index 6e86638..cb6ce76 100644 --- a/CRD/Downloader/Crunchyroll/CrunchyrollManager.cs +++ b/CRD/Downloader/Crunchyroll/CrunchyrollManager.cs @@ -96,6 +96,7 @@ public class CrunchyrollManager{ private CrDownloadOptions InitDownloadOptions(){ var options = new CrDownloadOptions(); + options.UseCrBetaApi = true; options.AutoDownload = false; options.RemoveFinishedDownload = false; options.Chapters = true; @@ -146,6 +147,7 @@ public class CrunchyrollManager{ if (Path.Exists(CfgManager.PathCrDownloadOptionsOld)){ var optionsYaml = new CrDownloadOptionsYaml(); + optionsYaml.UseCrBetaApi = true; optionsYaml.AutoDownload = false; optionsYaml.RemoveFinishedDownload = false; optionsYaml.Chapters = true; @@ -349,6 +351,8 @@ public class CrunchyrollManager{ foreach (var keyValue in groupByDub){ var result = await MuxStreams(keyValue.Value, new CrunchyMuxOptions{ + DubLangList = options.DubLang, + SubLangList = options.DlSubs, FfmpegOptions = options.FfmpegOptions, SkipSubMux = options.SkipSubsMux, Output = fileNameAndPath, @@ -364,7 +368,11 @@ public class CrunchyrollManager{ CcTag = options.CcTag, KeepAllVideos = true, MuxDescription = options.IncludeVideoDescription, - DlVideoOnce = options.DlVideoOnce + DlVideoOnce = options.DlVideoOnce, + DefaultSubSigns = options.DefaultSubSigns, + DefaultSubForcedDisplay = options.DefaultSubForcedDisplay, + CcSubsMuxingFlag = options.CcSubsMuxingFlag, + SignsSubsAsForced = options.SignsSubsAsForced, }, fileNameAndPath); @@ -403,6 +411,8 @@ public class CrunchyrollManager{ } else{ var result = await MuxStreams(res.Data, new CrunchyMuxOptions{ + DubLangList = options.DubLang, + SubLangList = options.DlSubs, FfmpegOptions = options.FfmpegOptions, SkipSubMux = options.SkipSubsMux, Output = fileNameAndPath, @@ -418,7 +428,11 @@ public class CrunchyrollManager{ CcTag = options.CcTag, KeepAllVideos = true, MuxDescription = options.IncludeVideoDescription, - DlVideoOnce = options.DlVideoOnce + DlVideoOnce = options.DlVideoOnce, + DefaultSubSigns = options.DefaultSubSigns, + DefaultSubForcedDisplay = options.DefaultSubForcedDisplay, + CcSubsMuxingFlag = options.CcSubsMuxingFlag, + SignsSubsAsForced = options.SignsSubsAsForced, }, fileNameAndPath); @@ -497,6 +511,9 @@ public class CrunchyrollManager{ History.SetAsDownloaded(data.SeriesId, data.SeasonId, data.Data.First().MediaId); } + if (options.MarkAsWatched && data.Data is{ Count: > 0 }){ + _ = CrEpisode.MarkAsWatched(data.Data.First().MediaId); + } return true; } @@ -623,6 +640,8 @@ public class CrunchyrollManager{ var merger = new Merger(new MergerOptions{ + DubLangList = options.DubLangList, + SubLangList = options.SubLangList, OnlyVid = data.Where(a => a.Type == DownloadMediaType.Video).Select(a => new MergerInput{ Language = a.Lang, Path = a.Path ?? string.Empty }).ToList(), SkipSubMux = options.SkipSubMux, OnlyAudio = data.Where(a => a.Type == DownloadMediaType.Audio).Select(a => new MergerInput{ Language = a.Lang, Path = a.Path ?? string.Empty }).ToList(), @@ -643,6 +662,10 @@ public class CrunchyrollManager{ }, CcTag = options.CcTag, mp3 = muxToMp3, + DefaultSubSigns = options.DefaultSubSigns, + DefaultSubForcedDisplay = options.DefaultSubForcedDisplay, + CcSubsMuxingFlag = options.CcSubsMuxingFlag, + SignsSubsAsForced = options.SignsSubsAsForced, Description = muxDesc ? data.Where(a => a.Type == DownloadMediaType.Description).Select(a => new MergerInput{ Path = a.Path ?? string.Empty }).ToList() :[], }); @@ -805,7 +828,7 @@ public class CrunchyrollManager{ if (data.Data is{ Count: > 0 }){ options.Partsize = options.Partsize > 0 ? options.Partsize : 1; - + var sortedMetaData = data.Data .OrderBy(metaData => options.DubLang.IndexOf(metaData.Lang?.CrLocale ?? string.Empty) != -1 ? options.DubLang.IndexOf(metaData.Lang?.CrLocale ?? string.Empty) : int.MaxValue) .ToList(); @@ -1332,7 +1355,7 @@ public class CrunchyrollManager{ Data = files, Error = dlFailed, FileName = fileName.Length > 0 ? (Path.IsPathRooted(fileName) ? fileName : Path.Combine(fileDir, fileName)) : "./unknown", - ErrorText = "" + ErrorText = "Audio or Video download failed", }; } diff --git a/CRD/Downloader/Crunchyroll/ViewModels/CrunchyrollSettingsViewModel.cs b/CRD/Downloader/Crunchyroll/ViewModels/CrunchyrollSettingsViewModel.cs index 5884c31..6db931d 100644 --- a/CRD/Downloader/Crunchyroll/ViewModels/CrunchyrollSettingsViewModel.cs +++ b/CRD/Downloader/Crunchyroll/ViewModels/CrunchyrollSettingsViewModel.cs @@ -227,6 +227,15 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{ [ObservableProperty] private bool _searchFetchFeaturedMusic; + [ObservableProperty] + private bool _useCrBetaApi; + + [ObservableProperty] + private bool _downloadFirstAvailableDub; + + [ObservableProperty] + private bool _markAsWatched; + private bool settingsLoaded; public CrunchyrollSettingsViewModel(){ @@ -280,6 +289,9 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{ AddScaledBorderAndShadow = options.SubsAddScaledBorder is ScaledBorderAndShadowSelection.ScaledBorderAndShadowNo or ScaledBorderAndShadowSelection.ScaledBorderAndShadowYes; SelectedScaledBorderAndShadow = GetScaledBorderAndShadowFromOptions(options); + MarkAsWatched = options.MarkAsWatched; + DownloadFirstAvailableDub = options.DownloadFirstAvailableDub; + UseCrBetaApi = options.UseCrBetaApi; CCSubsFont = options.CcSubsFont ?? ""; CCSubsMuxingFlag = options.CcSubsMuxingFlag; SignsSubsAsForced = options.SignsSubsAsForced; @@ -344,6 +356,9 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{ return; } + CrunchyrollManager.Instance.CrunOptions.MarkAsWatched = MarkAsWatched; + CrunchyrollManager.Instance.CrunOptions.DownloadFirstAvailableDub = DownloadFirstAvailableDub; + CrunchyrollManager.Instance.CrunOptions.UseCrBetaApi = UseCrBetaApi; CrunchyrollManager.Instance.CrunOptions.SignsSubsAsForced = SignsSubsAsForced; CrunchyrollManager.Instance.CrunOptions.CcSubsMuxingFlag = CCSubsMuxingFlag; CrunchyrollManager.Instance.CrunOptions.CcSubsFont = CCSubsFont; diff --git a/CRD/Downloader/Crunchyroll/Views/CrunchyrollSettingsView.axaml b/CRD/Downloader/Crunchyroll/Views/CrunchyrollSettingsView.axaml index e8ad4f3..b5edb2b 100644 --- a/CRD/Downloader/Crunchyroll/Views/CrunchyrollSettingsView.axaml +++ b/CRD/Downloader/Crunchyroll/Views/CrunchyrollSettingsView.axaml @@ -9,7 +9,7 @@ x:DataType="vm:CrunchyrollSettingsViewModel" x:Class="CRD.Downloader.Crunchyroll.Views.CrunchyrollSettingsView"> - + @@ -17,6 +17,7 @@ + @@ -203,10 +204,10 @@ - - + + - + + + + + + + + + + + + + @@ -541,6 +554,27 @@ + + + + + + + + + + + + + + + + + + + diff --git a/CRD/Downloader/ProgramManager.cs b/CRD/Downloader/ProgramManager.cs index 266b4a5..40cfdca 100644 --- a/CRD/Downloader/ProgramManager.cs +++ b/CRD/Downloader/ProgramManager.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Threading.Tasks; using Avalonia; using Avalonia.Controls.ApplicationLifetimes; @@ -175,7 +176,10 @@ public partial class ProgramManager : ObservableObject{ private void CleanUpOldUpdater(){ - string backupFilePath = Path.Combine(Directory.GetCurrentDirectory(), "Updater.exe.bak"); + + var executableExtension = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : string.Empty; + + string backupFilePath = Path.Combine(Directory.GetCurrentDirectory(), $"Updater{executableExtension}.bak"); if (File.Exists(backupFilePath)){ try{ diff --git a/CRD/Downloader/QueueManager.cs b/CRD/Downloader/QueueManager.cs index 5848260..f43bb6e 100644 --- a/CRD/Downloader/QueueManager.cs +++ b/CRD/Downloader/QueueManager.cs @@ -5,8 +5,10 @@ using System.Collections.Specialized; using System.Linq; using System.Threading.Tasks; using CRD.Downloader.Crunchyroll; +using CRD.Utils; using CRD.Utils.CustomList; using CRD.Utils.Structs; +using CRD.Utils.Structs.Crunchyroll; using CRD.Utils.Structs.History; using CRD.ViewModels; using CRD.Views; @@ -61,6 +63,8 @@ public class QueueManager{ Console.Error.WriteLine("Failed to Remove Episode from list"); } } + } else if (e.Action == NotifyCollectionChangedAction.Reset && Queue.Count == 0){ + DownloadItemModels.Clear(); } UpdateDownloadListItems(); @@ -151,14 +155,41 @@ public class QueueManager{ selected.OnlySubs = onlySubs; + if (CrunchyrollManager.Instance.CrunOptions.DownloadFirstAvailableDub && selected.Data.Count > 1){ + var sortedMetaData = selected.Data + .OrderBy(metaData => { + var locale = metaData.Lang?.CrLocale ?? string.Empty; + var index = dubLang.IndexOf(locale); + return index != -1 ? index : int.MaxValue; + }) + .ToList(); + + if (sortedMetaData.Count != 0){ + var first = sortedMetaData.First(); + selected.Data =[first]; + selected.SelectedDubs =[first.Lang?.CrLocale ?? string.Empty]; + } + } + + var newOptions = Helpers.DeepCopy(CrunchyrollManager.Instance.CrunOptions); + + if (selected.OnlySubs){ + newOptions.Novids = true; + newOptions.Noaudio = true; + } + + newOptions.DubLang = dubLang; + + selected.DownloadSettings = newOptions; + Queue.Add(selected); - if (selected.Data.Count < dubLang.Count){ + if (selected.Data.Count < dubLang.Count && !CrunchyrollManager.Instance.CrunOptions.DownloadFirstAvailableDub){ Console.WriteLine("Added Episode to Queue but couldn't find all selected dubs"); Console.Error.WriteLine("Added Episode to Queue but couldn't find all selected dubs - Available dubs/subs: "); var languages = sList.EpisodeAndLanguages.Items.Select((a, index) => - $"{(a.IsPremiumOnly ? "+ " : "")}{sList.EpisodeAndLanguages.Langs.ElementAtOrDefault(index).CrLocale ?? "Unknown"}").ToArray(); + $"{(a.IsPremiumOnly ? "+ " : "")}{sList.EpisodeAndLanguages.Langs.ElementAtOrDefault(index)?.CrLocale ?? "Unknown"}").ToArray(); Console.Error.WriteLine( $"{selected.SeasonTitle} - Season {selected.Season} - {selected.EpisodeTitle} dubs - [{string.Join(", ", languages)}] subs - [{string.Join(", ", selected.AvailableSubs ??[])}]"); @@ -172,7 +203,7 @@ public class QueueManager{ Console.Error.WriteLine("Episode couldn't be added to Queue - Available dubs/subs: "); var languages = sList.EpisodeAndLanguages.Items.Select((a, index) => - $"{(a.IsPremiumOnly ? "+ " : "")}{sList.EpisodeAndLanguages.Langs.ElementAtOrDefault(index).CrLocale ?? "Unknown"}").ToArray(); + $"{(a.IsPremiumOnly ? "+ " : "")}{sList.EpisodeAndLanguages.Langs.ElementAtOrDefault(index)?.CrLocale ?? "Unknown"}").ToArray(); Console.Error.WriteLine($"{selected.SeasonTitle} - Season {selected.Season} - {selected.EpisodeTitle} dubs - [{string.Join(", ", languages)}] subs - [{string.Join(", ", selected.AvailableSubs ??[])}]"); MessageBus.Current.SendMessage(new ToastMessage($"Couldn't add episode to the queue with current dub settings", ToastType.Error, 2)); @@ -188,6 +219,18 @@ public class QueueManager{ if (movieMeta != null){ movieMeta.DownloadSubs = CrunchyrollManager.Instance.CrunOptions.DlSubs; movieMeta.OnlySubs = onlySubs; + + var newOptions = Helpers.DeepCopy(CrunchyrollManager.Instance.CrunOptions); + + if (movieMeta.OnlySubs){ + newOptions.Novids = true; + newOptions.Noaudio = true; + } + + newOptions.DubLang = dubLang; + + movieMeta.DownloadSettings = newOptions; + Queue.Add(movieMeta); Console.WriteLine("Added Movie to Queue"); @@ -199,6 +242,9 @@ public class QueueManager{ public void CrAddMusicMetaToQueue(CrunchyEpMeta epMeta){ + var newOptions = Helpers.DeepCopy(CrunchyrollManager.Instance.CrunOptions); + epMeta.DownloadSettings = newOptions; + Queue.Add(epMeta); MessageBus.Current.SendMessage(new ToastMessage($"Added episode to the queue", ToastType.Information, 1)); } @@ -210,15 +256,18 @@ public class QueueManager{ if (musicVideo != null){ var musicVideoMeta = CrunchyrollManager.Instance.CrMusic.EpisodeMeta(musicVideo); - + (HistoryEpisode? historyEpisode, List dublist, List sublist, string downloadDirPath, string videoQuality) historyEpisode = (null, [], [], "", ""); if (CrunchyrollManager.Instance.CrunOptions.History){ historyEpisode = CrunchyrollManager.Instance.History.GetHistoryEpisodeWithDubListAndDownloadDir(musicVideoMeta.SeriesId, musicVideoMeta.SeasonId, musicVideoMeta.Data.First().MediaId); } - + musicVideoMeta.VideoQuality = !string.IsNullOrEmpty(historyEpisode.videoQuality) ? historyEpisode.videoQuality : CrunchyrollManager.Instance.CrunOptions.QualityVideo; - + + var newOptions = Helpers.DeepCopy(CrunchyrollManager.Instance.CrunOptions); + musicVideoMeta.DownloadSettings = newOptions; + Queue.Add(musicVideoMeta); MessageBus.Current.SendMessage(new ToastMessage($"Added music video to the queue", ToastType.Information, 1)); } @@ -231,14 +280,18 @@ public class QueueManager{ if (concert != null){ var concertMeta = CrunchyrollManager.Instance.CrMusic.EpisodeMeta(concert); - + (HistoryEpisode? historyEpisode, List dublist, List sublist, string downloadDirPath, string videoQuality) historyEpisode = (null, [], [], "", ""); if (CrunchyrollManager.Instance.CrunOptions.History){ historyEpisode = CrunchyrollManager.Instance.History.GetHistoryEpisodeWithDubListAndDownloadDir(concertMeta.SeriesId, concertMeta.SeasonId, concertMeta.Data.First().MediaId); } - + concertMeta.VideoQuality = !string.IsNullOrEmpty(historyEpisode.videoQuality) ? historyEpisode.videoQuality : CrunchyrollManager.Instance.CrunOptions.QualityVideo; + + var newOptions = Helpers.DeepCopy(CrunchyrollManager.Instance.CrunOptions); + concertMeta.DownloadSettings = newOptions; + Queue.Add(concertMeta); MessageBus.Current.SendMessage(new ToastMessage($"Added concert to the queue", ToastType.Information, 1)); } @@ -285,6 +338,34 @@ public class QueueManager{ crunchyEpMeta.DownloadSubs = subLangList.sublist.Count > 0 ? subLangList.sublist : CrunchyrollManager.Instance.CrunOptions.DlSubs; + if (CrunchyrollManager.Instance.CrunOptions.DownloadFirstAvailableDub && crunchyEpMeta.Data.Count > 1){ + var sortedMetaData = crunchyEpMeta.Data + .OrderBy(metaData => { + var locale = metaData.Lang?.CrLocale ?? string.Empty; + var index = data.DubLang.IndexOf(locale); + return index != -1 ? index : int.MaxValue; + }) + .ToList(); + + if (sortedMetaData.Count != 0){ + var first = sortedMetaData.First(); + crunchyEpMeta.Data =[first]; + crunchyEpMeta.SelectedDubs =[first.Lang?.CrLocale ?? string.Empty]; + } + } + + var newOptions = Helpers.DeepCopy(CrunchyrollManager.Instance.CrunOptions); + + if (crunchyEpMeta.OnlySubs){ + newOptions.Novids = true; + newOptions.Noaudio = true; + } + + newOptions.DubLang = data.DubLang; + + crunchyEpMeta.DownloadSettings = newOptions; + + Queue.Add(crunchyEpMeta); } else{ failed = true; diff --git a/CRD/Utils/Helpers.cs b/CRD/Utils/Helpers.cs index 5992515..8929177 100644 --- a/CRD/Utils/Helpers.cs +++ b/CRD/Utils/Helpers.cs @@ -21,6 +21,7 @@ using CRD.Utils.Structs; using CRD.Utils.Structs.Crunchyroll; using Microsoft.Win32; using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; namespace CRD.Utils; @@ -39,10 +40,19 @@ public class Helpers{ } public static T DeepCopy(T obj){ - var json = JsonConvert.SerializeObject(obj); + var settings = new JsonSerializerSettings{ + ContractResolver = new DefaultContractResolver{ + IgnoreSerializableAttribute = true, + IgnoreSerializableInterface = true + }, + ObjectCreationHandling = ObjectCreationHandling.Replace + }; + + var json = JsonConvert.SerializeObject(obj, settings); return JsonConvert.DeserializeObject(json); } + public static string ConvertTimeFormat(string time){ var timeParts = time.Split(':', '.'); int hours = int.Parse(timeParts[0]); diff --git a/CRD/Utils/Http/HttpClientReq.cs b/CRD/Utils/Http/HttpClientReq.cs index 1ef16cd..7bb4d10 100644 --- a/CRD/Utils/Http/HttpClientReq.cs +++ b/CRD/Utils/Http/HttpClientReq.cs @@ -249,21 +249,21 @@ public static class ApiUrls{ public static readonly string ApiBeta = "https://beta-api.crunchyroll.com"; public static readonly string ApiN = "https://www.crunchyroll.com"; public static readonly string Anilist = "https://graphql.anilist.co"; - - public static readonly string Auth = ApiN + "/auth/v1/token"; - public static readonly string Profile = ApiN + "/accounts/v1/me/profile"; - public static readonly string CmsToken = ApiN + "/index/v2"; - public static readonly string Search = ApiN + "/content/v2/discover/search"; - public static readonly string Browse = ApiN + "/content/v2/discover/browse"; - public static readonly string Cms = ApiN + "/content/v2/cms"; - public static readonly string Content = ApiN + "/content/v2"; - + + public static string Auth => (CrunchyrollManager.Instance.CrunOptions.UseCrBetaApi ? ApiBeta : ApiN) + "/auth/v1/token"; + public static string Profile => (CrunchyrollManager.Instance.CrunOptions.UseCrBetaApi ? ApiBeta : ApiN) + "/accounts/v1/me/profile"; + public static string CmsToken => (CrunchyrollManager.Instance.CrunOptions.UseCrBetaApi ? ApiBeta : ApiN) + "/index/v2"; + public static string Search => (CrunchyrollManager.Instance.CrunOptions.UseCrBetaApi ? ApiBeta : ApiN) + "/content/v2/discover/search"; + public static string Browse => (CrunchyrollManager.Instance.CrunOptions.UseCrBetaApi ? ApiBeta : ApiN) + "/content/v2/discover/browse"; + public static string Cms => (CrunchyrollManager.Instance.CrunOptions.UseCrBetaApi ? ApiBeta : ApiN) + "/content/v2/cms"; + public static string Content => (CrunchyrollManager.Instance.CrunOptions.UseCrBetaApi ? ApiBeta : ApiN) + "/content/v2"; + + public static string Subscription => (CrunchyrollManager.Instance.CrunOptions.UseCrBetaApi ? ApiBeta : ApiN) + "/subs/v3/subscriptions/"; + public static readonly string BetaBrowse = ApiBeta + "/content/v1/browse"; public static readonly string BetaCms = ApiBeta + "/cms/v2"; public static readonly string DRM = ApiBeta + "/drm/v1/auth"; - public static readonly string Subscription = ApiN + "/subs/v3/subscriptions/"; - public static readonly string authBasic = "Basic bm9haWhkZXZtXzZpeWcwYThsMHE6"; public static readonly string authBasicMob = "Basic ZG1yeWZlc2NkYm90dWJldW56NXo6NU45aThPV2cyVmtNcm1oekNfNUNXekRLOG55SXo0QU0="; diff --git a/CRD/Utils/Muxing/Merger.cs b/CRD/Utils/Muxing/Merger.cs index 6169a0a..e24099f 100644 --- a/CRD/Utils/Muxing/Merger.cs +++ b/CRD/Utils/Muxing/Merger.cs @@ -86,7 +86,7 @@ public class Merger{ args.Add($"-i \"{sub.value.File}\""); metaData.Add($"-map {index}:s"); if (options.Defaults.Sub.Code == sub.value.Language.Code && - (CrunchyrollManager.Instance.CrunOptions.DefaultSubSigns == sub.value.Signs || CrunchyrollManager.Instance.CrunOptions.DefaultSubSigns && !hasSignsSub) + (options.DefaultSubSigns == sub.value.Signs || options.DefaultSubSigns && !hasSignsSub) && sub.value.ClosedCaption == false){ metaData.Add($"-disposition:s:{sub.i} default"); } else{ @@ -168,9 +168,9 @@ public class Merger{ args.Add($"\"{Helpers.AddUncPrefixIfNeeded(vid.Path)}\""); } } - + var sortedAudio = options.OnlyAudio - .OrderBy(sub => CrunchyrollManager.Instance.CrunOptions.DubLang.IndexOf(sub.Language.CrLocale) != -1 ? CrunchyrollManager.Instance.CrunOptions.DubLang.IndexOf(sub.Language.CrLocale) : int.MaxValue) + .OrderBy(sub => options.DubLangList.IndexOf(sub.Language.CrLocale) != -1 ? options.DubLangList.IndexOf(sub.Language.CrLocale) : int.MaxValue) .ToList(); foreach (var aud in sortedAudio){ @@ -198,11 +198,12 @@ public class Merger{ bool hasSignsSub = options.Subtitles.Any(sub => sub.Signs && options.Defaults.Sub.Code == sub.Language.Code); var sortedSubtitles = options.Subtitles - .OrderBy(sub => CrunchyrollManager.Instance.CrunOptions.DlSubs.IndexOf(sub.Language.CrLocale) != -1 ? CrunchyrollManager.Instance.CrunOptions.DlSubs.IndexOf(sub.Language.CrLocale) : int.MaxValue) - .ThenBy(sub => sub.Signs ? 0 : 1) - .ThenBy(sub => sub.ClosedCaption ? 0 : 1) + .OrderBy(sub => options.SubLangList.IndexOf(sub.Language.CrLocale) != -1 + ? options.SubLangList.IndexOf(sub.Language.CrLocale) + : int.MaxValue) + .ThenBy(sub => sub.ClosedCaption ? 2 : sub.Signs ? 1 : 0) .ToList(); - + foreach (var subObj in sortedSubtitles){ bool isForced = false; if (subObj.Delay.HasValue){ @@ -210,17 +211,17 @@ public class Merger{ args.Add($"--sync 0:{delay}"); } - string trackNameExtra = subObj.ClosedCaption == true ? $" {options.CcTag}" : ""; - trackNameExtra += subObj.Signs == true ? " Signs" : ""; + string trackNameExtra = subObj.ClosedCaption ? $" {options.CcTag}" : ""; + trackNameExtra += subObj.Signs ? " Signs" : ""; string trackName = $"0:\"{(subObj.Language.Language ?? subObj.Language.Name) + trackNameExtra}\""; args.Add($"--track-name {trackName}"); args.Add($"--language 0:\"{subObj.Language.Code}\""); if (options.Defaults.Sub.Code == subObj.Language.Code && - (CrunchyrollManager.Instance.CrunOptions.DefaultSubSigns == subObj.Signs || CrunchyrollManager.Instance.CrunOptions.DefaultSubSigns && !hasSignsSub) && subObj.ClosedCaption == false){ + (options.DefaultSubSigns == subObj.Signs || options.DefaultSubSigns && !hasSignsSub) && subObj.ClosedCaption == false){ args.Add("--default-track 0"); - if (CrunchyrollManager.Instance.CrunOptions.DefaultSubForcedDisplay){ + if (options.DefaultSubForcedDisplay){ args.Add("--forced-track 0:yes"); isForced = true; } @@ -228,11 +229,11 @@ public class Merger{ args.Add("--default-track 0:0"); } - if (subObj.ClosedCaption == true && CrunchyrollManager.Instance.CrunOptions.CcSubsMuxingFlag){ + if (subObj.ClosedCaption && options.CcSubsMuxingFlag){ args.Add("--hearing-impaired-flag 0:yes"); } - if (subObj.Signs && CrunchyrollManager.Instance.CrunOptions.SignsSubsAsForced && !isForced){ + if (subObj.Signs && options.SignsSubsAsForced && !isForced){ args.Add("--forced-track 0:yes"); } @@ -302,7 +303,7 @@ public class Merger{ Console.Error.WriteLine("Failed to retrieve video durations"); return -100; } - + var extractFramesBaseEnd = await SyncingHelper.ExtractFrames(baseVideoPath, baseFramesDirEnd, baseVideoDurationTimeSpan.Value.TotalSeconds - 360, 360); var extractFramesCompareEnd = await SyncingHelper.ExtractFrames(compareVideoPath, compareFramesDirEnd, compareVideoDurationTimeSpan.Value.TotalSeconds - 360, 360); @@ -333,21 +334,20 @@ public class Merger{ Time = GetTimeFromFileName(fp, extractFramesCompareEnd.frameRate) }).ToList(); - - + // Calculate offsets var startOffset = SyncingHelper.CalculateOffset(baseFramesStart, compareFramesStart); - var endOffset = SyncingHelper.CalculateOffset(baseFramesEnd, compareFramesEnd,true); + var endOffset = SyncingHelper.CalculateOffset(baseFramesEnd, compareFramesEnd, true); var lengthDiff = (baseVideoDurationTimeSpan.Value.TotalMicroseconds - compareVideoDurationTimeSpan.Value.TotalMicroseconds) / 1000000; - + endOffset += lengthDiff; - + Console.WriteLine($"Start offset: {startOffset} seconds"); Console.WriteLine($"End offset: {endOffset} seconds"); CleanupDirectory(cleanupDir); - + baseFramesStart.Clear(); baseFramesEnd.Clear(); compareFramesStart.Clear(); @@ -378,7 +378,7 @@ public class Merger{ private static double GetTimeFromFileName(string fileName, double frameRate){ var match = Regex.Match(Path.GetFileName(fileName), @"frame(\d+)"); if (match.Success){ - return int.Parse(match.Groups[1].Value) / frameRate; + return int.Parse(match.Groups[1].Value) / frameRate; } return 0; @@ -452,6 +452,8 @@ public class ParsedFont{ } public class CrunchyMuxOptions{ + public List DubLangList{ get; set; } = new List(); + public List SubLangList{ get; set; } = new List(); public string Output{ get; set; } public bool? SkipSubMux{ get; set; } public bool? KeepAllVideos{ get; set; } @@ -468,9 +470,17 @@ public class CrunchyMuxOptions{ public string CcTag{ get; set; } public bool SyncTiming{ get; set; } public bool DlVideoOnce{ get; set; } + + public bool DefaultSubSigns{ get; set; } + + public bool DefaultSubForcedDisplay{ get; set; } + public bool CcSubsMuxingFlag{ get; set; } + public bool SignsSubsAsForced{ get; set; } } public class MergerOptions{ + public List DubLangList{ get; set; } = new List(); + public List SubLangList{ get; set; } = new List(); public List OnlyVid{ get; set; } = new List(); public List OnlyAudio{ get; set; } = new List(); public List Subtitles{ get; set; } = new List(); @@ -484,6 +494,10 @@ public class MergerOptions{ public MuxOptions Options{ get; set; } public Defaults Defaults{ get; set; } public bool mp3{ get; set; } + public bool DefaultSubSigns{ get; set; } + public bool DefaultSubForcedDisplay{ get; set; } + public bool CcSubsMuxingFlag{ get; set; } + public bool SignsSubsAsForced{ get; set; } public List Description{ get; set; } = new List(); } diff --git a/CRD/Utils/Structs/Crunchyroll/CrDownloadOptions.cs b/CRD/Utils/Structs/Crunchyroll/CrDownloadOptions.cs index 7d77cbc..9a1bdbd 100644 --- a/CRD/Utils/Structs/Crunchyroll/CrDownloadOptions.cs +++ b/CRD/Utils/Structs/Crunchyroll/CrDownloadOptions.cs @@ -46,7 +46,7 @@ public class CrDownloadOptions{ public List Override{ get; set; } =[]; [JsonIgnore] - public string CcTag{ get; set; } = ""; + public string CcTag{ get; set; } = "CC"; [JsonIgnore] public bool Nocleanup{ get; set; } @@ -115,6 +115,12 @@ public class CrDownloadOptions{ #region Crunchyroll Settings + [JsonProperty("cr_mark_as_watched")] + public bool MarkAsWatched{ get; set; } + + [JsonProperty("cr_beta_api")] + public bool UseCrBetaApi{ get; set; } + [JsonProperty("hard_sub_lang")] public string Hslang{ get; set; } = ""; @@ -208,6 +214,9 @@ public class CrDownloadOptions{ [JsonProperty("keep_dubs_seperate")] public bool KeepDubsSeperate{ get; set; } + [JsonProperty("dl_first_available_dub")] + public bool DownloadFirstAvailableDub{ get; set; } + [JsonProperty("mux_skip_muxing")] public bool SkipMuxing{ get; set; } @@ -364,6 +373,9 @@ public class CrDownloadOptionsYaml{ #region Crunchyroll Settings + [YamlIgnore] + public bool UseCrBetaApi{ get; set; } + [YamlMember(Alias = "hard_sub_lang", ApplyNamingConventions = false)] public string Hslang{ get; set; } = ""; diff --git a/CRD/Utils/Structs/Crunchyroll/Episode/EpisodeStructs.cs b/CRD/Utils/Structs/Crunchyroll/Episode/EpisodeStructs.cs index 7f6f4a1..541e2e2 100644 --- a/CRD/Utils/Structs/Crunchyroll/Episode/EpisodeStructs.cs +++ b/CRD/Utils/Structs/Crunchyroll/Episode/EpisodeStructs.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; +using CRD.Utils.Structs.Crunchyroll; using CRD.Utils.Structs.History; using Newtonsoft.Json; @@ -378,6 +379,9 @@ public class CrunchyEpMeta{ public List downloadedFiles{ get; set; } =[]; public bool OnlySubs{ get; set; } + + public CrDownloadOptions? DownloadSettings; + } public class DownloadProgress{ diff --git a/CRD/Utils/Updater/Updater.cs b/CRD/Utils/Updater/Updater.cs index 6209dec..98c8b21 100644 --- a/CRD/Utils/Updater/Updater.cs +++ b/CRD/Utils/Updater/Updater.cs @@ -15,6 +15,7 @@ namespace CRD.Utils.Updater; public class Updater : INotifyPropertyChanged{ public double progress = 0; + public bool failed = false; #region Singelton @@ -50,8 +51,8 @@ public class Updater : INotifyPropertyChanged{ private readonly string apiEndpoint = "https://api.github.com/repos/Crunchy-DL/Crunchy-Downloader/releases/latest"; public async Task CheckForUpdatesAsync(){ - if (Directory.Exists(tempPath)){ - Directory.Delete(tempPath, true); + if (File.Exists(tempPath)){ + File.Delete(tempPath); } if (Directory.Exists(extractPath)){ @@ -123,6 +124,7 @@ public class Updater : INotifyPropertyChanged{ public async Task DownloadAndUpdateAsync(){ try{ + failed = false; Helpers.EnsureDirectoriesExist(tempPath); // Download the zip file @@ -164,19 +166,54 @@ public class Updater : INotifyPropertyChanged{ ApplyUpdate(extractPath); } else{ Console.Error.WriteLine("Failed to get Update"); + failed = true; + OnPropertyChanged(nameof(failed)); } } catch (Exception e){ Console.Error.WriteLine($"Failed to get Update: {e.Message}"); + failed = true; + OnPropertyChanged(nameof(failed)); } } private void ApplyUpdate(string updateFolder){ - var ExecutableExtension = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : string.Empty; - var currentPath = AppDomain.CurrentDomain.BaseDirectory; - var updaterPath = Path.Combine(currentPath, "Updater" + ExecutableExtension); - var arguments = $"\"{currentPath.Substring(0, currentPath.Length - 1)}\" \"{updateFolder}\""; + var executableExtension = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : string.Empty; + var currentPath = Path.GetFullPath(AppContext.BaseDirectory); + var updaterPath = Path.Combine(currentPath, "Updater" + executableExtension); - System.Diagnostics.Process.Start(updaterPath, arguments); - Environment.Exit(0); + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)){ + try{ + var chmodProcess = new System.Diagnostics.ProcessStartInfo{ + FileName = "/bin/bash", + Arguments = $"-c \"chmod +x '{updaterPath}'\"", + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + System.Diagnostics.Process.Start(chmodProcess)?.WaitForExit(); + } catch (Exception ex){ + Console.Error.WriteLine($"Error setting execute permissions: {ex.Message}"); + failed = true; + OnPropertyChanged(nameof(failed)); + return; + } + } + + try{ + var startInfo = new System.Diagnostics.ProcessStartInfo{ + FileName = updaterPath, + UseShellExecute = false + }; + startInfo.ArgumentList.Add(currentPath); + startInfo.ArgumentList.Add(updateFolder); + + System.Diagnostics.Process.Start(startInfo); + Environment.Exit(0); + } catch (Exception ex){ + Console.Error.WriteLine($"Error launching updater: {ex.Message}"); + failed = true; + OnPropertyChanged(nameof(failed)); + } } } \ No newline at end of file diff --git a/CRD/ViewModels/DownloadsPageViewModel.cs b/CRD/ViewModels/DownloadsPageViewModel.cs index 2523f90..bb1d814 100644 --- a/CRD/ViewModels/DownloadsPageViewModel.cs +++ b/CRD/ViewModels/DownloadsPageViewModel.cs @@ -45,6 +45,26 @@ public partial class DownloadsPageViewModel : ViewModelBase{ CrunchyrollManager.Instance.CrunOptions.RemoveFinishedDownload = value; CfgManager.WriteCrSettings(); } + + [RelayCommand] + public void ClearQueue(){ + var items = QueueManager.Instance.Queue; + QueueManager.Instance.Queue.Clear(); + + foreach (var crunchyEpMeta in items){ + if (!crunchyEpMeta.DownloadProgress.Done){ + foreach (var downloadItemDownloadedFile in crunchyEpMeta.downloadedFiles){ + try{ + if (File.Exists(downloadItemDownloadedFile)){ + File.Delete(downloadItemDownloadedFile); + } + } catch (Exception){ + // ignored + } + } + } + } + } } public partial class DownloadItemModel : INotifyPropertyChanged{ @@ -210,7 +230,7 @@ public partial class DownloadItemModel : INotifyPropertyChanged{ newOptions.Noaudio = true; } - await CrunchyrollManager.Instance.DownloadEpisode(epMeta, newOptions); + await CrunchyrollManager.Instance.DownloadEpisode(epMeta, epMeta.DownloadSettings ?? newOptions); } } diff --git a/CRD/ViewModels/Utils/ContentDialogUpdateViewModel.cs b/CRD/ViewModels/Utils/ContentDialogUpdateViewModel.cs index 6c59cb1..55bd78b 100644 --- a/CRD/ViewModels/Utils/ContentDialogUpdateViewModel.cs +++ b/CRD/ViewModels/Utils/ContentDialogUpdateViewModel.cs @@ -12,6 +12,8 @@ public partial class ContentDialogUpdateViewModel : ViewModelBase{ [ObservableProperty] private double _progress; + [ObservableProperty] + private bool _failed; private AccountPageViewModel accountPageViewModel; @@ -28,7 +30,11 @@ public partial class ContentDialogUpdateViewModel : ViewModelBase{ private void Progress_PropertyChanged(object? sender, PropertyChangedEventArgs e){ if (e.PropertyName == nameof(Updater.Instance.progress)){ Progress = Updater.Instance.progress; + }else if (e.PropertyName == nameof(Updater.Instance.failed)){ + Failed = Updater.Instance.failed; + dialog.IsPrimaryButtonEnabled = !Failed; } + } diff --git a/CRD/Views/DownloadsPageView.axaml b/CRD/Views/DownloadsPageView.axaml index 3e01c98..01d7b05 100644 --- a/CRD/Views/DownloadsPageView.axaml +++ b/CRD/Views/DownloadsPageView.axaml @@ -20,9 +20,20 @@ - + + + diff --git a/CRD/Views/MainWindow.axaml.cs b/CRD/Views/MainWindow.axaml.cs index e05ddab..3f2f38d 100644 --- a/CRD/Views/MainWindow.axaml.cs +++ b/CRD/Views/MainWindow.axaml.cs @@ -178,9 +178,11 @@ public partial class MainWindow : AppWindow{ public async void ShowUpdateDialog(){ var dialog = new ContentDialog(){ Title = "Updating", - // CloseButtonText = "Close" + PrimaryButtonText = "Close" }; + dialog.IsPrimaryButtonEnabled = false; + var viewModel = new ContentDialogUpdateViewModel(dialog); dialog.Content = new ContentDialogUpdateView(){ DataContext = viewModel diff --git a/CRD/Views/Utils/ContentDialogUpdateView.axaml b/CRD/Views/Utils/ContentDialogUpdateView.axaml index d3295ae..e317023 100644 --- a/CRD/Views/Utils/ContentDialogUpdateView.axaml +++ b/CRD/Views/Utils/ContentDialogUpdateView.axaml @@ -9,7 +9,8 @@ x:Class="CRD.Views.Utils.ContentDialogUpdateView"> - + + \ No newline at end of file