diff --git a/CRD/Downloader/Crunchyroll/CrunchyrollManager.cs b/CRD/Downloader/Crunchyroll/CrunchyrollManager.cs index 0080d72..5c96626 100644 --- a/CRD/Downloader/Crunchyroll/CrunchyrollManager.cs +++ b/CRD/Downloader/Crunchyroll/CrunchyrollManager.cs @@ -197,8 +197,8 @@ public class CrunchyrollManager{ CrunOptions.StreamEndpoint = "tv/android_tv"; CrAuthEndpoint1.AuthSettings = new CrAuthSettings(){ Endpoint = "tv/android_tv", - Authorization = "Basic Ym1icmt4eXgzZDd1NmpzZnlsYTQ6QUlONEQ1VkVfY3Awd1Z6Zk5vUDBZcUhVcllGcDloU2c=", - UserAgent = "ANDROIDTV/3.42.1_22267 Android/16", + Authorization = "Basic Y2I5bnpybWh0MzJ2Z3RleHlna286S1V3bU1qSlh4eHVyc0hJVGQxenZsMkMyeVFhUW84TjQ=", + UserAgent = "ANDROIDTV/3.42.1_22273 Android/16", Device_name = "Android TV", Device_type = "Android TV" }; @@ -213,14 +213,14 @@ public class CrunchyrollManager{ Device_type = "OnePlus CPH2449" }; } - + CrAuthEndpoint2.AuthSettings = CrunOptions.StreamEndpointSecondSettings; await CrAuthEndpoint1.Auth(); if (!string.IsNullOrEmpty(CrAuthEndpoint2.AuthSettings.Endpoint)){ await CrAuthEndpoint2.Auth(); } - + CfgManager.WriteCrSettings(); // var token = await GetBase64EncodedTokenAsync(); @@ -281,17 +281,11 @@ public class CrunchyrollManager{ await SonarrClient.Instance.RefreshSonarr(); } - - //Fix hslang - can be removed in a future version - var lang = Languages.Locale2language(CrunOptions.Hslang); - if (lang != Languages.DEFAULT_lang){ - CrunOptions.Hslang = lang.CrLocale; - } } public async Task DownloadEpisode(CrunchyEpMeta data, CrDownloadOptions options){ - QueueManager.Instance.ActiveDownloads++; + QueueManager.Instance.IncrementDownloads(); data.DownloadProgress = new DownloadProgress(){ IsDownloading = true, @@ -305,7 +299,7 @@ public class CrunchyrollManager{ var res = await DownloadMediaList(data, options); if (res.Error){ - QueueManager.Instance.ActiveDownloads--; + QueueManager.Instance.DecrementDownloads(); data.DownloadProgress = new DownloadProgress(){ IsDownloading = false, Error = true, @@ -318,6 +312,10 @@ public class CrunchyrollManager{ return false; } + if (options.DownloadAllowEarlyStart){ + QueueManager.Instance.DecrementDownloads(); + } + if (options.SkipMuxing == false){ bool syncError = false; bool muxError = false; @@ -511,7 +509,10 @@ public class CrunchyrollManager{ } - QueueManager.Instance.ActiveDownloads--; + if (!options.DownloadAllowEarlyStart){ + QueueManager.Instance.IncrementDownloads(); + } + QueueManager.Instance.Queue.Refresh(); if (options.History && data.Data is{ Count: > 0 } && (options.HistoryIncludeCrArtists && data.Music || !data.Music)){ @@ -523,7 +524,7 @@ public class CrunchyrollManager{ _ = CrEpisode.MarkAsWatched(data.Data.First().MediaId); } - if (QueueManager.Instance.Queue.Count == 0){ + if (QueueManager.Instance.Queue.Count == 0 || QueueManager.Instance.Queue.All(e => e.DownloadProgress.Done)){ try{ var audioPath = CrunOptions.DownloadFinishedSoundPath; if (!string.IsNullOrEmpty(audioPath)){ @@ -1192,7 +1193,7 @@ public class CrunchyrollManager{ Console.WriteLine("Downloading video..."); curStream = streams[options.Kstream - 1]; - Console.WriteLine($"Playlists URL: {string.Join(", ", curStream.Url)} ({curStream.Type})"); + Console.WriteLine($"Playlists URL: {string.Join(", ", curStream.Url.Select(u => u.Url))} ({curStream.Type})"); } string tsFile = ""; @@ -1321,8 +1322,8 @@ public class CrunchyrollManager{ // Audio: Remove duplicates, then sort by bandwidth audios = audios - .GroupBy(a => new{ a.bandwidth, a.language }) // Add more properties if needed - .Select(g => g.First()) + .GroupBy(a => new{ a.bandwidth, a.language }) + .Select(g => g.OrderByDescending(x => x.audioSamplingRate).First()) .OrderBy(a => a.bandwidth) .ThenBy(a => a.audioSamplingRate) .ToList(); @@ -1385,20 +1386,22 @@ public class CrunchyrollManager{ foreach (var server in streamServers){ Console.WriteLine($"\t{server}"); } - - Console.WriteLine("Available Video Qualities:"); + + var sb = new StringBuilder(); + sb.AppendLine("Available Video Qualities:"); for (int i = 0; i < videos.Count; i++){ - Console.WriteLine($"\t[{i + 1}] {videos[i].resolutionText}"); + sb.AppendLine($"\t- {videos[i].resolutionText}"); } - Console.WriteLine("Available Audio Qualities:"); + sb.AppendLine("Available Audio Qualities:"); for (int i = 0; i < audios.Count; i++){ - Console.WriteLine($"\t[{i + 1}] {audios[i].resolutionText} / {audios[i].audioSamplingRate}"); + sb.AppendLine($"\t- {audios[i].resolutionText} / {audios[i].audioSamplingRate}"); } variables.Add(new Variable("height", chosenVideoSegments.quality.height, false)); variables.Add(new Variable("width", chosenVideoSegments.quality.width, false)); if (string.IsNullOrEmpty(data.Resolution)) data.Resolution = chosenVideoSegments.quality.height + "p"; + LanguageItem? lang = Languages.languages.FirstOrDefault(a => a.CrLocale == curStream.AudioLang.CrLocale); if (lang == null){ @@ -1412,11 +1415,18 @@ public class CrunchyrollManager{ }; } - Console.WriteLine($"Selected quality:"); - Console.WriteLine($"\tVideo: {chosenVideoSegments.resolutionText}"); - Console.WriteLine($"\tAudio: {chosenAudioSegments.resolutionText} / {chosenAudioSegments.audioSamplingRate}"); - Console.WriteLine($"\tServer: {selectedServer}"); + sb.AppendLine($"Selected quality:"); + sb.AppendLine($"\tVideo: {chosenVideoSegments.resolutionText}"); + sb.AppendLine($"\tAudio: {chosenAudioSegments.resolutionText} / {chosenAudioSegments.audioSamplingRate}"); + sb.AppendLine($"\tServer: {selectedServer}"); + + string qualityConsoleLog = sb.ToString(); + Console.WriteLine(qualityConsoleLog); + data.AvailableQualities = qualityConsoleLog; + Console.WriteLine("Stream URL:" + chosenVideoSegments.segments[0].uri.Split(new[]{ ",.urlset" }, StringSplitOptions.None)[0]); + + fileName = Path.Combine(FileNameManager.ParseFileName(options.FileName, variables, options.Numbers, options.FileNameWhitespaceSubstitute, options.Override).ToArray()); @@ -2302,7 +2312,7 @@ public class CrunchyrollManager{ private async Task<(bool IsOk, string ResponseContent, string error)> SendPlaybackRequestAsync(string endpoint, CrAuth authEndpoint){ var request = HttpClientReq.CreateRequestMessage(endpoint, HttpMethod.Get, true, authEndpoint.Token?.access_token, null); request.Headers.UserAgent.ParseAdd(authEndpoint.AuthSettings.UserAgent); - return await HttpClientReq.Instance.SendHttpRequest(request,false,authEndpoint.cookieStore); + return await HttpClientReq.Instance.SendHttpRequest(request, false, authEndpoint.cookieStore); } private async Task<(bool IsOk, string ResponseContent, string error)> HandleStreamErrorsAsync((bool IsOk, string ResponseContent, string error) response, string endpoint, CrAuth authEndpoint){ diff --git a/CRD/Downloader/Crunchyroll/ViewModels/CrunchyrollSettingsViewModel.cs b/CRD/Downloader/Crunchyroll/ViewModels/CrunchyrollSettingsViewModel.cs index 42eace0..adef593 100644 --- a/CRD/Downloader/Crunchyroll/ViewModels/CrunchyrollSettingsViewModel.cs +++ b/CRD/Downloader/Crunchyroll/ViewModels/CrunchyrollSettingsViewModel.cs @@ -691,9 +691,7 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{ _ = await dialog.ShowAsync(); - if (CrunchyrollManager.Instance.CrAuthEndpoint2.Profile.Username == "???"){ - EndpointNotSignedWarning = true; - } + EndpointNotSignedWarning = CrunchyrollManager.Instance.CrAuthEndpoint2.Profile.Username == "???"; } diff --git a/CRD/Downloader/QueueManager.cs b/CRD/Downloader/QueueManager.cs index 4fe488a..41032ac 100644 --- a/CRD/Downloader/QueueManager.cs +++ b/CRD/Downloader/QueueManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; +using System.Threading; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using CRD.Downloader.Crunchyroll; @@ -21,7 +22,9 @@ public partial class QueueManager : ObservableObject{ public RefreshableObservableCollection Queue = new RefreshableObservableCollection(); public ObservableCollection DownloadItemModels = new ObservableCollection(); - public int ActiveDownloads; + private int activeDownloads; + + public int ActiveDownloads => Volatile.Read(ref activeDownloads); #endregion @@ -53,6 +56,20 @@ public partial class QueueManager : ObservableObject{ Queue.CollectionChanged += UpdateItemListOnRemove; } + public void IncrementDownloads(){ + Interlocked.Increment(ref activeDownloads); + } + + public void DecrementDownloads(){ + while (true){ + int current = Volatile.Read(ref activeDownloads); + if (current == 0) return; + + if (Interlocked.CompareExchange(ref activeDownloads, current - 1, current) == current) + return; + } + } + private void UpdateItemListOnRemove(object? sender, NotifyCollectionChangedEventArgs e){ if (e.Action == NotifyCollectionChangedAction.Remove){ diff --git a/CRD/Utils/Http/HttpClientReq.cs b/CRD/Utils/Http/HttpClientReq.cs index 065368d..3d58557 100644 --- a/CRD/Utils/Http/HttpClientReq.cs +++ b/CRD/Utils/Http/HttpClientReq.cs @@ -70,8 +70,6 @@ public class HttpClientReq{ client = new HttpClient(CreateHttpClientHandler()); } - client.Timeout = TimeSpan.FromSeconds(100); - // client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0"); client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36"); // client.DefaultRequestHeaders.UserAgent.ParseAdd("Crunchyroll/1.9.0 Nintendo Switch/18.1.0.0 UE4/4.27"); diff --git a/CRD/Utils/Structs/Crunchyroll/CrDownloadOptions.cs b/CRD/Utils/Structs/Crunchyroll/CrDownloadOptions.cs index 5c7ec83..7285306 100644 --- a/CRD/Utils/Structs/Crunchyroll/CrDownloadOptions.cs +++ b/CRD/Utils/Structs/Crunchyroll/CrDownloadOptions.cs @@ -31,6 +31,9 @@ public class CrDownloadOptions{ [JsonProperty("download_methode_new")] public bool DownloadMethodeNew{ get; set; } + + [JsonProperty("download_allow_early_start")] + public bool DownloadAllowEarlyStart{ get; set; } [JsonProperty("simultaneous_downloads")] public int SimultaneousDownloads{ get; set; } diff --git a/CRD/Utils/Structs/Crunchyroll/Episode/EpisodeStructs.cs b/CRD/Utils/Structs/Crunchyroll/Episode/EpisodeStructs.cs index 1d0eaa8..7de04c1 100644 --- a/CRD/Utils/Structs/Crunchyroll/Episode/EpisodeStructs.cs +++ b/CRD/Utils/Structs/Crunchyroll/Episode/EpisodeStructs.cs @@ -384,6 +384,8 @@ public class CrunchyEpMeta{ public bool Music{ get; set; } public string Resolution{ get; set; } + + public string AvailableQualities{ get; set; } public List downloadedFiles{ get; set; } =[]; diff --git a/CRD/ViewModels/DownloadsPageViewModel.cs b/CRD/ViewModels/DownloadsPageViewModel.cs index 23a3024..a369a26 100644 --- a/CRD/ViewModels/DownloadsPageViewModel.cs +++ b/CRD/ViewModels/DownloadsPageViewModel.cs @@ -109,6 +109,7 @@ public partial class DownloadItemModel : INotifyPropertyChanged{ public string DoingWhat{ get; set; } public string DownloadSpeed{ get; set; } public string InfoText{ get; set; } + public string InfoTextHover{ get; set; } public CrunchyEpMeta epMeta{ get; set; } @@ -137,6 +138,7 @@ public partial class DownloadItemModel : INotifyPropertyChanged{ GetSubtitleString(), epMeta.Resolution ); + InfoTextHover = epMeta.AvailableQualities; Error = epMeta.DownloadProgress.Error; } @@ -202,7 +204,7 @@ public partial class DownloadItemModel : INotifyPropertyChanged{ GetSubtitleString(), epMeta.Resolution ); - + InfoTextHover = epMeta.AvailableQualities; Error = epMeta.DownloadProgress.Error; @@ -214,6 +216,7 @@ public partial class DownloadItemModel : INotifyPropertyChanged{ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(DoingWhat))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Error))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(InfoText))); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(InfoTextHover))); } } diff --git a/CRD/ViewModels/Utils/GeneralSettingsViewModel.cs b/CRD/ViewModels/Utils/GeneralSettingsViewModel.cs index 7895053..d6fad20 100644 --- a/CRD/ViewModels/Utils/GeneralSettingsViewModel.cs +++ b/CRD/ViewModels/Utils/GeneralSettingsViewModel.cs @@ -56,6 +56,9 @@ public partial class GeneralSettingsViewModel : ViewModelBase{ [ObservableProperty] private bool _downloadMethodeNew; + + [ObservableProperty] + private bool _downloadAllowEarlyStart; [ObservableProperty] private double? _downloadSpeed; @@ -272,6 +275,7 @@ public partial class GeneralSettingsViewModel : ViewModelBase{ HistoryCountSonarr = options.HistoryCountSonarr; DownloadSpeed = options.DownloadSpeedLimit; DownloadMethodeNew = options.DownloadMethodeNew; + DownloadAllowEarlyStart = options.DownloadAllowEarlyStart; RetryAttempts = Math.Clamp((options.RetryAttempts), 1, 10); RetryDelay = Math.Clamp((options.RetryDelay), 1, 30); DownloadToTempFolder = options.DownloadToTempFolder; @@ -300,6 +304,7 @@ public partial class GeneralSettingsViewModel : ViewModelBase{ settings.DownloadFinishedPlaySound = DownloadFinishedPlaySound; settings.DownloadMethodeNew = DownloadMethodeNew; + settings.DownloadAllowEarlyStart = DownloadAllowEarlyStart; settings.BackgroundImageBlurRadius = Math.Clamp((BackgroundImageBlurRadius ?? 0), 0, 40); settings.BackgroundImageOpacity = Math.Clamp((BackgroundImageOpacity ?? 0), 0, 1); diff --git a/CRD/Views/DownloadsPageView.axaml b/CRD/Views/DownloadsPageView.axaml index 7d8eb97..2c22586 100644 --- a/CRD/Views/DownloadsPageView.axaml +++ b/CRD/Views/DownloadsPageView.axaml @@ -1,4 +1,4 @@ - + TextWrapping="Wrap" VerticalAlignment="Center"> + + + +