mirror of
https://github.com/Crunchy-DL/Crunchy-Downloader.git
synced 2026-01-11 20:10:26 +00:00
Add - Added path reset buttons to Temp Folder Path, Download Folder, and Background Image settings
Add - Added background image option to the Appearance settings Add - Background Image Settings - Added new options to control opacity and blur radius Add - Added "Couldn't sync dubs" status if the syncing failed Add - Added functionality to combine multiple episodes from the same season into a single entry in the calendar Add - Added video resolution display next to dubs/subs in the downloads tab Add - Added Cloudflare check to image loading Add - Added hardsub selection if the current is not available Add - Added part size setting to configure the size of parts downloaded at the same time Add - Added quality override to history series Add - Added history marker to search results to indicate if a series is already in the user's history Add - Added seasons tab for seasonal releases (Spring, Summer, Fall, Winter) Add - Added potential releases and release times for the current day and the next week to the custom calendar Chg - Changed Calendar cards background color for improved visibility Chg - Combined Appearance settings into a single section in the settings tab Chg - Consolidated Debug settings into one settings expander for better organization Chg - Changed time sync to now check both the start and end of the video Chg - Changed encoding progress to be displayed by the progress bar Chg - Updated the functionality for hiding dubs in the custom calendar Chg - Adjusted Dub sync to improve accuracy, resolving issues where it failed for more episodes than expected Chg - Subtitles and dubs are now sorted according to the order selected in the MKV file Chg - Changed logout behavior to correctly log out if login fails when starting the downloader Chg - Changed that all downloaded files are removed if an in-progress download is removed from the queue Chg - Changed default profile image Chg - Updated used packages to the newest version Chg - Separated settings to separate tabs Fix - Fixed some series didn't get added to the history Fix - Fixed an issue with file path length that prevented some files from being accessed properly Fix - Fixed an issue where file names exceeded the maximum allowable length, causing errors Fix - Fixed an issue where refreshing a series could get stuck Fix - Fixed a crash that could happen with the syncing Fix - Fixed an issue where the download status showed "Done" while moving files from the temp folder Fix - Fixed an issue where cookies were not being utilized correctly Fix - Resolved issues with displaying dates in UTC format Fix - Fixed an issue with incorrect calendar grouping Fix - Fixed an issue with the previous week navigation in the calendar Fix - Fixed an issue where the calendar would not display correctly when not logged in Fix - Fixed incorrect FFmpeg check for other OS (Linux/macOS) Fix - Fixed an issue where image loading used a different HTTP client
This commit is contained in:
parent
54224afeba
commit
95cd06a523
93 changed files with 5221 additions and 2657 deletions
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
|
@ -7,8 +6,6 @@ using CRD.ViewModels;
|
|||
using MainWindow = CRD.Views.MainWindow;
|
||||
using System.Linq;
|
||||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils.Updater;
|
||||
|
||||
namespace CRD;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,16 @@ using System.Globalization;
|
|||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.History;
|
||||
using DynamicData;
|
||||
using HtmlAgilityPack;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CRD.Downloader;
|
||||
|
||||
|
|
@ -87,7 +92,7 @@ public class CalendarManager{
|
|||
var date = day.SelectSingleNode(".//time[@datetime]")?.GetAttributeValue("datetime", "No date");
|
||||
DateTime dayDateTime = DateTime.Parse(date, null, DateTimeStyles.RoundtripKind);
|
||||
|
||||
if (week.FirstDayOfWeek == null){
|
||||
if (week.FirstDayOfWeek == DateTime.MinValue){
|
||||
week.FirstDayOfWeek = dayDateTime;
|
||||
week.FirstDayOfWeekString = dayDateTime.ToString("yyyy-MM-dd");
|
||||
}
|
||||
|
|
@ -147,8 +152,10 @@ public class CalendarManager{
|
|||
}
|
||||
|
||||
|
||||
public async Task<CalendarWeek> BuildCustomCalendar(bool forceUpdate){
|
||||
if (!forceUpdate && calendar.TryGetValue("C" + DateTime.Now.ToString("yyyy-MM-dd"), out var forDate)){
|
||||
public async Task<CalendarWeek> BuildCustomCalendar(DateTime calTargetDate, bool forceUpdate){
|
||||
await LoadAnilistUpcoming();
|
||||
|
||||
if (!forceUpdate && calendar.TryGetValue("C" + calTargetDate.ToString("yyyy-MM-dd"), out var forDate)){
|
||||
return forDate;
|
||||
}
|
||||
|
||||
|
|
@ -156,14 +163,14 @@ public class CalendarManager{
|
|||
CalendarWeek week = new CalendarWeek();
|
||||
week.CalendarDays = new List<CalendarDay>();
|
||||
|
||||
DateTime today = DateTime.Now;
|
||||
DateTime targetDay = calTargetDate;
|
||||
|
||||
for (int i = 0; i < 7; i++){
|
||||
CalendarDay calDay = new CalendarDay();
|
||||
|
||||
calDay.CalendarEpisodes = new List<CalendarEpisode>();
|
||||
calDay.DateTime = today.AddDays(-i);
|
||||
calDay.DayName = calDay.DateTime.Value.DayOfWeek.ToString();
|
||||
calDay.DateTime = targetDay.AddDays(-i);
|
||||
calDay.DayName = calDay.DateTime.DayOfWeek.ToString();
|
||||
|
||||
week.CalendarDays.Add(calDay);
|
||||
}
|
||||
|
|
@ -171,21 +178,68 @@ public class CalendarManager{
|
|||
week.CalendarDays.Reverse();
|
||||
|
||||
var firstDayOfWeek = week.CalendarDays.First().DateTime;
|
||||
week.FirstDayOfWeek = firstDayOfWeek;
|
||||
|
||||
var newEpisodesBase = await CrunchyrollManager.Instance.CrEpisode.GetNewEpisodes(CrunchyrollManager.Instance.CrunOptions.HistoryLang, 200, firstDayOfWeek, true);
|
||||
var newEpisodesBase = await CrunchyrollManager.Instance.CrEpisode.GetNewEpisodes("", 200, firstDayOfWeek, true);
|
||||
|
||||
if (newEpisodesBase is{ Data.Count: > 0 }){
|
||||
var newEpisodes = newEpisodesBase.Data;
|
||||
|
||||
//EpisodeAirDate
|
||||
foreach (var crBrowseEpisode in newEpisodes){
|
||||
var targetDate = CrunchyrollManager.Instance.CrunOptions.CalendarFilterByAirDate ? crBrowseEpisode.EpisodeMetadata.EpisodeAirDate : crBrowseEpisode.LastPublic;
|
||||
DateTime episodeAirDate = crBrowseEpisode.EpisodeMetadata.EpisodeAirDate.Kind == DateTimeKind.Utc
|
||||
? crBrowseEpisode.EpisodeMetadata.EpisodeAirDate.ToLocalTime()
|
||||
: crBrowseEpisode.EpisodeMetadata.EpisodeAirDate;
|
||||
|
||||
if (CrunchyrollManager.Instance.CrunOptions.CalendarHideDubs && crBrowseEpisode.EpisodeMetadata.SeasonTitle != null &&
|
||||
(crBrowseEpisode.EpisodeMetadata.SeasonTitle.EndsWith("Dub)") || crBrowseEpisode.EpisodeMetadata.AudioLocale != Locale.JaJp)){
|
||||
continue;
|
||||
DateTime premiumAvailableStart = crBrowseEpisode.EpisodeMetadata.PremiumAvailableDate.Kind == DateTimeKind.Utc
|
||||
? crBrowseEpisode.EpisodeMetadata.PremiumAvailableDate.ToLocalTime()
|
||||
: crBrowseEpisode.EpisodeMetadata.PremiumAvailableDate;
|
||||
|
||||
DateTime now = DateTime.Now;
|
||||
DateTime oneYearFromNow = now.AddYears(1);
|
||||
|
||||
DateTime targetDate;
|
||||
|
||||
if (CrunchyrollManager.Instance.CrunOptions.CalendarFilterByAirDate){
|
||||
targetDate = episodeAirDate;
|
||||
|
||||
if (targetDate >= oneYearFromNow){
|
||||
DateTime freeAvailableStart = crBrowseEpisode.EpisodeMetadata.FreeAvailableDate.Kind == DateTimeKind.Utc
|
||||
? crBrowseEpisode.EpisodeMetadata.FreeAvailableDate.ToLocalTime()
|
||||
: crBrowseEpisode.EpisodeMetadata.FreeAvailableDate;
|
||||
|
||||
if (freeAvailableStart <= oneYearFromNow){
|
||||
targetDate = freeAvailableStart;
|
||||
} else{
|
||||
targetDate = premiumAvailableStart;
|
||||
}
|
||||
}
|
||||
} else{
|
||||
targetDate = premiumAvailableStart;
|
||||
|
||||
if (targetDate >= oneYearFromNow){
|
||||
DateTime freeAvailableStart = crBrowseEpisode.EpisodeMetadata.FreeAvailableDate.Kind == DateTimeKind.Utc
|
||||
? crBrowseEpisode.EpisodeMetadata.FreeAvailableDate.ToLocalTime()
|
||||
: crBrowseEpisode.EpisodeMetadata.FreeAvailableDate;
|
||||
|
||||
if (freeAvailableStart <= oneYearFromNow){
|
||||
targetDate = freeAvailableStart;
|
||||
} else{
|
||||
targetDate = episodeAirDate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var dubFilter = CrunchyrollManager.Instance.CrunOptions.CalendarDubFilter;
|
||||
|
||||
if (CrunchyrollManager.Instance.CrunOptions.CalendarHideDubs && crBrowseEpisode.EpisodeMetadata.SeasonTitle != null &&
|
||||
(crBrowseEpisode.EpisodeMetadata.SeasonTitle.EndsWith("Dub)") || crBrowseEpisode.EpisodeMetadata.SeasonTitle.EndsWith("Audio)")) &&
|
||||
(string.IsNullOrEmpty(dubFilter) || dubFilter == "none" || (crBrowseEpisode.EpisodeMetadata.AudioLocale != null && crBrowseEpisode.EpisodeMetadata.AudioLocale.GetEnumMemberValue() != dubFilter))){
|
||||
//|| crBrowseEpisode.EpisodeMetadata.AudioLocale != Locale.JaJp
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (!string.IsNullOrEmpty(dubFilter) && dubFilter != "none"){
|
||||
if (crBrowseEpisode.EpisodeMetadata.AudioLocale != null && crBrowseEpisode.EpisodeMetadata.AudioLocale.GetEnumMemberValue() != dubFilter){
|
||||
continue;
|
||||
|
|
@ -193,7 +247,7 @@ public class CalendarManager{
|
|||
}
|
||||
|
||||
var calendarDay = (from day in week.CalendarDays
|
||||
where day.DateTime.HasValue && day.DateTime.Value.Date == targetDate.Date
|
||||
where day.DateTime != DateTime.MinValue && day.DateTime.Date == targetDate.Date
|
||||
select day).FirstOrDefault();
|
||||
|
||||
if (calendarDay != null){
|
||||
|
|
@ -209,13 +263,62 @@ public class CalendarManager{
|
|||
calEpisode.IsPremiere = crBrowseEpisode.EpisodeMetadata.Episode == "1";
|
||||
calEpisode.SeasonName = crBrowseEpisode.EpisodeMetadata.SeasonTitle;
|
||||
calEpisode.EpisodeNumber = crBrowseEpisode.EpisodeMetadata.Episode;
|
||||
calEpisode.CrSeriesID = crBrowseEpisode.EpisodeMetadata.SeriesId;
|
||||
|
||||
calendarDay.CalendarEpisodes?.Add(calEpisode);
|
||||
var existingEpisode = calendarDay.CalendarEpisodes
|
||||
?.FirstOrDefault(e => e.SeasonName == calEpisode.SeasonName);
|
||||
|
||||
if (existingEpisode != null){
|
||||
if (!int.TryParse(existingEpisode.EpisodeNumber, out var num)){
|
||||
existingEpisode.EpisodeNumber = "...";
|
||||
} else{
|
||||
var existingNumbers = existingEpisode.EpisodeNumber
|
||||
.Split('-')
|
||||
.Select(n => int.TryParse(n, out var num) ? num : 0)
|
||||
.Where(n => n > 0)
|
||||
.ToList();
|
||||
|
||||
if (int.TryParse(calEpisode.EpisodeNumber, out var newEpisodeNumber)){
|
||||
existingNumbers.Add(newEpisodeNumber);
|
||||
}
|
||||
|
||||
existingNumbers.Sort();
|
||||
var lowest = existingNumbers.First();
|
||||
var highest = existingNumbers.Last();
|
||||
|
||||
// Update the existing episode's number to the new range
|
||||
existingEpisode.EpisodeNumber = lowest == highest
|
||||
? lowest.ToString()
|
||||
: $"{lowest}-{highest}";
|
||||
|
||||
if (lowest == 1){
|
||||
existingEpisode.IsPremiere = true;
|
||||
}
|
||||
}
|
||||
|
||||
existingEpisode.CalendarEpisodes.Add(calEpisode);
|
||||
} else{
|
||||
calendarDay.CalendarEpisodes?.Add(calEpisode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
foreach (var calendarDay in week.CalendarDays){
|
||||
if (calendarDay.DateTime.Date >= DateTime.Now.Date){
|
||||
if (ProgramManager.Instance.AnilistUpcoming.ContainsKey(calendarDay.DateTime.ToString("yyyy-MM-dd"))){
|
||||
var list = ProgramManager.Instance.AnilistUpcoming[calendarDay.DateTime.ToString("yyyy-MM-dd")];
|
||||
|
||||
foreach (var calendarEpisode in list.Where(calendarEpisode => calendarDay.DateTime.Date == calendarEpisode.DateTime.Date)
|
||||
.Where(calendarEpisode => calendarDay.CalendarEpisodes.All(ele => ele.CrSeriesID != calendarEpisode.CrSeriesID))){
|
||||
calendarDay.CalendarEpisodes.Add(calendarEpisode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var weekCalendarDay in week.CalendarDays){
|
||||
if (weekCalendarDay.CalendarEpisodes != null)
|
||||
if (weekCalendarDay.CalendarEpisodes.Count > 0)
|
||||
weekCalendarDay.CalendarEpisodes = weekCalendarDay.CalendarEpisodes
|
||||
.OrderBy(e => e.DateTime)
|
||||
.ThenBy(e => e.SeasonName)
|
||||
|
|
@ -232,9 +335,232 @@ public class CalendarManager{
|
|||
if (day.CalendarEpisodes != null) day.CalendarEpisodes = day.CalendarEpisodes.OrderBy(e => e.DateTime).ToList();
|
||||
}
|
||||
|
||||
calendar["C" + DateTime.Now.ToString("yyyy-MM-dd")] = week;
|
||||
calendar["C" + calTargetDate.ToString("yyyy-MM-dd")] = week;
|
||||
|
||||
|
||||
return week;
|
||||
}
|
||||
|
||||
|
||||
private async Task LoadAnilistUpcoming(){
|
||||
DateTime today = DateTime.Today;
|
||||
|
||||
string formattedDate = today.ToString("yyyy-MM-dd");
|
||||
|
||||
if (ProgramManager.Instance.AnilistUpcoming.ContainsKey(formattedDate)){
|
||||
return;
|
||||
}
|
||||
|
||||
DateTimeOffset todayMidnight = DateTimeOffset.Now.Date;
|
||||
|
||||
long todayMidnightUnix = todayMidnight.ToUnixTimeSeconds();
|
||||
long sevenDaysLaterUnix = todayMidnight.AddDays(8).ToUnixTimeSeconds();
|
||||
|
||||
AniListResponseCalendar? aniListResponse = null;
|
||||
|
||||
int currentPage = 1; // Start from page 1
|
||||
bool hasNextPage;
|
||||
|
||||
do{
|
||||
var variables = new{
|
||||
weekStart = todayMidnightUnix,
|
||||
weekEnd = sevenDaysLaterUnix,
|
||||
page = currentPage
|
||||
};
|
||||
|
||||
var payload = new{
|
||||
query,
|
||||
variables
|
||||
};
|
||||
|
||||
string jsonPayload = JsonConvert.SerializeObject(payload, Formatting.Indented);
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, ApiUrls.Anilist){
|
||||
Content = new StringContent(jsonPayload, Encoding.UTF8, "application/json")
|
||||
};
|
||||
|
||||
var response = await HttpClientReq.Instance.SendHttpRequest(request);
|
||||
|
||||
if (!response.IsOk){
|
||||
Console.Error.WriteLine("Anilist Request Failed for upcoming calendar episodes");
|
||||
return;
|
||||
}
|
||||
|
||||
AniListResponseCalendar currentResponse = Helpers.Deserialize<AniListResponseCalendar>(
|
||||
response.ResponseContent, CrunchyrollManager.Instance.SettingsJsonSerializerSettings
|
||||
) ?? new AniListResponseCalendar();
|
||||
|
||||
|
||||
aniListResponse ??= currentResponse;
|
||||
|
||||
if (aniListResponse != currentResponse){
|
||||
aniListResponse.Data?.Page?.AiringSchedules?.AddRange(currentResponse.Data?.Page?.AiringSchedules ??[]);
|
||||
}
|
||||
|
||||
hasNextPage = currentResponse.Data?.Page?.PageInfo?.HasNextPage ?? false;
|
||||
|
||||
currentPage++;
|
||||
} while (hasNextPage && currentPage < 20);
|
||||
|
||||
|
||||
var list = aniListResponse.Data?.Page?.AiringSchedules ??[];
|
||||
|
||||
list = list.Where(ele => ele.Media?.ExternalLinks != null && ele.Media.ExternalLinks.Any(external =>
|
||||
string.Equals(external.Site, "Crunchyroll", StringComparison.OrdinalIgnoreCase))).ToList();
|
||||
|
||||
List<CalendarEpisode> calendarEpisodes =[];
|
||||
|
||||
foreach (var anilistEle in list){
|
||||
var calEp = new CalendarEpisode();
|
||||
|
||||
calEp.DateTime = DateTimeOffset.FromUnixTimeSeconds(anilistEle.AiringAt).UtcDateTime.ToLocalTime();
|
||||
calEp.HasPassed = false;
|
||||
calEp.EpisodeName = anilistEle.Media?.Title.English;
|
||||
calEp.SeriesUrl = $"https://www.crunchyroll.com/{CrunchyrollManager.Instance.CrunOptions.HistoryLang}/series/";
|
||||
calEp.EpisodeUrl = $"https://www.crunchyroll.com/{CrunchyrollManager.Instance.CrunOptions.HistoryLang}/watch/";
|
||||
calEp.ThumbnailUrl = anilistEle.Media?.CoverImage.ExtraLarge ?? ""; //https://www.crunchyroll.com/i/coming_soon_beta_thumb.jpg
|
||||
calEp.IsPremiumOnly = true;
|
||||
calEp.IsPremiere = anilistEle.Episode == 1;
|
||||
calEp.SeasonName = anilistEle.Media?.Title.English;
|
||||
calEp.EpisodeNumber = anilistEle.Episode.ToString();
|
||||
calEp.AnilistEpisode = true;
|
||||
|
||||
var crunchyrollID = "";
|
||||
|
||||
if (anilistEle.Media?.ExternalLinks != null){
|
||||
var url = anilistEle.Media.ExternalLinks.First(external =>
|
||||
string.Equals(external.Site, "Crunchyroll", StringComparison.OrdinalIgnoreCase)).Url;
|
||||
|
||||
string pattern = @"series\/([^\/]+)";
|
||||
|
||||
Match match = Regex.Match(url, pattern);
|
||||
if (match.Success){
|
||||
crunchyrollID = match.Groups[1].Value;
|
||||
|
||||
calEp.CrSeriesID = crunchyrollID;
|
||||
|
||||
if (CrunchyrollManager.Instance.CrunOptions.History){
|
||||
var historySeries = CrunchyrollManager.Instance.HistoryList.FirstOrDefault(item => item.SeriesId == crunchyrollID);
|
||||
|
||||
if (historySeries != null){
|
||||
var oldestRelease = DateTime.MinValue;
|
||||
foreach (var historySeriesSeason in historySeries.Seasons){
|
||||
if (historySeriesSeason.EpisodesList.Any()){
|
||||
var releaseDate = historySeriesSeason.EpisodesList.Last().EpisodeCrPremiumAirDate;
|
||||
|
||||
if (releaseDate.HasValue && oldestRelease < releaseDate.Value){
|
||||
oldestRelease = releaseDate.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (oldestRelease != DateTime.MinValue){
|
||||
calEp.DateTime = new DateTime(
|
||||
calEp.DateTime.Year,
|
||||
calEp.DateTime.Month,
|
||||
calEp.DateTime.Day,
|
||||
oldestRelease.Hour,
|
||||
oldestRelease.Minute,
|
||||
oldestRelease.Second,
|
||||
calEp.DateTime.Kind
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else{
|
||||
crunchyrollID = "";
|
||||
}
|
||||
}
|
||||
|
||||
calendarEpisodes.Add(calEp);
|
||||
}
|
||||
|
||||
foreach (var calendarEpisode in calendarEpisodes){
|
||||
var airDate = calendarEpisode.DateTime.ToString("yyyy-MM-dd");
|
||||
|
||||
if (!ProgramManager.Instance.AnilistUpcoming.TryGetValue(airDate, out var value)){
|
||||
value = new List<CalendarEpisode>();
|
||||
ProgramManager.Instance.AnilistUpcoming[airDate] = value;
|
||||
}
|
||||
|
||||
value.Add(calendarEpisode);
|
||||
}
|
||||
}
|
||||
|
||||
#region Query
|
||||
|
||||
private string query = @"query ($weekStart: Int, $weekEnd: Int, $page: Int) {
|
||||
Page(page: $page) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
total
|
||||
}
|
||||
airingSchedules(
|
||||
airingAt_greater: $weekStart
|
||||
airingAt_lesser: $weekEnd
|
||||
) {
|
||||
id
|
||||
episode
|
||||
airingAt
|
||||
media {
|
||||
id
|
||||
idMal
|
||||
title {
|
||||
romaji
|
||||
native
|
||||
english
|
||||
}
|
||||
startDate {
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
endDate {
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
status
|
||||
season
|
||||
format
|
||||
synonyms
|
||||
episodes
|
||||
description
|
||||
bannerImage
|
||||
isAdult
|
||||
coverImage {
|
||||
extraLarge
|
||||
color
|
||||
}
|
||||
trailer {
|
||||
id
|
||||
site
|
||||
thumbnail
|
||||
}
|
||||
externalLinks {
|
||||
site
|
||||
icon
|
||||
color
|
||||
url
|
||||
}
|
||||
relations {
|
||||
edges {
|
||||
relationType(version: 2)
|
||||
node {
|
||||
id
|
||||
title {
|
||||
romaji
|
||||
native
|
||||
english
|
||||
}
|
||||
siteUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}";
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.Structs;
|
||||
|
|
@ -17,40 +16,51 @@ public class CrAuth{
|
|||
private readonly CrunchyrollManager crunInstance = CrunchyrollManager.Instance;
|
||||
|
||||
public async Task AuthAnonymous(){
|
||||
string uuid = Guid.NewGuid().ToString();
|
||||
|
||||
var formData = new Dictionary<string, string>{
|
||||
{ "grant_type", "client_id" },
|
||||
{ "scope", "offline_access" }
|
||||
{ "scope", "offline_access" },
|
||||
{ "device_id", uuid },
|
||||
{ "device_type", "Chrome on Windows" }
|
||||
};
|
||||
var requestContent = new FormUrlEncodedContent(formData);
|
||||
requestContent.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, Api.BetaAuth){
|
||||
var requestContent = new FormUrlEncodedContent(formData);
|
||||
|
||||
var crunchyAuthHeaders = new Dictionary<string, string>{
|
||||
{ "Authorization", ApiUrls.authBasicSwitch },
|
||||
{ "User-Agent", ApiUrls.ChromeUserAgent }
|
||||
};
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, ApiUrls.BetaAuth){
|
||||
Content = requestContent
|
||||
};
|
||||
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Basic", Api.authBasicSwitch);
|
||||
foreach (var header in crunchyAuthHeaders){
|
||||
request.Headers.Add(header.Key, header.Value);
|
||||
}
|
||||
|
||||
var response = await HttpClientReq.Instance.SendHttpRequest(request);
|
||||
|
||||
if (response.IsOk){
|
||||
JsonTokenToFileAndVariable(response.ResponseContent);
|
||||
JsonTokenToFileAndVariable(response.ResponseContent, uuid);
|
||||
} else{
|
||||
Console.Error.WriteLine("Anonymous login failed");
|
||||
}
|
||||
|
||||
crunInstance.Profile = new CrProfile{
|
||||
Username = "???",
|
||||
Avatar = "003-cr-hime-excited.png",
|
||||
Avatar = "crbrand_avatars_logo_marks_mangagirl_taupe.png",
|
||||
PreferredContentAudioLanguage = "ja-JP",
|
||||
PreferredContentSubtitleLanguage = "de-DE"
|
||||
};
|
||||
}
|
||||
|
||||
private void JsonTokenToFileAndVariable(string content){
|
||||
private void JsonTokenToFileAndVariable(string content, string deviceId){
|
||||
crunInstance.Token = Helpers.Deserialize<CrToken>(content, crunInstance.SettingsJsonSerializerSettings);
|
||||
|
||||
|
||||
if (crunInstance.Token != null && crunInstance.Token.expires_in != null){
|
||||
if (crunInstance.Token is{ expires_in: not null }){
|
||||
crunInstance.Token.device_id = deviceId;
|
||||
crunInstance.Token.expires = DateTime.Now.AddSeconds((double)crunInstance.Token.expires_in);
|
||||
|
||||
CfgManager.WriteTokenToYamlFile(crunInstance.Token, CfgManager.PathCrToken);
|
||||
|
|
@ -58,25 +68,36 @@ public class CrAuth{
|
|||
}
|
||||
|
||||
public async Task Auth(AuthData data){
|
||||
var formData = new Dictionary<string, string>{
|
||||
{ "username", data.Username },
|
||||
{ "password", data.Password },
|
||||
{ "grant_type", "password" },
|
||||
{ "scope", "offline_access" }
|
||||
};
|
||||
var requestContent = new FormUrlEncodedContent(formData);
|
||||
requestContent.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
|
||||
string uuid = Guid.NewGuid().ToString();
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, Api.BetaAuth){
|
||||
var formData = new Dictionary<string, string>{
|
||||
{ "username", data.Username }, // Replace with actual data
|
||||
{ "password", data.Password }, // Replace with actual data
|
||||
{ "grant_type", "password" },
|
||||
{ "scope", "offline_access" },
|
||||
{ "device_id", uuid },
|
||||
{ "device_type", "Chrome on Windows" }
|
||||
};
|
||||
|
||||
var requestContent = new FormUrlEncodedContent(formData);
|
||||
|
||||
var crunchyAuthHeaders = new Dictionary<string, string>{
|
||||
{ "Authorization", ApiUrls.authBasicSwitch },
|
||||
{ "User-Agent", ApiUrls.ChromeUserAgent }
|
||||
};
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, ApiUrls.BetaAuth){
|
||||
Content = requestContent
|
||||
};
|
||||
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Basic", Api.authBasicSwitch);
|
||||
foreach (var header in crunchyAuthHeaders){
|
||||
request.Headers.Add(header.Key, header.Value);
|
||||
}
|
||||
|
||||
var response = await HttpClientReq.Instance.SendHttpRequest(request);
|
||||
|
||||
if (response.IsOk){
|
||||
JsonTokenToFileAndVariable(response.ResponseContent);
|
||||
JsonTokenToFileAndVariable(response.ResponseContent, uuid);
|
||||
} else{
|
||||
if (response.ResponseContent.Contains("invalid_credentials")){
|
||||
MessageBus.Current.SendMessage(new ToastMessage($"Failed to login - because of invalid login credentials", ToastType.Error, 10));
|
||||
|
|
@ -99,7 +120,7 @@ public class CrAuth{
|
|||
return;
|
||||
}
|
||||
|
||||
var request = HttpClientReq.CreateRequestMessage(Api.BetaProfile, HttpMethod.Get, true, true, null);
|
||||
var request = HttpClientReq.CreateRequestMessage(ApiUrls.BetaProfile, HttpMethod.Get, true, true, null);
|
||||
|
||||
var response = await HttpClientReq.Instance.SendHttpRequest(request);
|
||||
|
||||
|
|
@ -109,7 +130,7 @@ public class CrAuth{
|
|||
if (profileTemp != null){
|
||||
crunInstance.Profile = profileTemp;
|
||||
|
||||
var requestSubs = HttpClientReq.CreateRequestMessage(Api.Subscription + crunInstance.Token.account_id, HttpMethod.Get, true, false, null);
|
||||
var requestSubs = HttpClientReq.CreateRequestMessage(ApiUrls.Subscription + crunInstance.Token.account_id, HttpMethod.Get, true, false, null);
|
||||
|
||||
var responseSubs = await HttpClientReq.Instance.SendHttpRequest(requestSubs);
|
||||
|
||||
|
|
@ -152,35 +173,50 @@ public class CrAuth{
|
|||
public async Task LoginWithToken(){
|
||||
if (crunInstance.Token?.refresh_token == null){
|
||||
Console.Error.WriteLine("Missing Refresh Token");
|
||||
await AuthAnonymous();
|
||||
return;
|
||||
}
|
||||
|
||||
string uuid = Guid.NewGuid().ToString();
|
||||
|
||||
var formData = new Dictionary<string, string>{
|
||||
{ "refresh_token", crunInstance.Token.refresh_token },
|
||||
{ "grant_type", "refresh_token" },
|
||||
{ "scope", "offline_access" }
|
||||
{ "scope", "offline_access" },
|
||||
{ "device_id", uuid },
|
||||
{ "device_type", "Chrome on Windows" },
|
||||
{ "grant_type", "refresh_token" }
|
||||
};
|
||||
var requestContent = new FormUrlEncodedContent(formData);
|
||||
requestContent.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, Api.BetaAuth){
|
||||
var requestContent = new FormUrlEncodedContent(formData);
|
||||
|
||||
var crunchyAuthHeaders = new Dictionary<string, string>{
|
||||
{ "Authorization", ApiUrls.authBasicSwitch },
|
||||
{ "User-Agent", ApiUrls.ChromeUserAgent }
|
||||
};
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, ApiUrls.BetaAuth){
|
||||
Content = requestContent
|
||||
};
|
||||
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Basic", Api.authBasicSwitch);
|
||||
foreach (var header in crunchyAuthHeaders){
|
||||
request.Headers.Add(header.Key, header.Value);
|
||||
}
|
||||
|
||||
HttpClientReq.Instance.SetETPCookie(crunInstance.Token.refresh_token);
|
||||
|
||||
var response = await HttpClientReq.Instance.SendHttpRequest(request);
|
||||
|
||||
if (response.IsOk){
|
||||
JsonTokenToFileAndVariable(response.ResponseContent);
|
||||
JsonTokenToFileAndVariable(response.ResponseContent, uuid);
|
||||
|
||||
if (crunInstance.Token?.refresh_token != null){
|
||||
HttpClientReq.Instance.SetETPCookie(crunInstance.Token.refresh_token);
|
||||
|
||||
await GetProfile();
|
||||
}
|
||||
} else{
|
||||
Console.Error.WriteLine("Token Auth Failed");
|
||||
}
|
||||
|
||||
if (crunInstance.Token?.refresh_token != null){
|
||||
HttpClientReq.Instance.SetETPCookie(crunInstance.Token.refresh_token);
|
||||
|
||||
await GetProfile();
|
||||
await AuthAnonymous();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -198,24 +234,37 @@ public class CrAuth{
|
|||
return;
|
||||
}
|
||||
|
||||
var formData = new Dictionary<string, string>{
|
||||
{ "refresh_token", crunInstance.Token?.refresh_token ?? string.Empty },
|
||||
{ "grant_type", "refresh_token" },
|
||||
{ "scope", "offline_access" }
|
||||
};
|
||||
var requestContent = new FormUrlEncodedContent(formData);
|
||||
requestContent.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
|
||||
string uuid = Guid.NewGuid().ToString();
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, Api.BetaAuth){
|
||||
var formData = new Dictionary<string, string>{
|
||||
{ "refresh_token", crunInstance.Token.refresh_token },
|
||||
{ "grant_type", "refresh_token" },
|
||||
{ "scope", "offline_access" },
|
||||
{ "device_id", uuid },
|
||||
{ "device_type", "Chrome on Windows" }
|
||||
};
|
||||
|
||||
var requestContent = new FormUrlEncodedContent(formData);
|
||||
|
||||
var crunchyAuthHeaders = new Dictionary<string, string>{
|
||||
{ "Authorization", ApiUrls.authBasicSwitch },
|
||||
{ "User-Agent", ApiUrls.ChromeUserAgent }
|
||||
};
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, ApiUrls.BetaAuth){
|
||||
Content = requestContent
|
||||
};
|
||||
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Basic", Api.authBasicSwitch);
|
||||
foreach (var header in crunchyAuthHeaders){
|
||||
request.Headers.Add(header.Key, header.Value);
|
||||
}
|
||||
|
||||
HttpClientReq.Instance.SetETPCookie(crunInstance.Token.refresh_token);
|
||||
|
||||
var response = await HttpClientReq.Instance.SendHttpRequest(request);
|
||||
|
||||
if (response.IsOk){
|
||||
JsonTokenToFileAndVariable(response.ResponseContent);
|
||||
JsonTokenToFileAndVariable(response.ResponseContent, uuid);
|
||||
} else{
|
||||
Console.Error.WriteLine("Refresh Token Auth Failed");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ public class CrEpisode(){
|
|||
}
|
||||
|
||||
|
||||
var request = HttpClientReq.CreateRequestMessage($"{Api.Cms}/episodes/{id}", HttpMethod.Get, true, true, query);
|
||||
var request = HttpClientReq.CreateRequestMessage($"{ApiUrls.Cms}/episodes/{id}", HttpMethod.Get, true, true, query);
|
||||
|
||||
var response = await HttpClientReq.Instance.SendHttpRequest(request);
|
||||
|
||||
|
|
@ -181,6 +181,7 @@ public class CrEpisode(){
|
|||
};
|
||||
epMeta.AvailableSubs = item.SubtitleLocales;
|
||||
epMeta.Description = item.Description;
|
||||
epMeta.Hslang = CrunchyrollManager.Instance.CrunOptions.Hslang;
|
||||
|
||||
if (episodeP.EpisodeAndLanguages.Langs.Count > 0){
|
||||
epMeta.SelectedDubs = dubLang
|
||||
|
|
@ -236,7 +237,7 @@ public class CrEpisode(){
|
|||
query["sort_by"] = "newly_added";
|
||||
query["type"] = "episode";
|
||||
|
||||
var request = HttpClientReq.CreateRequestMessage($"{Api.Browse}", HttpMethod.Get, true, false, query);
|
||||
var request = HttpClientReq.CreateRequestMessage($"{ApiUrls.Browse}", HttpMethod.Get, true, false, query);
|
||||
|
||||
var response = await HttpClientReq.Instance.SendHttpRequest(request);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
|||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using CRD.Utils;
|
||||
|
|
@ -26,7 +25,7 @@ public class CrMovies{
|
|||
}
|
||||
|
||||
|
||||
var request = HttpClientReq.CreateRequestMessage($"{Api.Cms}/movies/{id}", HttpMethod.Get, true, true, query);
|
||||
var request = HttpClientReq.CreateRequestMessage($"{ApiUrls.Cms}/movies/{id}", HttpMethod.Get, true, true, query);
|
||||
|
||||
var response = await HttpClientReq.Instance.SendHttpRequest(request);
|
||||
|
||||
|
|
@ -81,6 +80,7 @@ public class CrMovies{
|
|||
};
|
||||
epMeta.AvailableSubs = new List<string>();
|
||||
epMeta.Description = episodeP.Description;
|
||||
epMeta.Hslang = CrunchyrollManager.Instance.CrunOptions.Hslang;
|
||||
|
||||
return epMeta;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ public class CrMusic{
|
|||
}
|
||||
|
||||
public async Task<CrunchyMusicVideoList?> ParseArtistMusicVideosByIdAsync(string id, string crLocale, bool forcedLang = false){
|
||||
var musicVideosTask = FetchMediaListAsync($"{Api.Content}/music/artists/{id}/music_videos", crLocale, forcedLang);
|
||||
var concertsTask = FetchMediaListAsync($"{Api.Content}/music/artists/{id}/concerts", crLocale, forcedLang);
|
||||
var musicVideosTask = FetchMediaListAsync($"{ApiUrls.Content}/music/artists/{id}/music_videos", crLocale, forcedLang);
|
||||
var concertsTask = FetchMediaListAsync($"{ApiUrls.Content}/music/artists/{id}/concerts", crLocale, forcedLang);
|
||||
|
||||
await Task.WhenAll(musicVideosTask, concertsTask);
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ public class CrMusic{
|
|||
}
|
||||
|
||||
private async Task<CrunchyMusicVideo?> ParseMediaByIdAsync(string id, string crLocale, bool forcedLang, string endpoint){
|
||||
var mediaList = await FetchMediaListAsync($"{Api.Content}/{endpoint}/{id}", crLocale, forcedLang);
|
||||
var mediaList = await FetchMediaListAsync($"{ApiUrls.Content}/{endpoint}/{id}", crLocale, forcedLang);
|
||||
|
||||
switch (mediaList.Total){
|
||||
case < 1:
|
||||
|
|
@ -110,6 +110,7 @@ public class CrMusic{
|
|||
epMeta.AvailableSubs = new List<string>();
|
||||
epMeta.Description = episodeP.Description;
|
||||
epMeta.Music = true;
|
||||
epMeta.Hslang = CrunchyrollManager.Instance.CrunOptions.Hslang;
|
||||
|
||||
return epMeta;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ public class CrSeries(){
|
|||
Time = 0,
|
||||
DownloadSpeed = 0
|
||||
};
|
||||
epMeta.Hslang = CrunchyrollManager.Instance.CrunOptions.Hslang;
|
||||
epMeta.Description = item.Description;
|
||||
epMeta.AvailableSubs = item.SubtitleLocales;
|
||||
if (episode.Langs.Count > 0){
|
||||
|
|
@ -308,7 +309,7 @@ public class CrSeries(){
|
|||
}
|
||||
}
|
||||
|
||||
var showRequest = HttpClientReq.CreateRequestMessage($"{Api.Cms}/seasons/{seasonID}", HttpMethod.Get, true, true, query);
|
||||
var showRequest = HttpClientReq.CreateRequestMessage($"{ApiUrls.Cms}/seasons/{seasonID}", HttpMethod.Get, true, true, query);
|
||||
|
||||
var response = await HttpClientReq.Instance.SendHttpRequest(showRequest);
|
||||
|
||||
|
|
@ -329,7 +330,7 @@ public class CrSeries(){
|
|||
}
|
||||
}
|
||||
|
||||
var episodeRequest = HttpClientReq.CreateRequestMessage($"{Api.Cms}/seasons/{seasonID}/episodes", HttpMethod.Get, true, true, query);
|
||||
var episodeRequest = HttpClientReq.CreateRequestMessage($"{ApiUrls.Cms}/seasons/{seasonID}/episodes", HttpMethod.Get, true, true, query);
|
||||
|
||||
var episodeRequestResponse = await HttpClientReq.Instance.SendHttpRequest(episodeRequest);
|
||||
|
||||
|
|
@ -391,7 +392,7 @@ public class CrSeries(){
|
|||
}
|
||||
|
||||
|
||||
var request = HttpClientReq.CreateRequestMessage($"{Api.Cms}/series/{id}/seasons", HttpMethod.Get, true, true, query);
|
||||
var request = HttpClientReq.CreateRequestMessage($"{ApiUrls.Cms}/series/{id}/seasons", HttpMethod.Get, true, true, query);
|
||||
|
||||
var response = await HttpClientReq.Instance.SendHttpRequest(request);
|
||||
|
||||
|
|
@ -422,7 +423,7 @@ public class CrSeries(){
|
|||
}
|
||||
}
|
||||
|
||||
var request = HttpClientReq.CreateRequestMessage($"{Api.Cms}/series/{id}", HttpMethod.Get, true, true, query);
|
||||
var request = HttpClientReq.CreateRequestMessage($"{ApiUrls.Cms}/series/{id}", HttpMethod.Get, true, true, query);
|
||||
|
||||
var response = await HttpClientReq.Instance.SendHttpRequest(request);
|
||||
|
||||
|
|
@ -457,7 +458,7 @@ public class CrSeries(){
|
|||
query["n"] = "6";
|
||||
query["type"] = "top_results";
|
||||
|
||||
var request = HttpClientReq.CreateRequestMessage($"{Api.Search}", HttpMethod.Get, true, false, query);
|
||||
var request = HttpClientReq.CreateRequestMessage($"{ApiUrls.Search}", HttpMethod.Get, true, false, query);
|
||||
|
||||
var response = await HttpClientReq.Instance.SendHttpRequest(request);
|
||||
|
||||
|
|
@ -468,6 +469,20 @@ public class CrSeries(){
|
|||
|
||||
CrSearchSeriesBase? series = Helpers.Deserialize<CrSearchSeriesBase>(response.ResponseContent, crunInstance.SettingsJsonSerializerSettings);
|
||||
|
||||
if (crunInstance.CrunOptions.History){
|
||||
var historyIDs = new HashSet<string>(crunInstance.HistoryList.Select(item => item.SeriesId ?? ""));
|
||||
|
||||
if (series?.Data != null){
|
||||
foreach (var crSearchSeries in series.Data){
|
||||
if (crSearchSeries.Items != null){
|
||||
foreach (var crBrowseSeries in crSearchSeries.Items.Where(crBrowseSeries => historyIDs.Contains(crBrowseSeries.Id ?? "unknownID"))){
|
||||
crBrowseSeries.IsInHistory = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return series;
|
||||
}
|
||||
|
||||
|
|
@ -488,7 +503,7 @@ public class CrSeries(){
|
|||
query["n"] = "50";
|
||||
query["sort_by"] = "alphabetical";
|
||||
|
||||
var request = HttpClientReq.CreateRequestMessage($"{Api.Browse}", HttpMethod.Get, true, false, query);
|
||||
var request = HttpClientReq.CreateRequestMessage($"{ApiUrls.Browse}", HttpMethod.Get, true, false, query);
|
||||
|
||||
var response = await HttpClientReq.Instance.SendHttpRequest(request);
|
||||
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ using System.Globalization;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using Avalonia.Media;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.DRM;
|
||||
using CRD.Utils.Ffmpeg_Encoding;
|
||||
|
|
@ -21,7 +21,10 @@ using CRD.Utils.Sonarr;
|
|||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.Crunchyroll;
|
||||
using CRD.Utils.Structs.History;
|
||||
using CRD.ViewModels.Utils;
|
||||
using CRD.Views;
|
||||
using CRD.Views.Utils;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using LanguageItem = CRD.Utils.Structs.LanguageItem;
|
||||
|
|
@ -116,11 +119,15 @@ public class CrunchyrollManager{
|
|||
options.Theme = "System";
|
||||
options.SelectedCalendarLanguage = "en-us";
|
||||
options.CalendarDubFilter = "none";
|
||||
options.CustomCalendar = true;
|
||||
options.DlVideoOnce = true;
|
||||
options.StreamEndpoint = "web/firefox";
|
||||
options.SubsAddScaledBorder = ScaledBorderAndShadowSelection.ScaledBorderAndShadowYes;
|
||||
options.HistoryLang = DefaultLocale;
|
||||
|
||||
options.BackgroundImageOpacity = 0.5;
|
||||
options.BackgroundImageBlurRadius = 10;
|
||||
|
||||
options.History = true;
|
||||
|
||||
CfgManager.UpdateSettingsFromFile(options);
|
||||
|
|
@ -141,7 +148,7 @@ public class CrunchyrollManager{
|
|||
|
||||
Profile = new CrProfile{
|
||||
Username = "???",
|
||||
Avatar = "003-cr-hime-excited.png",
|
||||
Avatar = "crbrand_avatars_logo_marks_mangagirl_taupe.png",
|
||||
PreferredContentAudioLanguage = "ja-JP",
|
||||
PreferredContentSubtitleLanguage = "de-DE",
|
||||
HasPremium = false,
|
||||
|
|
@ -235,6 +242,8 @@ public class CrunchyrollManager{
|
|||
}
|
||||
|
||||
if (options.SkipMuxing == false){
|
||||
bool syncError = false;
|
||||
|
||||
data.DownloadProgress = new DownloadProgress(){
|
||||
IsDownloading = true,
|
||||
Percent = 100,
|
||||
|
|
@ -275,6 +284,10 @@ public class CrunchyrollManager{
|
|||
if (result is{ merger: not null, isMuxed: true }){
|
||||
mergers.Add(result.merger);
|
||||
}
|
||||
|
||||
if (result.syncError){
|
||||
syncError = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var merger in mergers){
|
||||
|
|
@ -291,7 +304,7 @@ public class CrunchyrollManager{
|
|||
|
||||
QueueManager.Instance.Queue.Refresh();
|
||||
|
||||
await Helpers.RunFFmpegWithPresetAsync(merger?.options.Output, FfmpegEncoding.GetPreset(CrunOptions.EncodingPresetName));
|
||||
await Helpers.RunFFmpegWithPresetAsync(merger?.options.Output, FfmpegEncoding.GetPreset(CrunOptions.EncodingPresetName), data);
|
||||
}
|
||||
|
||||
if (CrunOptions.DownloadToTempFolder){
|
||||
|
|
@ -319,6 +332,8 @@ public class CrunchyrollManager{
|
|||
},
|
||||
fileNameAndPath);
|
||||
|
||||
syncError = result.syncError;
|
||||
|
||||
if (result is{ merger: not null, isMuxed: true }){
|
||||
result.merger.CleanUp();
|
||||
}
|
||||
|
|
@ -334,7 +349,7 @@ public class CrunchyrollManager{
|
|||
|
||||
QueueManager.Instance.Queue.Refresh();
|
||||
|
||||
await Helpers.RunFFmpegWithPresetAsync(result.merger?.options.Output, FfmpegEncoding.GetPreset(CrunOptions.EncodingPresetName));
|
||||
await Helpers.RunFFmpegWithPresetAsync(result.merger?.options.Output, FfmpegEncoding.GetPreset(CrunOptions.EncodingPresetName), data);
|
||||
}
|
||||
|
||||
if (CrunOptions.DownloadToTempFolder){
|
||||
|
|
@ -349,10 +364,10 @@ public class CrunchyrollManager{
|
|||
Percent = 100,
|
||||
Time = 0,
|
||||
DownloadSpeed = 0,
|
||||
Doing = "Done"
|
||||
Doing = "Done" + (syncError ? " - Couldn't sync dubs" : "")
|
||||
};
|
||||
|
||||
if (CrunOptions.RemoveFinishedDownload){
|
||||
if (CrunOptions.RemoveFinishedDownload && !syncError){
|
||||
QueueManager.Instance.Queue.Remove(data);
|
||||
}
|
||||
} else{
|
||||
|
|
@ -402,7 +417,6 @@ public class CrunchyrollManager{
|
|||
|
||||
data.DownloadProgress = new DownloadProgress{
|
||||
IsDownloading = true,
|
||||
Done = true,
|
||||
Percent = 100,
|
||||
Time = 0,
|
||||
DownloadSpeed = 0,
|
||||
|
|
@ -471,7 +485,7 @@ public class CrunchyrollManager{
|
|||
|
||||
#endregion
|
||||
|
||||
private async Task<(Merger? merger, bool isMuxed)> MuxStreams(List<DownloadedMedia> data, CrunchyMuxOptions options, string filename){
|
||||
private async Task<(Merger? merger, bool isMuxed, bool syncError)> MuxStreams(List<DownloadedMedia> data, CrunchyMuxOptions options, string filename){
|
||||
var muxToMp3 = false;
|
||||
|
||||
if (options.Novids == true || data.FindAll(a => a.Type == DownloadMediaType.Video).Count == 0){
|
||||
|
|
@ -480,7 +494,7 @@ public class CrunchyrollManager{
|
|||
muxToMp3 = true;
|
||||
} else{
|
||||
Console.WriteLine("Skip muxing since no videos are downloaded");
|
||||
return (null, false);
|
||||
return (null, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -523,9 +537,9 @@ public class CrunchyrollManager{
|
|||
OnlyAudio = data.Where(a => a.Type == DownloadMediaType.Audio).Select(a => new MergerInput{ Language = a.Lang, Path = a.Path ?? string.Empty }).ToList(),
|
||||
Output = $"{filename}.{(muxToMp3 ? "mp3" : options.Mp4 ? "mp4" : "mkv")}",
|
||||
Subtitles = data.Where(a => a.Type == DownloadMediaType.Subtitle).Select(a => new SubtitleInput
|
||||
{ File = a.Path ?? string.Empty, Language = a.Language, ClosedCaption = a.Cc, Signs = a.Signs, RelatedVideoDownloadMedia = a.RelatedVideoDownloadMedia }).ToList(),
|
||||
{ File = a.Path ?? string.Empty, Language = a.Language, ClosedCaption = a.Cc ?? false, Signs = a.Signs ?? false, RelatedVideoDownloadMedia = a.RelatedVideoDownloadMedia }).ToList(),
|
||||
KeepAllVideos = options.KeepAllVideos,
|
||||
Fonts = FontsManager.Instance.MakeFontsList(CfgManager.PathFONTS_DIR, subsList), // Assuming MakeFontsList is properly defined
|
||||
Fonts = FontsManager.Instance.MakeFontsList(CfgManager.PathFONTS_DIR, subsList),
|
||||
Chapters = data.Where(a => a.Type == DownloadMediaType.Chapters).Select(a => new MergerInput{ Language = a.Lang, Path = a.Path ?? string.Empty }).ToList(),
|
||||
VideoTitle = options.VideoTitle,
|
||||
Options = new MuxOptions(){
|
||||
|
|
@ -549,7 +563,7 @@ public class CrunchyrollManager{
|
|||
Console.Error.WriteLine("MKVmerge not found");
|
||||
}
|
||||
|
||||
bool isMuxed;
|
||||
bool isMuxed, syncError = false;
|
||||
|
||||
if (options.SyncTiming && CrunOptions.DlVideoOnce){
|
||||
var basePath = merger.options.OnlyVid.First().Path;
|
||||
|
|
@ -559,6 +573,12 @@ public class CrunchyrollManager{
|
|||
foreach (var syncVideo in syncVideosList){
|
||||
if (!string.IsNullOrEmpty(syncVideo.Path)){
|
||||
var delay = await merger.ProcessVideo(basePath, syncVideo.Path);
|
||||
|
||||
if (delay <= -100){
|
||||
syncError = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
var audio = merger.options.OnlyAudio.FirstOrDefault(audio => audio.Language.CrLocale == syncVideo.Lang.CrLocale);
|
||||
if (audio != null){
|
||||
audio.Delay = (int)(delay * 1000);
|
||||
|
|
@ -585,21 +605,10 @@ public class CrunchyrollManager{
|
|||
isMuxed = true;
|
||||
}
|
||||
|
||||
return (merger, isMuxed);
|
||||
return (merger, isMuxed, syncError);
|
||||
}
|
||||
|
||||
private async Task<DownloadResponse> DownloadMediaList(CrunchyEpMeta data, CrDownloadOptions options){
|
||||
// if (CmsToken?.Cms == null){
|
||||
// Console.WriteLine("Missing CMS Token");
|
||||
// MainWindow.Instance.ShowError("Missing CMS Token - are you signed in?");
|
||||
// return new DownloadResponse{
|
||||
// Data = new List<DownloadedMedia>(),
|
||||
// Error = true,
|
||||
// FileName = "./unknown",
|
||||
// ErrorText = "Login problem"
|
||||
// };
|
||||
// }
|
||||
|
||||
if (Profile.Username == "???"){
|
||||
MainWindow.Instance.ShowError("User Account not recognized - are you signed in?");
|
||||
return new DownloadResponse{
|
||||
|
|
@ -610,31 +619,55 @@ public class CrunchyrollManager{
|
|||
};
|
||||
}
|
||||
|
||||
if (!File.Exists(CfgManager.PathFFMPEG)){
|
||||
Console.Error.WriteLine("Missing ffmpeg");
|
||||
MainWindow.Instance.ShowError("FFmpeg not found");
|
||||
return new DownloadResponse{
|
||||
Data = new List<DownloadedMedia>(),
|
||||
Error = true,
|
||||
FileName = "./unknown",
|
||||
ErrorText = "Missing ffmpeg"
|
||||
};
|
||||
}
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)){
|
||||
if (!File.Exists(CfgManager.PathFFMPEG)){
|
||||
Console.Error.WriteLine("Missing ffmpeg");
|
||||
MainWindow.Instance.ShowError($"FFmpeg not found at: {CfgManager.PathFFMPEG}");
|
||||
return new DownloadResponse{
|
||||
Data = new List<DownloadedMedia>(),
|
||||
Error = true,
|
||||
FileName = "./unknown",
|
||||
ErrorText = "Missing ffmpeg"
|
||||
};
|
||||
}
|
||||
|
||||
if (!File.Exists(CfgManager.PathMKVMERGE)){
|
||||
Console.Error.WriteLine("Missing Mkvmerge");
|
||||
MainWindow.Instance.ShowError("Mkvmerge not found");
|
||||
return new DownloadResponse{
|
||||
Data = new List<DownloadedMedia>(),
|
||||
Error = true,
|
||||
FileName = "./unknown",
|
||||
ErrorText = "Missing Mkvmerge"
|
||||
};
|
||||
if (!File.Exists(CfgManager.PathMKVMERGE)){
|
||||
Console.Error.WriteLine("Missing Mkvmerge");
|
||||
MainWindow.Instance.ShowError($"Mkvmerge not found at: {CfgManager.PathMKVMERGE}");
|
||||
return new DownloadResponse{
|
||||
Data = new List<DownloadedMedia>(),
|
||||
Error = true,
|
||||
FileName = "./unknown",
|
||||
ErrorText = "Missing Mkvmerge"
|
||||
};
|
||||
}
|
||||
} else{
|
||||
if (!Helpers.IsInstalled("ffmpeg", "-version") && !File.Exists(Path.Combine(AppContext.BaseDirectory, "lib", "ffmpeg"))){
|
||||
Console.Error.WriteLine("Ffmpeg is not installed or not in the system PATH.");
|
||||
MainWindow.Instance.ShowError("Ffmpeg is not installed on the system or not found in the PATH.");
|
||||
return new DownloadResponse{
|
||||
Data = new List<DownloadedMedia>(),
|
||||
Error = true,
|
||||
FileName = "./unknown",
|
||||
ErrorText = "Ffmpeg is not installed"
|
||||
};
|
||||
}
|
||||
|
||||
if (!Helpers.IsInstalled("mkvmerge", "--version") && !File.Exists(Path.Combine(AppContext.BaseDirectory, "lib", "mkvmerge"))){
|
||||
Console.Error.WriteLine("Mkvmerge is not installed or not in the system PATH.");
|
||||
MainWindow.Instance.ShowError("Mkvmerge is not installed on the system or not found in the PATH.");
|
||||
return new DownloadResponse{
|
||||
Data = new List<DownloadedMedia>(),
|
||||
Error = true,
|
||||
FileName = "./unknown",
|
||||
ErrorText = "Mkvmerge is not installed"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!_widevine.canDecrypt){
|
||||
Console.Error.WriteLine("L3 key files missing");
|
||||
MainWindow.Instance.ShowError("Can't find CDM files in widevine folder ");
|
||||
MainWindow.Instance.ShowError("Can't find CDM files in the Widevine folder.\nFor more information, please check the FAQ section in the Wiki on the GitHub page.");
|
||||
return new DownloadResponse{
|
||||
Data = new List<DownloadedMedia>(),
|
||||
Error = true,
|
||||
|
|
@ -645,7 +678,7 @@ public class CrunchyrollManager{
|
|||
|
||||
if (!File.Exists(CfgManager.PathMP4Decrypt)){
|
||||
Console.Error.WriteLine("mp4decrypt not found");
|
||||
MainWindow.Instance.ShowError("Can't find mp4decrypt in lib folder ");
|
||||
MainWindow.Instance.ShowError($"Can't find mp4decrypt in lib folder at: {CfgManager.PathMP4Decrypt}");
|
||||
return new DownloadResponse{
|
||||
Data = new List<DownloadedMedia>(),
|
||||
Error = true,
|
||||
|
|
@ -864,14 +897,52 @@ public class CrunchyrollManager{
|
|||
return true;
|
||||
}).ToList();
|
||||
} else{
|
||||
dlFailed = true;
|
||||
if (hsLangs.Count > 0){
|
||||
var dialog = new ContentDialog(){
|
||||
Title = "Hardsub Select",
|
||||
PrimaryButtonText = "Select",
|
||||
CloseButtonText = "Close"
|
||||
};
|
||||
|
||||
return new DownloadResponse{
|
||||
Data = new List<DownloadedMedia>(),
|
||||
Error = dlFailed,
|
||||
FileName = "./unknown",
|
||||
ErrorText = "Hardsubs not available"
|
||||
};
|
||||
var viewModel = new ContentDialogDropdownSelectViewModel(dialog,
|
||||
data.SeriesTitle + (!string.IsNullOrEmpty(data.Season)
|
||||
? " - S" + data.Season + "E" + (data.EpisodeNumber != string.Empty ? data.EpisodeNumber : data.AbsolutEpisodeNumberE)
|
||||
: "") + " - " +
|
||||
data.EpisodeTitle, hsLangs);
|
||||
dialog.Content = new ContentDialogDropdownSelectView(){
|
||||
DataContext = viewModel
|
||||
};
|
||||
|
||||
var result = await dialog.ShowAsync();
|
||||
|
||||
if (result == ContentDialogResult.Primary){
|
||||
string selectedValue = viewModel.SelectedDropdownItem.stringValue;
|
||||
|
||||
if (hsLangs.IndexOf(selectedValue) > -1){
|
||||
Console.WriteLine($"Selecting stream with {Languages.Locale2language(selectedValue).Language} hardsubs");
|
||||
streams = streams.Where((s) => s.HardsubLang != "-" && s.HardsubLang == selectedValue).ToList();
|
||||
data.Hslang = selectedValue;
|
||||
}
|
||||
} else{
|
||||
dlFailed = true;
|
||||
|
||||
return new DownloadResponse{
|
||||
Data = new List<DownloadedMedia>(),
|
||||
Error = dlFailed,
|
||||
FileName = "./unknown",
|
||||
ErrorText = "Hardsub not available"
|
||||
};
|
||||
}
|
||||
} else{
|
||||
dlFailed = true;
|
||||
|
||||
return new DownloadResponse{
|
||||
Data = new List<DownloadedMedia>(),
|
||||
Error = dlFailed,
|
||||
FileName = "./unknown",
|
||||
ErrorText = "No Hardsubs available"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
} else{
|
||||
|
|
@ -986,12 +1057,12 @@ public class CrunchyrollManager{
|
|||
int chosenVideoQuality;
|
||||
if (options.DlVideoOnce && dlVideoOnce && options.SyncTiming){
|
||||
chosenVideoQuality = 1;
|
||||
} else if (options.QualityVideo == "best"){
|
||||
} else if (data.VideoQuality == "best"){
|
||||
chosenVideoQuality = videos.Count;
|
||||
} else if (options.QualityVideo == "worst"){
|
||||
} else if (data.VideoQuality == "worst"){
|
||||
chosenVideoQuality = 1;
|
||||
} else{
|
||||
var tempIndex = videos.FindIndex(a => a.quality.height + "" == options.QualityVideo);
|
||||
var tempIndex = videos.FindIndex(a => a.quality.height + "" == data.VideoQuality.Replace("p", ""));
|
||||
if (tempIndex < 0){
|
||||
chosenVideoQuality = videos.Count;
|
||||
} else{
|
||||
|
|
@ -1049,6 +1120,7 @@ public class CrunchyrollManager{
|
|||
|
||||
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.Code == curStream.AudioLang);
|
||||
if (lang == null){
|
||||
|
|
@ -1070,7 +1142,37 @@ 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?.CrLocale ?? lang.Value.Name), variables, options.Numbers, options.Override).ToArray());
|
||||
|
||||
string onlyFileName = Path.GetFileNameWithoutExtension(fileName);
|
||||
int maxLength = 220;
|
||||
|
||||
if (onlyFileName.Length > maxLength){
|
||||
Console.Error.WriteLine($"Filename too long {onlyFileName}");
|
||||
if (options.FileName.Split("\\").Last().Contains("${title}") && onlyFileName.Length - (data.EpisodeTitle ?? string.Empty).Length < maxLength){
|
||||
var titleVariable = variables.Find(e => e.Name == "title");
|
||||
|
||||
if (titleVariable != null){
|
||||
int excessLength = (onlyFileName.Length - maxLength);
|
||||
|
||||
if (excessLength > 0 && ((string)titleVariable.ReplaceWith).Length > excessLength){
|
||||
titleVariable.ReplaceWith = ((string)titleVariable.ReplaceWith).Substring(0, ((string)titleVariable.ReplaceWith).Length - excessLength);
|
||||
fileName = Path.Combine(FileNameManager.ParseFileName(options.FileName, variables, options.Numbers, options.Override).ToArray());
|
||||
onlyFileName = Path.GetFileNameWithoutExtension(fileName);
|
||||
|
||||
if (onlyFileName.Length > maxLength){
|
||||
fileName = Helpers.LimitFileNameLength(fileName, maxLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else{
|
||||
fileName = Helpers.LimitFileNameLength(fileName, maxLength);
|
||||
}
|
||||
|
||||
Console.Error.WriteLine($"Filename changed to {Path.GetFileNameWithoutExtension(fileName)}");
|
||||
}
|
||||
|
||||
//string outFile = Path.Combine(FileNameManager.ParseFileName(options.FileName + "." + (epMeta.Lang?.CrLocale ?? lang.Value.Name), variables, options.Numbers, options.Override).ToArray());
|
||||
string outFile = fileName + "." + (epMeta.Lang?.CrLocale ?? lang.Value.CrLocale);
|
||||
|
||||
string tempFile = Path.Combine(FileNameManager.ParseFileName($"temp-{(currentVersion.Guid != null ? currentVersion.Guid : currentMediaId)}", variables, options.Numbers, options.Override)
|
||||
.ToArray());
|
||||
|
|
@ -1163,7 +1265,7 @@ public class CrunchyrollManager{
|
|||
var json = JsonConvert.SerializeObject(reqBodyData);
|
||||
var reqBody = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
|
||||
var decRequest = HttpClientReq.CreateRequestMessage($"{Api.DRM}", HttpMethod.Post, false, false, null);
|
||||
var decRequest = HttpClientReq.CreateRequestMessage($"{ApiUrls.DRM}", HttpMethod.Post, false, false, null);
|
||||
decRequest.Content = reqBody;
|
||||
|
||||
var decRequestResponse = await HttpClientReq.Instance.SendHttpRequest(decRequest);
|
||||
|
|
@ -1270,6 +1372,7 @@ public class CrunchyrollManager{
|
|||
IsPrimary = isPrimary
|
||||
};
|
||||
files.Add(videoDownloadMedia);
|
||||
data.downloadedFiles.Add($"{tsFile}.video.m4s");
|
||||
} else{
|
||||
Console.WriteLine("No Video downloaded");
|
||||
}
|
||||
|
|
@ -1335,6 +1438,7 @@ public class CrunchyrollManager{
|
|||
Lang = lang.Value,
|
||||
IsPrimary = isPrimary
|
||||
});
|
||||
data.downloadedFiles.Add($"{tsFile}.audio.m4s");
|
||||
} else{
|
||||
Console.WriteLine("No Audio downloaded");
|
||||
}
|
||||
|
|
@ -1351,6 +1455,7 @@ public class CrunchyrollManager{
|
|||
IsPrimary = isPrimary
|
||||
};
|
||||
files.Add(videoDownloadMedia);
|
||||
data.downloadedFiles.Add($"{tsFile}.video.m4s");
|
||||
}
|
||||
|
||||
if (audioDownloaded){
|
||||
|
|
@ -1360,6 +1465,7 @@ public class CrunchyrollManager{
|
|||
Lang = lang.Value,
|
||||
IsPrimary = isPrimary
|
||||
});
|
||||
data.downloadedFiles.Add($"{tsFile}.audio.m4s");
|
||||
}
|
||||
}
|
||||
} else if (options.Novids){
|
||||
|
|
@ -1410,6 +1516,7 @@ public class CrunchyrollManager{
|
|||
File.WriteAllText($"{tsFile}.txt", string.Join("\r\n", compiledChapters));
|
||||
|
||||
files.Add(new DownloadedMedia{ Path = $"{tsFile}.txt", Lang = lang, Type = DownloadMediaType.Chapters });
|
||||
data.downloadedFiles.Add($"{tsFile}.txt");
|
||||
} catch{
|
||||
Console.Error.WriteLine("Failed to write chapter file");
|
||||
}
|
||||
|
|
@ -1429,9 +1536,9 @@ public class CrunchyrollManager{
|
|||
} else{
|
||||
Console.WriteLine("Subtitles downloading skipped!");
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(options.Waittime);
|
||||
}
|
||||
|
||||
// await Task.Delay(options.Waittime);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1468,12 +1575,14 @@ public class CrunchyrollManager{
|
|||
Type = DownloadMediaType.Description,
|
||||
Path = fullPath,
|
||||
});
|
||||
data.downloadedFiles.Add(fullPath);
|
||||
} else{
|
||||
if (files.All(e => e.Type != DownloadMediaType.Description)){
|
||||
files.Add(new DownloadedMedia{
|
||||
Type = DownloadMediaType.Description,
|
||||
Path = fullPath,
|
||||
});
|
||||
data.downloadedFiles.Add(fullPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1649,6 +1758,7 @@ public class CrunchyrollManager{
|
|||
Lang = sxData.Language,
|
||||
RelatedVideoDownloadMedia = videoDownloadMedia
|
||||
});
|
||||
data.downloadedFiles.Add(sxData.Path);
|
||||
} else{
|
||||
Console.WriteLine($"Failed to download subtitle: ${sxData.File}");
|
||||
}
|
||||
|
|
@ -1678,7 +1788,10 @@ public class CrunchyrollManager{
|
|||
M3U8Json videoJson = new M3U8Json{
|
||||
Segments = chosenVideoSegments.segments.Cast<dynamic>().ToList()
|
||||
};
|
||||
|
||||
|
||||
data.downloadedFiles.Add(chosenVideoSegments.pssh != null ? $"{tempTsFile}.video.enc.m4s" : $"{tsFile}.video.m4s");
|
||||
data.downloadedFiles.Add(chosenVideoSegments.pssh != null ? $"{tempTsFile}.video.enc.m4s.resume" : $"{tsFile}.video.m4s.resume");
|
||||
|
||||
var videoDownloader = new HlsDownloader(new HlsOptions{
|
||||
Output = chosenVideoSegments.pssh != null ? $"{tempTsFile}.video.enc.m4s" : $"{tsFile}.video.m4s",
|
||||
Timeout = options.Timeout,
|
||||
|
|
@ -1689,6 +1802,7 @@ public class CrunchyrollManager{
|
|||
Override = options.Force,
|
||||
}, data, true, false);
|
||||
|
||||
|
||||
var videoDownloadResult = await videoDownloader.Download();
|
||||
|
||||
return (videoDownloadResult.Ok, videoDownloadResult.Parts, tsFile);
|
||||
|
|
@ -1730,6 +1844,9 @@ public class CrunchyrollManager{
|
|||
M3U8Json audioJson = new M3U8Json{
|
||||
Segments = chosenAudioSegments.segments.Cast<dynamic>().ToList()
|
||||
};
|
||||
|
||||
data.downloadedFiles.Add(chosenAudioSegments.pssh != null ? $"{tempTsFile}.audio.enc.m4s" : $"{tsFile}.audio.m4s");
|
||||
data.downloadedFiles.Add(chosenAudioSegments.pssh != null ? $"{tempTsFile}.audio.enc.m4s.resume" : $"{tsFile}.audio.m4s.resume");
|
||||
|
||||
var audioDownloader = new HlsDownloader(new HlsOptions{
|
||||
Output = chosenAudioSegments.pssh != null ? $"{tempTsFile}.audio.enc.m4s" : $"{tsFile}.audio.m4s",
|
||||
|
|
@ -1863,7 +1980,7 @@ public class CrunchyrollManager{
|
|||
private async Task<bool> ParseChapters(string currentMediaId, List<string> compiledChapters){
|
||||
var showRequest = HttpClientReq.CreateRequestMessage($"https://static.crunchyroll.com/skip-events/production/{currentMediaId}.json", HttpMethod.Get, true, true, null);
|
||||
|
||||
var showRequestResponse = await HttpClientReq.Instance.SendHttpRequest(showRequest);
|
||||
var showRequestResponse = await HttpClientReq.Instance.SendHttpRequest(showRequest, true);
|
||||
|
||||
if (showRequestResponse.IsOk){
|
||||
CrunchyChapters chapterData = new CrunchyChapters();
|
||||
|
|
@ -1873,7 +1990,7 @@ public class CrunchyrollManager{
|
|||
JObject jObject = JObject.Parse(showRequestResponse.ResponseContent);
|
||||
|
||||
if (jObject.TryGetValue("lastUpdate", out JToken lastUpdateToken)){
|
||||
chapterData.lastUpdate = lastUpdateToken.ToObject<DateTime?>();
|
||||
chapterData.lastUpdate = lastUpdateToken.ToObject<DateTime>();
|
||||
}
|
||||
|
||||
if (jObject.TryGetValue("mediaId", out JToken mediaIdToken)){
|
||||
|
|
@ -1954,7 +2071,7 @@ public class CrunchyrollManager{
|
|||
|
||||
showRequest = HttpClientReq.CreateRequestMessage($"https://static.crunchyroll.com/datalab-intro-v2/{currentMediaId}.json", HttpMethod.Get, true, true, null);
|
||||
|
||||
showRequestResponse = await HttpClientReq.Instance.SendHttpRequest(showRequest);
|
||||
showRequestResponse = await HttpClientReq.Instance.SendHttpRequest(showRequest, true);
|
||||
|
||||
if (showRequestResponse.IsOk){
|
||||
CrunchyOldChapter chapterData = Helpers.Deserialize<CrunchyOldChapter>(showRequestResponse.ResponseContent, SettingsJsonSerializerSettings);
|
||||
|
|
@ -1988,7 +2105,7 @@ public class CrunchyrollManager{
|
|||
return true;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine("Old Chapter API request failed");
|
||||
Console.Error.WriteLine("Chapter request failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,550 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.Ffmpeg_Encoding;
|
||||
using CRD.Utils.Sonarr;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.History;
|
||||
using CRD.ViewModels;
|
||||
using CRD.ViewModels.Utils;
|
||||
using CRD.Views.Utils;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace CRD.Downloader.Crunchyroll.ViewModels;
|
||||
|
||||
public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
||||
[ObservableProperty]
|
||||
private bool _downloadVideo = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _downloadAudio = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _downloadChapters = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _addScaledBorderAndShadow;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _includeSignSubs;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _includeCcSubs;
|
||||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem _selectedScaledBorderAndShadow;
|
||||
|
||||
public ObservableCollection<ComboBoxItem> ScaledBorderAndShadow{ get; } = new(){
|
||||
new ComboBoxItem(){ Content = "ScaledBorderAndShadow: yes" },
|
||||
new ComboBoxItem(){ Content = "ScaledBorderAndShadow: no" },
|
||||
};
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _skipMuxing;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _muxToMp4;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _syncTimings;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _defaultSubSigns;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _defaultSubForcedDisplay;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _includeEpisodeDescription;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _downloadVideoForEveryDub;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _keepDubsSeparate;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _skipSubMux;
|
||||
|
||||
[ObservableProperty]
|
||||
private double? _leadingNumbers;
|
||||
|
||||
[ObservableProperty]
|
||||
private double? _partSize;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _fileName = "";
|
||||
|
||||
[ObservableProperty]
|
||||
private string _fileTitle = "";
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<StringItem> _mkvMergeOptions = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private string _mkvMergeOption = "";
|
||||
|
||||
[ObservableProperty]
|
||||
private string _ffmpegOption = "";
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<StringItem> _ffmpegOptions = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private string _selectedSubs = "all";
|
||||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem _selectedHSLang;
|
||||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem _selectedDescriptionLang;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _selectedDubs = "ja-JP";
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<ListBoxItem> _selectedDubLang = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem _selectedStreamEndpoint;
|
||||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem _selectedDefaultDubLang;
|
||||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem _selectedDefaultSubLang;
|
||||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem? _selectedVideoQuality;
|
||||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem? _selectedAudioQuality;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<ListBoxItem> _selectedSubLang = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private Color _listBoxColor;
|
||||
|
||||
public ObservableCollection<ComboBoxItem> VideoQualityList{ get; } = new(){
|
||||
new ComboBoxItem(){ Content = "best" },
|
||||
new ComboBoxItem(){ Content = "1080" },
|
||||
new ComboBoxItem(){ Content = "720" },
|
||||
new ComboBoxItem(){ Content = "480" },
|
||||
new ComboBoxItem(){ Content = "360" },
|
||||
new ComboBoxItem(){ Content = "240" },
|
||||
new ComboBoxItem(){ Content = "worst" },
|
||||
};
|
||||
|
||||
public ObservableCollection<ComboBoxItem> AudioQualityList{ get; } = new(){
|
||||
new ComboBoxItem(){ Content = "best" },
|
||||
new ComboBoxItem(){ Content = "128kB/s" },
|
||||
new ComboBoxItem(){ Content = "96kB/s" },
|
||||
new ComboBoxItem(){ Content = "64kB/s" },
|
||||
new ComboBoxItem(){ Content = "worst" },
|
||||
};
|
||||
|
||||
public ObservableCollection<ComboBoxItem> HardSubLangList{ get; } = new(){
|
||||
new ComboBoxItem(){ Content = "none" },
|
||||
};
|
||||
|
||||
public ObservableCollection<ComboBoxItem> DescriptionLangList{ get; } = new(){
|
||||
new ComboBoxItem(){ Content = "default" },
|
||||
new ComboBoxItem(){ Content = "de-DE" },
|
||||
new ComboBoxItem(){ Content = "en-US" },
|
||||
new ComboBoxItem(){ Content = "es-419" },
|
||||
new ComboBoxItem(){ Content = "es-ES" },
|
||||
new ComboBoxItem(){ Content = "fr-FR" },
|
||||
new ComboBoxItem(){ Content = "it-IT" },
|
||||
new ComboBoxItem(){ Content = "pt-BR" },
|
||||
new ComboBoxItem(){ Content = "pt-PT" },
|
||||
new ComboBoxItem(){ Content = "ru-RU" },
|
||||
new ComboBoxItem(){ Content = "hi-IN" },
|
||||
new ComboBoxItem(){ Content = "ar-SA" },
|
||||
};
|
||||
|
||||
public ObservableCollection<ListBoxItem> DubLangList{ get; } = new(){
|
||||
};
|
||||
|
||||
|
||||
public ObservableCollection<ComboBoxItem> DefaultDubLangList{ get; } = new(){
|
||||
};
|
||||
|
||||
public ObservableCollection<ComboBoxItem> DefaultSubLangList{ get; } = new(){
|
||||
};
|
||||
|
||||
|
||||
public ObservableCollection<ListBoxItem> SubLangList{ get; } = new(){
|
||||
new ListBoxItem(){ Content = "all" },
|
||||
new ListBoxItem(){ Content = "none" },
|
||||
};
|
||||
|
||||
public ObservableCollection<ComboBoxItem> StreamEndpoints{ get; } = new(){
|
||||
new ComboBoxItem(){ Content = "web/firefox" },
|
||||
new ComboBoxItem(){ Content = "console/switch" },
|
||||
new ComboBoxItem(){ Content = "console/ps4" },
|
||||
new ComboBoxItem(){ Content = "console/ps5" },
|
||||
new ComboBoxItem(){ Content = "console/xbox_one" },
|
||||
new ComboBoxItem(){ Content = "web/edge" },
|
||||
// new ComboBoxItem(){ Content = "web/safari" },
|
||||
new ComboBoxItem(){ Content = "web/chrome" },
|
||||
new ComboBoxItem(){ Content = "web/fallback" },
|
||||
// new ComboBoxItem(){ Content = "ios/iphone" },
|
||||
// new ComboBoxItem(){ Content = "ios/ipad" },
|
||||
new ComboBoxItem(){ Content = "android/phone" },
|
||||
new ComboBoxItem(){ Content = "tv/samsung" },
|
||||
};
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isEncodeEnabled;
|
||||
|
||||
[ObservableProperty]
|
||||
private StringItem _selectedEncodingPreset;
|
||||
|
||||
public ObservableCollection<StringItem> EncodingPresetsList{ get; } = new();
|
||||
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _cCSubsMuxingFlag;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _cCSubsFont;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _signsSubsAsForced;
|
||||
|
||||
private bool settingsLoaded;
|
||||
|
||||
public CrunchyrollSettingsViewModel(){
|
||||
|
||||
foreach (var languageItem in Languages.languages){
|
||||
HardSubLangList.Add(new ComboBoxItem{ Content = languageItem.CrLocale });
|
||||
SubLangList.Add(new ListBoxItem{ Content = languageItem.CrLocale });
|
||||
DubLangList.Add(new ListBoxItem{ Content = languageItem.CrLocale });
|
||||
DefaultDubLangList.Add(new ComboBoxItem{ Content = languageItem.CrLocale });
|
||||
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;
|
||||
|
||||
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];
|
||||
|
||||
ComboBoxItem? hsLang = HardSubLangList.FirstOrDefault(a => a.Content != null && (string)a.Content == Languages.Locale2language(options.Hslang).CrLocale) ?? null;
|
||||
SelectedHSLang = hsLang ?? HardSubLangList[0];
|
||||
|
||||
ComboBoxItem? defaultDubLang = DefaultDubLangList.FirstOrDefault(a => a.Content != null && (string)a.Content == (options.DefaultAudio ?? "")) ?? null;
|
||||
SelectedDefaultDubLang = defaultDubLang ?? DefaultDubLangList[0];
|
||||
|
||||
ComboBoxItem? defaultSubLang = DefaultSubLangList.FirstOrDefault(a => a.Content != null && (string)a.Content == (options.DefaultSub ?? "")) ?? null;
|
||||
SelectedDefaultSubLang = defaultSubLang ?? DefaultSubLangList[0];
|
||||
|
||||
ComboBoxItem? streamEndpoint = StreamEndpoints.FirstOrDefault(a => a.Content != null && (string)a.Content == (options.StreamEndpoint ?? "")) ?? null;
|
||||
SelectedStreamEndpoint = streamEndpoint ?? StreamEndpoints[0];
|
||||
|
||||
var softSubLang = SubLangList.Where(a => options.DlSubs.Contains(a.Content)).ToList();
|
||||
|
||||
SelectedSubLang.Clear();
|
||||
foreach (var listBoxItem in softSubLang){
|
||||
SelectedSubLang.Add(listBoxItem);
|
||||
}
|
||||
|
||||
var dubLang = DubLangList.Where(a => options.DubLang.Contains(a.Content)).ToList();
|
||||
|
||||
SelectedDubLang.Clear();
|
||||
foreach (var listBoxItem in dubLang){
|
||||
SelectedDubLang.Add(listBoxItem);
|
||||
}
|
||||
|
||||
AddScaledBorderAndShadow = options.SubsAddScaledBorder is ScaledBorderAndShadowSelection.ScaledBorderAndShadowNo or ScaledBorderAndShadowSelection.ScaledBorderAndShadowYes;
|
||||
SelectedScaledBorderAndShadow = GetScaledBorderAndShadowFromOptions(options);
|
||||
|
||||
CCSubsFont = options.CcSubsFont ?? "";
|
||||
CCSubsMuxingFlag = options.CcSubsMuxingFlag;
|
||||
SignsSubsAsForced = options.SignsSubsAsForced;
|
||||
SkipMuxing = options.SkipMuxing;
|
||||
IsEncodeEnabled = options.IsEncodeEnabled;
|
||||
DefaultSubForcedDisplay = options.DefaultSubForcedDisplay;
|
||||
DefaultSubSigns = options.DefaultSubSigns;
|
||||
PartSize = options.Partsize;
|
||||
IncludeEpisodeDescription = options.IncludeVideoDescription;
|
||||
FileTitle = options.VideoTitle ?? "";
|
||||
IncludeSignSubs = options.IncludeSignsSubs;
|
||||
IncludeCcSubs = options.IncludeCcSubs;
|
||||
DownloadVideo = !options.Novids;
|
||||
DownloadAudio = !options.Noaudio;
|
||||
DownloadVideoForEveryDub = !options.DlVideoOnce;
|
||||
KeepDubsSeparate = options.KeepDubsSeperate;
|
||||
DownloadChapters = options.Chapters;
|
||||
MuxToMp4 = options.Mp4;
|
||||
SyncTimings = options.SyncTiming;
|
||||
SkipSubMux = options.SkipSubsMux;
|
||||
LeadingNumbers = options.Numbers;
|
||||
FileName = options.FileName;
|
||||
|
||||
ComboBoxItem? qualityAudio = AudioQualityList.FirstOrDefault(a => a.Content != null && (string)a.Content == options.QualityAudio) ?? null;
|
||||
SelectedAudioQuality = qualityAudio ?? AudioQualityList[0];
|
||||
|
||||
ComboBoxItem? qualityVideo = VideoQualityList.FirstOrDefault(a => a.Content != null && (string)a.Content == options.QualityVideo) ?? null;
|
||||
SelectedVideoQuality = qualityVideo ?? VideoQualityList[0];
|
||||
|
||||
MkvMergeOptions.Clear();
|
||||
if (options.MkvmergeOptions != null){
|
||||
foreach (var mkvmergeParam in options.MkvmergeOptions){
|
||||
MkvMergeOptions.Add(new StringItem(){ stringValue = mkvmergeParam });
|
||||
}
|
||||
}
|
||||
|
||||
FfmpegOptions.Clear();
|
||||
if (options.FfmpegOptions != null){
|
||||
foreach (var ffmpegParam in options.FfmpegOptions){
|
||||
FfmpegOptions.Add(new StringItem(){ stringValue = ffmpegParam });
|
||||
}
|
||||
}
|
||||
|
||||
var dubs = SelectedDubLang.Select(item => item.Content?.ToString());
|
||||
SelectedDubs = string.Join(", ", dubs) ?? "";
|
||||
|
||||
var subs = SelectedSubLang.Select(item => item.Content?.ToString());
|
||||
SelectedSubs = string.Join(", ", subs) ?? "";
|
||||
|
||||
SelectedSubLang.CollectionChanged += Changes;
|
||||
SelectedDubLang.CollectionChanged += Changes;
|
||||
|
||||
MkvMergeOptions.CollectionChanged += Changes;
|
||||
FfmpegOptions.CollectionChanged += Changes;
|
||||
|
||||
settingsLoaded = true;
|
||||
}
|
||||
|
||||
private void UpdateSettings(){
|
||||
if (!settingsLoaded){
|
||||
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.DefaultSubSigns = DefaultSubSigns;
|
||||
CrunchyrollManager.Instance.CrunOptions.DefaultSubForcedDisplay = DefaultSubForcedDisplay;
|
||||
CrunchyrollManager.Instance.CrunOptions.IncludeVideoDescription = IncludeEpisodeDescription;
|
||||
CrunchyrollManager.Instance.CrunOptions.VideoTitle = FileTitle;
|
||||
CrunchyrollManager.Instance.CrunOptions.Novids = !DownloadVideo;
|
||||
CrunchyrollManager.Instance.CrunOptions.Noaudio = !DownloadAudio;
|
||||
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;
|
||||
CrunchyrollManager.Instance.CrunOptions.Numbers = Math.Clamp((int)(LeadingNumbers ?? 0), 0, 10);
|
||||
CrunchyrollManager.Instance.CrunOptions.FileName = FileName;
|
||||
CrunchyrollManager.Instance.CrunOptions.IncludeSignsSubs = IncludeSignSubs;
|
||||
CrunchyrollManager.Instance.CrunOptions.IncludeCcSubs = IncludeCcSubs;
|
||||
CrunchyrollManager.Instance.CrunOptions.Partsize = Math.Clamp((int)(PartSize ?? 0), 0, 10000);
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.SubsAddScaledBorder = GetScaledBorderAndShadowSelection();
|
||||
|
||||
List<string> softSubs = new List<string>();
|
||||
foreach (var listBoxItem in SelectedSubLang){
|
||||
softSubs.Add(listBoxItem.Content + "");
|
||||
}
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.DlSubs = softSubs;
|
||||
|
||||
string descLang = SelectedDescriptionLang.Content + "";
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.DescriptionLang = descLang != "default" ? descLang : CrunchyrollManager.Instance.DefaultLocale;
|
||||
|
||||
string hslang = SelectedHSLang.Content + "";
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.Hslang = hslang != "none" ? Languages.FindLang(hslang).Locale : hslang;
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.DefaultAudio = SelectedDefaultDubLang.Content + "";
|
||||
CrunchyrollManager.Instance.CrunOptions.DefaultSub = SelectedDefaultSubLang.Content + "";
|
||||
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.StreamEndpoint = SelectedStreamEndpoint.Content + "";
|
||||
|
||||
List<string> dubLangs = new List<string>();
|
||||
foreach (var listBoxItem in SelectedDubLang){
|
||||
dubLangs.Add(listBoxItem.Content + "");
|
||||
}
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.DubLang = dubLangs;
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.QualityAudio = SelectedAudioQuality?.Content + "";
|
||||
CrunchyrollManager.Instance.CrunOptions.QualityVideo = SelectedVideoQuality?.Content + "";
|
||||
|
||||
List<string> mkvmergeParams = new List<string>();
|
||||
foreach (var mkvmergeParam in MkvMergeOptions){
|
||||
mkvmergeParams.Add(mkvmergeParam.stringValue);
|
||||
}
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.MkvmergeOptions = mkvmergeParams;
|
||||
|
||||
List<string> ffmpegParams = new List<string>();
|
||||
foreach (var ffmpegParam in FfmpegOptions){
|
||||
ffmpegParams.Add(ffmpegParam.stringValue);
|
||||
}
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.FfmpegOptions = ffmpegParams;
|
||||
|
||||
CfgManager.WriteSettingsToFile();
|
||||
}
|
||||
|
||||
|
||||
private ScaledBorderAndShadowSelection GetScaledBorderAndShadowSelection(){
|
||||
if (!AddScaledBorderAndShadow){
|
||||
return ScaledBorderAndShadowSelection.DontAdd;
|
||||
}
|
||||
|
||||
if (SelectedScaledBorderAndShadow.Content + "" == "ScaledBorderAndShadow: yes"){
|
||||
return ScaledBorderAndShadowSelection.ScaledBorderAndShadowYes;
|
||||
}
|
||||
|
||||
if (SelectedScaledBorderAndShadow.Content + "" == "ScaledBorderAndShadow: no"){
|
||||
return ScaledBorderAndShadowSelection.ScaledBorderAndShadowNo;
|
||||
}
|
||||
|
||||
return ScaledBorderAndShadowSelection.ScaledBorderAndShadowYes;
|
||||
}
|
||||
|
||||
private ComboBoxItem GetScaledBorderAndShadowFromOptions(CrDownloadOptions options){
|
||||
switch (options.SubsAddScaledBorder){
|
||||
case (ScaledBorderAndShadowSelection.ScaledBorderAndShadowYes):
|
||||
return ScaledBorderAndShadow.FirstOrDefault(a => a.Content != null && (string)a.Content == "ScaledBorderAndShadow: yes") ?? ScaledBorderAndShadow[0];
|
||||
case ScaledBorderAndShadowSelection.ScaledBorderAndShadowNo:
|
||||
return ScaledBorderAndShadow.FirstOrDefault(a => a.Content != null && (string)a.Content == "ScaledBorderAndShadow: no") ?? ScaledBorderAndShadow[0];
|
||||
default:
|
||||
return ScaledBorderAndShadow[0];
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void AddMkvMergeParam(){
|
||||
MkvMergeOptions.Add(new StringItem(){ stringValue = MkvMergeOption });
|
||||
MkvMergeOption = "";
|
||||
RaisePropertyChanged(nameof(MkvMergeOptions));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void RemoveMkvMergeParam(StringItem param){
|
||||
MkvMergeOptions.Remove(param);
|
||||
RaisePropertyChanged(nameof(MkvMergeOptions));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void AddFfmpegParam(){
|
||||
FfmpegOptions.Add(new StringItem(){ stringValue = FfmpegOption });
|
||||
FfmpegOption = "";
|
||||
RaisePropertyChanged(nameof(FfmpegOptions));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void RemoveFfmpegParam(StringItem param){
|
||||
FfmpegOptions.Remove(param);
|
||||
RaisePropertyChanged(nameof(FfmpegOptions));
|
||||
}
|
||||
|
||||
private void Changes(object? sender, NotifyCollectionChangedEventArgs e){
|
||||
UpdateSettings();
|
||||
|
||||
var dubs = SelectedDubLang.Select(item => item.Content?.ToString());
|
||||
SelectedDubs = string.Join(", ", dubs) ?? "";
|
||||
|
||||
var subs = SelectedSubLang.Select(item => item.Content?.ToString());
|
||||
SelectedSubs = string.Join(", ", subs) ?? "";
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(PropertyChangedEventArgs e){
|
||||
base.OnPropertyChanged(e);
|
||||
|
||||
if (e.PropertyName is nameof(SelectedDubs)
|
||||
or nameof(SelectedSubs)
|
||||
or nameof(ListBoxColor)){
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateSettings();
|
||||
|
||||
if (e.PropertyName is nameof(History)){
|
||||
if (CrunchyrollManager.Instance.CrunOptions.History){
|
||||
if (File.Exists(CfgManager.PathCrHistory)){
|
||||
var decompressedJson = CfgManager.DecompressJsonFile(CfgManager.PathCrHistory);
|
||||
if (!string.IsNullOrEmpty(decompressedJson)){
|
||||
CrunchyrollManager.Instance.HistoryList = Helpers.Deserialize<ObservableCollection<HistorySeries>>(decompressedJson, CrunchyrollManager.Instance.SettingsJsonSerializerSettings) ??
|
||||
new ObservableCollection<HistorySeries>();
|
||||
|
||||
foreach (var historySeries in CrunchyrollManager.Instance.HistoryList){
|
||||
historySeries.Init();
|
||||
foreach (var historySeriesSeason in historySeries.Seasons){
|
||||
historySeriesSeason.Init();
|
||||
}
|
||||
}
|
||||
} else{
|
||||
CrunchyrollManager.Instance.HistoryList =[];
|
||||
}
|
||||
}
|
||||
|
||||
_ = SonarrClient.Instance.RefreshSonarrLite();
|
||||
} else{
|
||||
CrunchyrollManager.Instance.HistoryList =[];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[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];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
533
CRD/Downloader/Crunchyroll/Views/CrunchyrollSettingsView.axaml
Normal file
533
CRD/Downloader/Crunchyroll/Views/CrunchyrollSettingsView.axaml
Normal file
|
|
@ -0,0 +1,533 @@
|
|||
<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:vm="clr-namespace:CRD.Downloader.Crunchyroll.ViewModels"
|
||||
xmlns:structs="clr-namespace:CRD.Utils.Structs"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:DataType="vm:CrunchyrollSettingsViewModel"
|
||||
x:Class="CRD.Downloader.Crunchyroll.Views.CrunchyrollSettingsView">
|
||||
|
||||
|
||||
<Design.DataContext>
|
||||
<vm:CrunchyrollSettingsViewModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<ScrollViewer Padding="20 20 20 0">
|
||||
<StackPanel Spacing="8">
|
||||
|
||||
<controls:SettingsExpander Header="Dub language"
|
||||
IconSource="Speaker2"
|
||||
Description="Change the selected dub language (with multiple dubs some can be out of sync)">
|
||||
<controls:SettingsExpander.Footer>
|
||||
|
||||
<StackPanel>
|
||||
<ToggleButton x:Name="DropdownButtonDub" Width="210" HorizontalContentAlignment="Stretch">
|
||||
<ToggleButton.Content>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock HorizontalAlignment="Center" Text="{Binding SelectedDubs}"
|
||||
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=DropdownButtonDub, Mode=TwoWay}"
|
||||
Placement="Bottom"
|
||||
PlacementTarget="{Binding ElementName=DropdownButtonDub}">
|
||||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||
<ListBox x:Name="ListBoxDubsSelection" SelectionMode="Multiple,Toggle" Width="210"
|
||||
MaxHeight="400"
|
||||
ItemsSource="{Binding DubLangList}"
|
||||
SelectedItems="{Binding SelectedDubLang}"
|
||||
PointerWheelChanged="ListBox_PointerWheelChanged">
|
||||
</ListBox>
|
||||
</Border>
|
||||
</Popup>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
</controls:SettingsExpander.Footer>
|
||||
|
||||
</controls:SettingsExpander>
|
||||
|
||||
|
||||
<controls:SettingsExpander Header="Hardsubs language"
|
||||
IconSource="FontColorFilled"
|
||||
Description="Change the selected hardsub language">
|
||||
<controls:SettingsExpander.Footer>
|
||||
<ComboBox HorizontalContentAlignment="Center" MinWidth="210" MaxDropDownHeight="400"
|
||||
ItemsSource="{Binding HardSubLangList}"
|
||||
SelectedItem="{Binding SelectedHSLang}">
|
||||
</ComboBox>
|
||||
</controls:SettingsExpander.Footer>
|
||||
|
||||
</controls:SettingsExpander>
|
||||
|
||||
|
||||
<controls:SettingsExpander Header="Softsubs language"
|
||||
IconSource="FontColor"
|
||||
Description="Change the selected softsubs language">
|
||||
<controls:SettingsExpander.Footer>
|
||||
<StackPanel>
|
||||
<ToggleButton x:Name="dropdownButton" Width="210" HorizontalContentAlignment="Stretch">
|
||||
<ToggleButton.Content>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock HorizontalAlignment="Center" Text="{Binding SelectedSubs}"
|
||||
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=dropdownButton, Mode=TwoWay}" Placement="Bottom"
|
||||
PlacementTarget="{Binding ElementName=dropdownButton}">
|
||||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||
<ListBox x:Name="listBoxSubsSelection" SelectionMode="Multiple,Toggle" Width="210"
|
||||
MaxHeight="400"
|
||||
ItemsSource="{Binding SubLangList}" SelectedItems="{Binding SelectedSubLang}"
|
||||
PointerWheelChanged="ListBox_PointerWheelChanged">
|
||||
</ListBox>
|
||||
</Border>
|
||||
</Popup>
|
||||
</StackPanel>
|
||||
</controls:SettingsExpander.Footer>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Add ScaledBorderAndShadow ">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ComboBox HorizontalContentAlignment="Center" IsVisible="{Binding AddScaledBorderAndShadow}" Margin="5 0" MinWidth="210" MaxDropDownHeight="400"
|
||||
ItemsSource="{Binding ScaledBorderAndShadow}"
|
||||
SelectedItem="{Binding SelectedScaledBorderAndShadow}">
|
||||
</ComboBox>
|
||||
<CheckBox IsChecked="{Binding AddScaledBorderAndShadow}"> </CheckBox>
|
||||
</StackPanel>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Signs Subtitles " Description="Download Signs (Forced) Subtitles">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
|
||||
<CheckBox HorizontalAlignment="Right" IsChecked="{Binding IncludeSignSubs}"> </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 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 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>
|
||||
|
||||
|
||||
<controls:SettingsExpander Header="Download Settings"
|
||||
IconSource="Download"
|
||||
Description="Adjust download settings"
|
||||
IsExpanded="False">
|
||||
|
||||
|
||||
<controls:SettingsExpanderItem Content="Download Parts"
|
||||
Description="How many parts of the stream are downloaded simultaneously">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<controls:NumberBox Minimum="0" Maximum="10000"
|
||||
Value="{Binding PartSize}"
|
||||
SpinButtonPlacementMode="Hidden"
|
||||
HorizontalAlignment="Stretch" />
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
|
||||
<controls:SettingsExpanderItem Content="Stream Endpoint ">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<ComboBox HorizontalContentAlignment="Center" MinWidth="210" MaxDropDownHeight="400"
|
||||
ItemsSource="{Binding StreamEndpoints}"
|
||||
SelectedItem="{Binding SelectedStreamEndpoint}">
|
||||
</ComboBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
|
||||
<controls:SettingsExpanderItem Content="Video">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding DownloadVideo}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Download Video for every dub">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<StackPanel>
|
||||
<CheckBox IsChecked="{Binding DownloadVideoForEveryDub}"> </CheckBox>
|
||||
<CheckBox IsVisible="{Binding DownloadVideoForEveryDub}" Content="Keep files separate" IsChecked="{Binding KeepDubsSeparate}"> </CheckBox>
|
||||
</StackPanel>
|
||||
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Video Quality">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<ComboBox HorizontalContentAlignment="Center" MinWidth="210" MaxDropDownHeight="400"
|
||||
ItemsSource="{Binding VideoQualityList}"
|
||||
SelectedItem="{Binding SelectedVideoQuality}">
|
||||
</ComboBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Audio">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding DownloadAudio}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Audio Quality">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<ComboBox HorizontalContentAlignment="Center" MinWidth="210" MaxDropDownHeight="400"
|
||||
ItemsSource="{Binding AudioQualityList}"
|
||||
SelectedItem="{Binding SelectedAudioQuality}">
|
||||
</ComboBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Chapters">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding DownloadChapters}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpander.Footer>
|
||||
</controls:SettingsExpander.Footer>
|
||||
</controls:SettingsExpander>
|
||||
|
||||
|
||||
<controls:SettingsExpander Header="Filename Settings"
|
||||
IconSource="Edit"
|
||||
Description="Change how the files are named"
|
||||
IsExpanded="False">
|
||||
|
||||
<controls:SettingsExpanderItem Content="Leading 0 for seasons and episodes">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<controls:NumberBox Minimum="0" Maximum="5"
|
||||
Value="{Binding LeadingNumbers}"
|
||||
SpinButtonPlacementMode="Inline"
|
||||
HorizontalAlignment="Stretch" />
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Filename"
|
||||
Description="${seriesTitle} ${seasonTitle} ${title} ${season} ${episode} ${height} ${width} ${dubs} - Folder with \\">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<TextBox Name="FileNameTextBox" HorizontalAlignment="Left" MinWidth="250"
|
||||
Text="{Binding FileName}" />
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpander.Footer>
|
||||
|
||||
</controls:SettingsExpander.Footer>
|
||||
</controls:SettingsExpander>
|
||||
|
||||
|
||||
<controls:SettingsExpander Header="Muxing Settings"
|
||||
IconSource="Repair"
|
||||
Description="MKVMerge and FFMpeg Settings"
|
||||
IsExpanded="False">
|
||||
|
||||
<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 IsVisible="{Binding !SkipMuxing}" Content="Keep Subtitles separate">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding SkipSubMux}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem IsVisible="{Binding !SkipMuxing}" Content="Default Audio ">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<ComboBox HorizontalContentAlignment="Center" MinWidth="210" MaxDropDownHeight="400"
|
||||
ItemsSource="{Binding DefaultDubLangList}"
|
||||
SelectedItem="{Binding SelectedDefaultDubLang}">
|
||||
</ComboBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
|
||||
<controls:SettingsExpanderItem IsVisible="{Binding !SkipMuxing}" Content="Default Subtitle ">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<ComboBox HorizontalContentAlignment="Center" MinWidth="210" MaxDropDownHeight="400"
|
||||
ItemsSource="{Binding DefaultSubLangList}"
|
||||
SelectedItem="{Binding SelectedDefaultSubLang}">
|
||||
</ComboBox>
|
||||
<CheckBox Content="Forced Display" IsChecked="{Binding DefaultSubForcedDisplay}"> </CheckBox>
|
||||
</StackPanel>
|
||||
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<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 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 IsVisible="{Binding !SkipMuxing}" Content="Include Episode description">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding IncludeEpisodeDescription}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem IsVisible="{Binding !SkipMuxing}" Content="Episode description Language">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<ComboBox HorizontalContentAlignment="Center" MinWidth="210" MaxDropDownHeight="400"
|
||||
ItemsSource="{Binding DescriptionLangList}"
|
||||
SelectedItem="{Binding SelectedDescriptionLang}">
|
||||
</ComboBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<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 IsVisible="{Binding !SkipMuxing}" Content="Additional MKVMerge Options">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Name="TargetTextBox2" HorizontalAlignment="Left" MinWidth="250"
|
||||
Text="{Binding MkvMergeOption }">
|
||||
</TextBox>
|
||||
<Button HorizontalAlignment="Center" Margin="5 0" Command="{Binding AddMkvMergeParam}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Add" FontSize="18" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<ItemsControl ItemsSource="{Binding MkvMergeOptions}" Margin="0,5" 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].((vm:CrunchyrollSettingsViewModel)DataContext).RemoveMkvMergeParam}"
|
||||
CommandParameter="{Binding .}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem IsVisible="{Binding !SkipMuxing}" Content="Additional FFMpeg Options">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox HorizontalAlignment="Left" MinWidth="250"
|
||||
Text="{Binding FfmpegOption }">
|
||||
</TextBox>
|
||||
<Button HorizontalAlignment="Center" Margin="5 0" Command="{Binding AddFfmpegParam}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Add" FontSize="18" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<ItemsControl ItemsSource="{Binding FfmpegOptions}" Margin="0,5" 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].((vm:CrunchyrollSettingsViewModel)DataContext).RemoveFfmpegParam}"
|
||||
CommandParameter="{Binding .}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</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>
|
||||
</controls:SettingsExpander>
|
||||
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
|
||||
</UserControl>
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
using System.Linq;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.VisualTree;
|
||||
|
||||
namespace CRD.Downloader.Crunchyroll.Views;
|
||||
|
||||
public partial class CrunchyrollSettingsView : UserControl{
|
||||
public CrunchyrollSettingsView(){
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,20 +19,18 @@ namespace CRD.Downloader;
|
|||
public class History(){
|
||||
private readonly CrunchyrollManager crunInstance = CrunchyrollManager.Instance;
|
||||
|
||||
public async Task CRUpdateSeries(string seriesId, string? seasonId){
|
||||
public async Task<bool> CRUpdateSeries(string seriesId, string? seasonId){
|
||||
await crunInstance.CrAuth.RefreshToken(true);
|
||||
|
||||
CrSeriesSearch? parsedSeries = await crunInstance.CrSeries.ParseSeriesById(seriesId, "ja-JP", true);
|
||||
|
||||
if (parsedSeries == null){
|
||||
Console.Error.WriteLine("Parse Data Invalid - series is maybe only available with VPN or got deleted");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parsedSeries.Data != null){
|
||||
foreach (var s in parsedSeries.Data){
|
||||
if (!string.IsNullOrEmpty(seasonId) && s.Id != seasonId) continue;
|
||||
|
||||
var sId = s.Id;
|
||||
if (s.Versions is{ Count: > 0 }){
|
||||
foreach (var sVersion in s.Versions.Where(sVersion => sVersion.Original == true)){
|
||||
|
|
@ -44,8 +42,11 @@ public class History(){
|
|||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(seasonId) && sId != seasonId) continue;
|
||||
|
||||
|
||||
var seasonData = await crunInstance.CrSeries.GetSeasonDataById(sId, string.IsNullOrEmpty(crunInstance.CrunOptions.HistoryLang) ? crunInstance.DefaultLocale : crunInstance.CrunOptions.HistoryLang, true);
|
||||
if (seasonData.Data != null) await UpdateWithSeasonData(seasonData.Data);
|
||||
if (seasonData.Data is{ Count: > 0 }) await UpdateWithSeasonData(seasonData.Data);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -55,8 +56,11 @@ public class History(){
|
|||
MatchHistorySeriesWithSonarr(false);
|
||||
await MatchHistoryEpisodesWithSonarr(false, historySeries);
|
||||
CfgManager.UpdateHistoryFile();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -124,10 +128,12 @@ public class History(){
|
|||
return (null, downloadDirPath);
|
||||
}
|
||||
|
||||
public (HistoryEpisode? historyEpisode, List<string> dublist, List<string> sublist, string downloadDirPath) GetHistoryEpisodeWithDubListAndDownloadDir(string? seriesId, string? seasonId, string episodeId){
|
||||
public (HistoryEpisode? historyEpisode, List<string> dublist, List<string> sublist, string downloadDirPath, string videoQuality) GetHistoryEpisodeWithDubListAndDownloadDir(string? seriesId, string? seasonId,
|
||||
string episodeId){
|
||||
var historySeries = crunInstance.HistoryList.FirstOrDefault(series => series.SeriesId == seriesId);
|
||||
|
||||
var downloadDirPath = "";
|
||||
var videoQuality = "";
|
||||
List<string> dublist =[];
|
||||
List<string> sublist =[];
|
||||
|
||||
|
|
@ -145,6 +151,10 @@ public class History(){
|
|||
downloadDirPath = historySeries.SeriesDownloadPath;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(historySeries.HistorySeriesVideoQualityOverride)){
|
||||
videoQuality = historySeries.HistorySeriesVideoQualityOverride;
|
||||
}
|
||||
|
||||
if (historySeason != null){
|
||||
var historyEpisode = historySeason.EpisodesList.Find(e => e.EpisodeId == episodeId);
|
||||
if (historySeason.HistorySeasonDubLangOverride.Count > 0){
|
||||
|
|
@ -159,13 +169,17 @@ public class History(){
|
|||
downloadDirPath = historySeason.SeasonDownloadPath;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(historySeason.HistorySeasonVideoQualityOverride)){
|
||||
videoQuality = historySeason.HistorySeasonVideoQualityOverride;
|
||||
}
|
||||
|
||||
if (historyEpisode != null){
|
||||
return (historyEpisode, dublist, sublist, downloadDirPath);
|
||||
return (historyEpisode, dublist, sublist, downloadDirPath, videoQuality);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (null, dublist, sublist, downloadDirPath);
|
||||
return (null, dublist, sublist, downloadDirPath, videoQuality);
|
||||
}
|
||||
|
||||
public List<string> GetDubList(string? seriesId, string? seasonId){
|
||||
|
|
@ -187,10 +201,11 @@ public class History(){
|
|||
return dublist;
|
||||
}
|
||||
|
||||
public List<string> GetSubList(string? seriesId, string? seasonId){
|
||||
public (List<string> sublist, string videoQuality) GetSubList(string? seriesId, string? seasonId){
|
||||
var historySeries = crunInstance.HistoryList.FirstOrDefault(series => series.SeriesId == seriesId);
|
||||
|
||||
List<string> sublist =[];
|
||||
var videoQuality = "";
|
||||
|
||||
if (historySeries != null){
|
||||
var historySeason = historySeries.Seasons.FirstOrDefault(s => s.SeasonId == seasonId);
|
||||
|
|
@ -198,12 +213,20 @@ public class History(){
|
|||
sublist = historySeries.HistorySeriesSoftSubsOverride;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(historySeries.HistorySeriesVideoQualityOverride)){
|
||||
videoQuality = historySeries.HistorySeriesVideoQualityOverride;
|
||||
}
|
||||
|
||||
if (historySeason is{ HistorySeasonSoftSubsOverride.Count: > 0 }){
|
||||
sublist = historySeason.HistorySeasonSoftSubsOverride;
|
||||
}
|
||||
|
||||
if (historySeason != null && !string.IsNullOrEmpty(historySeason.HistorySeasonVideoQualityOverride)){
|
||||
videoQuality = historySeason.HistorySeasonVideoQualityOverride;
|
||||
}
|
||||
}
|
||||
|
||||
return sublist;
|
||||
return (sublist, videoQuality);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -242,15 +265,14 @@ 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,
|
||||
|
|
@ -260,19 +282,19 @@ public class History(){
|
|||
SpecialEpisode = !int.TryParse(crunchyEpisode.Episode, out _),
|
||||
HistoryEpisodeAvailableDubLang = Languages.SortListByLangList(langList),
|
||||
HistoryEpisodeAvailableSoftSubs = Languages.SortListByLangList(crunchyEpisode.SubtitleLocales),
|
||||
EpisodeCrPremiumAirDate = crunchyEpisode.PremiumAvailableDate
|
||||
};
|
||||
|
||||
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 _);
|
||||
|
|
@ -366,6 +388,7 @@ public class History(){
|
|||
historySeries.HistorySeriesAvailableDubLang = Languages.SortListByLangList(series.AudioLocales);
|
||||
historySeries.HistorySeriesAvailableSoftSubs = Languages.SortListByLangList(series.SubtitleLocales);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -505,16 +528,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,
|
||||
|
|
@ -524,6 +547,7 @@ public class History(){
|
|||
SpecialEpisode = !int.TryParse(crunchyEpisode.Episode, out _),
|
||||
HistoryEpisodeAvailableDubLang = langList,
|
||||
HistoryEpisodeAvailableSoftSubs = crunchyEpisode.SubtitleLocales,
|
||||
EpisodeCrPremiumAirDate = crunchyEpisode.PremiumAvailableDate
|
||||
};
|
||||
|
||||
newSeason.EpisodesList.Add(newHistoryEpisode);
|
||||
|
|
@ -531,7 +555,7 @@ public class History(){
|
|||
|
||||
return newSeason;
|
||||
}
|
||||
|
||||
|
||||
public void MatchHistorySeriesWithSonarr(bool updateAll){
|
||||
if (crunInstance.CrunOptions.SonarrProperties is{ SonarrEnabled: false }){
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,19 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Styling;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.History;
|
||||
using CRD.Utils.Updater;
|
||||
using FluentAvalonia.Styling;
|
||||
|
||||
|
|
@ -50,12 +55,18 @@ public partial class ProgramManager : ObservableObject{
|
|||
|
||||
#endregion
|
||||
|
||||
|
||||
public Dictionary<string, List<AnilistSeries>> AnilistSeasons = new();
|
||||
public Dictionary<string, List<CalendarEpisode>> AnilistUpcoming = new();
|
||||
|
||||
private readonly FluentAvaloniaTheme? _faTheme;
|
||||
|
||||
private Queue<Func<Task>> taskQueue = new Queue<Func<Task>>();
|
||||
|
||||
private bool exitOnTaskFinish = false;
|
||||
|
||||
public IStorageProvider StorageProvider;
|
||||
|
||||
public ProgramManager(){
|
||||
_faTheme = Application.Current?.Styles[0] as FluentAvaloniaTheme;
|
||||
|
||||
|
|
@ -106,7 +117,7 @@ public partial class ProgramManager : ObservableObject{
|
|||
|
||||
private async void Init(){
|
||||
CrunchyrollManager.Instance.InitOptions();
|
||||
|
||||
|
||||
UpdateAvailable = await Updater.Instance.CheckForUpdatesAsync();
|
||||
|
||||
if (CrunchyrollManager.Instance.CrunOptions.AccentColor != null && !string.IsNullOrEmpty(CrunchyrollManager.Instance.CrunOptions.AccentColor)){
|
||||
|
|
@ -125,6 +136,10 @@ public partial class ProgramManager : ObservableObject{
|
|||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(CrunchyrollManager.Instance.CrunOptions.BackgroundImagePath)){
|
||||
Helpers.SetBackgroundImage(CrunchyrollManager.Instance.CrunOptions.BackgroundImagePath, CrunchyrollManager.Instance.CrunOptions.BackgroundImageOpacity,
|
||||
CrunchyrollManager.Instance.CrunOptions.BackgroundImageBlurRadius);
|
||||
}
|
||||
|
||||
await CrunchyrollManager.Instance.Init();
|
||||
|
||||
|
|
@ -133,6 +148,7 @@ public partial class ProgramManager : ObservableObject{
|
|||
await WorkOffArgsTasks();
|
||||
}
|
||||
|
||||
|
||||
private async Task WorkOffArgsTasks(){
|
||||
if (taskQueue.Count == 0){
|
||||
return;
|
||||
|
|
@ -149,13 +165,11 @@ public partial class ProgramManager : ObservableObject{
|
|||
Console.WriteLine("Exiting...");
|
||||
IClassicDesktopStyleApplicationLifetime? lifetime = (IClassicDesktopStyleApplicationLifetime)Application.Current?.ApplicationLifetime;
|
||||
if (lifetime != null){
|
||||
lifetime.Shutdown();
|
||||
lifetime.Shutdown();
|
||||
} else{
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,8 @@ using System.Collections.Generic;
|
|||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.CustomList;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.History;
|
||||
|
|
@ -102,7 +100,7 @@ public class QueueManager{
|
|||
|
||||
var sList = await CrunchyrollManager.Instance.CrEpisode.EpisodeData((CrunchyEpisode)episodeL, updateHistory);
|
||||
|
||||
(HistoryEpisode? historyEpisode, List<string> dublist, List<string> sublist, string downloadDirPath) historyEpisode = (null, [], [], "");
|
||||
(HistoryEpisode? historyEpisode, List<string> dublist, List<string> sublist, string downloadDirPath,string videoQuality) historyEpisode = (null, [], [], "","");
|
||||
|
||||
if (CrunchyrollManager.Instance.CrunOptions.History){
|
||||
var episode = sList.EpisodeAndLanguages.Items.First();
|
||||
|
|
@ -143,6 +141,8 @@ public class QueueManager{
|
|||
}
|
||||
}
|
||||
|
||||
selected.VideoQuality = !string.IsNullOrEmpty(historyEpisode.videoQuality) ? historyEpisode.videoQuality : CrunchyrollManager.Instance.CrunOptions.QualityVideo;
|
||||
|
||||
selected.DownloadSubs = historyEpisode.sublist.Count > 0 ? historyEpisode.sublist : CrunchyrollManager.Instance.CrunOptions.DlSubs;
|
||||
|
||||
Queue.Add(selected);
|
||||
|
|
@ -162,6 +162,12 @@ public class QueueManager{
|
|||
}
|
||||
} else{
|
||||
Console.WriteLine("Episode couldn't be added to Queue");
|
||||
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();
|
||||
|
||||
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));
|
||||
}
|
||||
} else{
|
||||
|
|
@ -184,7 +190,7 @@ public class QueueManager{
|
|||
}
|
||||
|
||||
|
||||
public void CrAddEpMetaToQueue(CrunchyEpMeta epMeta){
|
||||
public void CrAddMusicMetaToQueue(CrunchyEpMeta epMeta){
|
||||
Queue.Add(epMeta);
|
||||
MessageBus.Current.SendMessage(new ToastMessage($"Added episode to the queue", ToastType.Information, 1));
|
||||
}
|
||||
|
|
@ -249,7 +255,9 @@ public class QueueManager{
|
|||
}
|
||||
|
||||
var subLangList = CrunchyrollManager.Instance.History.GetSubList(crunchyEpMeta.ShowId, crunchyEpMeta.SeasonId);
|
||||
crunchyEpMeta.DownloadSubs = subLangList.Count > 0 ? subLangList : CrunchyrollManager.Instance.CrunOptions.DlSubs;
|
||||
|
||||
crunchyEpMeta.VideoQuality = !string.IsNullOrEmpty(subLangList.videoQuality) ? subLangList.videoQuality : CrunchyrollManager.Instance.CrunOptions.QualityVideo;
|
||||
crunchyEpMeta.DownloadSubs = subLangList.sublist.Count > 0 ? subLangList.sublist : CrunchyrollManager.Instance.CrunOptions.DlSubs;
|
||||
|
||||
|
||||
Queue.Add(crunchyEpMeta);
|
||||
|
|
|
|||
|
|
@ -2,9 +2,6 @@
|
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
||||
xmlns:uip="using:FluentAvalonia.UI.Controls.Primitives">
|
||||
|
||||
|
||||
|
||||
<!--
|
||||
NavView style in MainView for main app navigation
|
||||
While you are free to copy this into your own apps
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ using System.IO;
|
|||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CRD.Utils.DRM;
|
||||
|
||||
|
|
|
|||
|
|
@ -73,6 +73,9 @@ public enum Locale{
|
|||
|
||||
[EnumMember(Value = "zh-TW")]
|
||||
ZhTw,
|
||||
|
||||
[EnumMember(Value = "zh-HK")]
|
||||
ZhHk,
|
||||
|
||||
[EnumMember(Value = "ca-ES")]
|
||||
CaEs,
|
||||
|
|
@ -94,9 +97,6 @@ public enum Locale{
|
|||
|
||||
[EnumMember(Value = "te-IN")]
|
||||
TeIn,
|
||||
|
||||
[EnumMember(Value = "id-ID")]
|
||||
idID,
|
||||
}
|
||||
|
||||
public static class EnumExtensions{
|
||||
|
|
|
|||
|
|
@ -3,13 +3,11 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Reflection;
|
||||
using CRD.Downloader;
|
||||
using System.Runtime.InteropServices;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.Crunchyroll;
|
||||
using Newtonsoft.Json;
|
||||
using YamlDotNet.Core;
|
||||
using YamlDotNet.Core.Events;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
|
|
@ -24,16 +22,22 @@ public class CfgManager{
|
|||
public static readonly string PathCrHistory = Path.Combine(WorkingDirectory, "config", "history.json");
|
||||
public static readonly string PathWindowSettings = Path.Combine(WorkingDirectory, "config", "windowSettings.json");
|
||||
|
||||
public static readonly string PathFFMPEG = Path.Combine(WorkingDirectory, "lib", "ffmpeg.exe");
|
||||
public static readonly string PathMKVMERGE = Path.Combine(WorkingDirectory, "lib", "mkvmerge.exe");
|
||||
public static readonly string PathMP4Decrypt = Path.Combine(WorkingDirectory, "lib", "mp4decrypt.exe");
|
||||
private static readonly string ExecutableExtension = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : string.Empty;
|
||||
|
||||
public static readonly string PathFFMPEG = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Path.Combine(WorkingDirectory, "lib", "ffmpeg.exe") :
|
||||
File.Exists(Path.Combine(WorkingDirectory, "lib", "ffmpeg")) ? Path.Combine(WorkingDirectory, "lib", "ffmpeg") : "ffmpeg";
|
||||
|
||||
public static readonly string PathMKVMERGE = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Path.Combine(WorkingDirectory, "lib", "mkvmerge.exe") :
|
||||
File.Exists(Path.Combine(WorkingDirectory, "lib", "mkvmerge")) ? Path.Combine(WorkingDirectory, "lib", "mkvmerge") : "mkvmerge";
|
||||
|
||||
public static readonly string PathMP4Decrypt = Path.Combine(WorkingDirectory, "lib", "mp4decrypt" + ExecutableExtension);
|
||||
|
||||
public static readonly string 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");
|
||||
public static readonly string PathFONTS_DIR = Path.Combine(WorkingDirectory, "fonts");
|
||||
|
||||
public static readonly string PathLogFile = Path.Combine(WorkingDirectory, "logfile.txt");
|
||||
|
||||
|
|
@ -242,7 +246,7 @@ 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.
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils.Parser.Utils;
|
||||
using CRD.Utils.Structs;
|
||||
using Newtonsoft.Json;
|
||||
|
|
@ -118,11 +116,9 @@ public class HlsDownloader{
|
|||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (_data.M3U8Json != null){
|
||||
List<dynamic> segments = _data.M3U8Json.Segments;
|
||||
|
||||
|
||||
// map has init uri outside is none init uri
|
||||
// Download init part
|
||||
if (segments[0].map != null && _data.Offset == 0 && !_data.SkipInit){
|
||||
|
|
@ -249,7 +245,7 @@ public class HlsDownloader{
|
|||
int downloadedSeg = Math.Min(dlOffset, totalSeg);
|
||||
_data.Parts.Completed = downloadedSeg + _data.Offset; //
|
||||
|
||||
var dataLog = GetDownloadInfo(_data.DateStart, _data.Parts.Completed, totalSeg, _data.BytesDownloaded,_data.TotalBytes);
|
||||
var dataLog = GetDownloadInfo(_data.DateStart, _data.Parts.Completed, totalSeg, _data.BytesDownloaded, _data.TotalBytes);
|
||||
_data.BytesDownloaded = 0;
|
||||
|
||||
// Save resume data to file
|
||||
|
|
@ -268,6 +264,17 @@ public class HlsDownloader{
|
|||
};
|
||||
|
||||
if (!QueueManager.Instance.Queue.Contains(_currentEpMeta)){
|
||||
if (!_currentEpMeta.DownloadProgress.Done){
|
||||
foreach (var downloadItemDownloadedFile in _currentEpMeta.downloadedFiles){
|
||||
try{
|
||||
if (File.Exists(downloadItemDownloadedFile)){
|
||||
File.Delete(downloadItemDownloadedFile);
|
||||
}
|
||||
} catch (Exception e){
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (Ok: false, _data.Parts);
|
||||
}
|
||||
|
||||
|
|
@ -285,7 +292,7 @@ public class HlsDownloader{
|
|||
return (Ok: true, _data.Parts);
|
||||
}
|
||||
|
||||
public static Info GetDownloadInfo(long dateStartUnix, int partsDownloaded, int partsTotal, long downloadedBytes,long totalDownloadedBytes){
|
||||
public static Info GetDownloadInfo(long dateStartUnix, int partsDownloaded, int partsTotal, long downloadedBytes, long totalDownloadedBytes){
|
||||
// Convert Unix timestamp to DateTime
|
||||
DateTime dateStart = DateTimeOffset.FromUnixTimeMilliseconds(dateStartUnix).UtcDateTime;
|
||||
double dateElapsed = (DateTime.UtcNow - dateStart).TotalMilliseconds;
|
||||
|
|
@ -293,15 +300,12 @@ public class HlsDownloader{
|
|||
// Calculate percentage
|
||||
int percentFixed = (int)((double)partsDownloaded / partsTotal * 100);
|
||||
int percent = percentFixed < 100 ? percentFixed : (partsTotal == partsDownloaded ? 100 : 99);
|
||||
|
||||
// Calculate download speed (bytes per second)
|
||||
|
||||
double downloadSpeed = downloadedBytes / (dateElapsed / 1000);
|
||||
|
||||
// Calculate remaining time estimate
|
||||
// double remainingTime = dateElapsed * (partsTotal / (double)partsDownloaded - 1);
|
||||
int partsLeft = partsTotal - partsDownloaded;
|
||||
double remainingTime = (partsLeft * (totalDownloadedBytes / partsDownloaded)) / downloadSpeed;
|
||||
|
||||
|
||||
return new Info{
|
||||
Percent = percent,
|
||||
Time = remainingTime,
|
||||
|
|
@ -586,32 +590,6 @@ public class Data{
|
|||
public long TotalBytes{ get; set; }
|
||||
}
|
||||
|
||||
public class ProgressData{
|
||||
public int Total{ get; set; }
|
||||
|
||||
public int Cur{ get; set; }
|
||||
|
||||
// Considering the dual type in TypeScript (number|string), you might opt for string in C# to accommodate both numeric and text representations.
|
||||
// Alternatively, you could use a custom setter to handle numeric inputs as strings, or define two separate properties if the usage context is clear.
|
||||
public string? Percent{ get; set; }
|
||||
public double Time{ get; set; } // Assuming this represents a duration or timestamp, you might consider TimeSpan or DateTime based on context.
|
||||
public double DownloadSpeed{ get; set; }
|
||||
public long Bytes{ get; set; }
|
||||
}
|
||||
|
||||
public class DownloadInfo{
|
||||
public string? Image{ get; set; }
|
||||
|
||||
public Parent? Parent{ get; set; }
|
||||
public string? Title{ get; set; }
|
||||
public LanguageItem? Language{ get; set; }
|
||||
public string? FileName{ get; set; }
|
||||
}
|
||||
|
||||
public class Parent{
|
||||
public string? Title{ get; set; }
|
||||
}
|
||||
|
||||
public class PartsData{
|
||||
public int First{ get; set; }
|
||||
public int Total{ get; set; }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
|
||||
namespace CRD.Utils.HLS;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +1,23 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.Imaging;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Downloader;
|
||||
using CRD.Utils.Ffmpeg_Encoding;
|
||||
using CRD.Utils.JsonConv;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.Crunchyroll.Music;
|
||||
using Microsoft.Win32;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CRD.Utils;
|
||||
|
|
@ -28,8 +31,9 @@ public class Helpers{
|
|||
return JsonConvert.DeserializeObject<T>(json, serializerSettings);
|
||||
} catch (JsonException ex){
|
||||
Console.Error.WriteLine($"Error deserializing JSON: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public static string ConvertTimeFormat(string time){
|
||||
|
|
@ -67,6 +71,8 @@ public class Helpers{
|
|||
}
|
||||
|
||||
public static void EnsureDirectoriesExist(string path){
|
||||
Console.WriteLine($"Check if path exists: {path}");
|
||||
|
||||
// Check if the path is absolute
|
||||
bool isAbsolute = Path.IsPathRooted(path);
|
||||
|
||||
|
|
@ -82,13 +88,17 @@ public class Helpers{
|
|||
string cumulativePath = isAbsolute ? Path.GetPathRoot(directoryPath) : Environment.CurrentDirectory;
|
||||
|
||||
// Get all directory parts
|
||||
string[] directories = directoryPath.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
||||
string[] directories = directoryPath.Split(Path.DirectorySeparatorChar);
|
||||
|
||||
// Start the loop from the correct initial index
|
||||
int startIndex = isAbsolute && directories.Length > 0 && string.IsNullOrEmpty(directories[0]) ? 2 : 0;
|
||||
int startIndex = isAbsolute && directories.Length > 0 && string.IsNullOrEmpty(directories[0]) ? 1 : 0;
|
||||
|
||||
if (isAbsolute && cumulativePath == "/"){
|
||||
cumulativePath = "/";
|
||||
}
|
||||
|
||||
for (int i = startIndex; i < directories.Length; i++){
|
||||
// Skip empty parts (which can occur with UNC paths)
|
||||
// Skip empty parts
|
||||
if (string.IsNullOrEmpty(directories[i])){
|
||||
continue;
|
||||
}
|
||||
|
|
@ -104,6 +114,7 @@ public class Helpers{
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public static bool IsValidPath(string path){
|
||||
char[] invalidChars = Path.GetInvalidPathChars();
|
||||
|
||||
|
|
@ -290,7 +301,16 @@ public class Helpers{
|
|||
}
|
||||
}
|
||||
|
||||
public static async Task<(bool IsOk, int ErrorCode)> RunFFmpegWithPresetAsync(string inputFilePath, VideoPreset preset){
|
||||
private static string GetQualityOption(VideoPreset preset){
|
||||
return preset.Codec switch{
|
||||
"h264_nvenc" or "hevc_nvenc" => $"-cq {preset.Crf}", // For NVENC
|
||||
"h264_qsv" or "hevc_qsv" => $"-global_quality {preset.Crf}", // For Intel QSV
|
||||
"h264_amf" or "hevc_amf" => $"-qp {preset.Crf}", // For AMD VCE
|
||||
_ => $"-crf {preset.Crf}", // For software codecs like libx264/libx265
|
||||
};
|
||||
}
|
||||
|
||||
public static async Task<(bool IsOk, int ErrorCode)> RunFFmpegWithPresetAsync(string inputFilePath, VideoPreset preset, CrunchyEpMeta? data = null){
|
||||
try{
|
||||
string outputExtension = Path.GetExtension(inputFilePath);
|
||||
string directory = Path.GetDirectoryName(inputFilePath);
|
||||
|
|
@ -298,18 +318,17 @@ public class Helpers{
|
|||
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
|
||||
string qualityOption = GetQualityOption(preset);
|
||||
|
||||
TimeSpan? totalDuration = await GetMediaDurationAsync(CfgManager.PathFFMPEG, inputFilePath);
|
||||
if (totalDuration == null){
|
||||
Console.Error.WriteLine("Unable to retrieve input file duration.");
|
||||
} else{
|
||||
qualityOption = $"-crf {preset.Crf}"; // For software codecs like libx264/libx265
|
||||
Console.WriteLine($"Total Duration: {totalDuration}");
|
||||
}
|
||||
|
||||
string ffmpegCommand = $"-loglevel warning -i \"{inputFilePath}\" -c:v {preset.Codec} {qualityOption} -vf \"scale={preset.Resolution},fps={preset.FrameRate}\" {additionalParams} \"{tempOutputFilePath}\"";
|
||||
|
||||
string ffmpegCommand = $"-loglevel info -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;
|
||||
|
|
@ -327,6 +346,9 @@ public class Helpers{
|
|||
process.ErrorDataReceived += (sender, e) => {
|
||||
if (!string.IsNullOrEmpty(e.Data)){
|
||||
Console.Error.WriteLine($"{e.Data}");
|
||||
if (data != null && totalDuration != null){
|
||||
ParseProgress(e.Data, totalDuration.Value, data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -358,6 +380,72 @@ public class Helpers{
|
|||
}
|
||||
}
|
||||
|
||||
private static void ParseProgress(string progressString, TimeSpan totalDuration, CrunchyEpMeta data){
|
||||
try{
|
||||
if (progressString.Contains("time=")){
|
||||
var timeIndex = progressString.IndexOf("time=") + 5;
|
||||
var timeString = progressString.Substring(timeIndex, 11);
|
||||
|
||||
|
||||
if (TimeSpan.TryParse(timeString, out var currentTime)){
|
||||
int progress = (int)(currentTime.TotalSeconds / totalDuration.TotalSeconds * 100);
|
||||
Console.WriteLine($"Progress: {progress:F2}%");
|
||||
|
||||
data.DownloadProgress = new DownloadProgress(){
|
||||
IsDownloading = true,
|
||||
Percent = progress,
|
||||
Time = 0,
|
||||
DownloadSpeed = 0,
|
||||
Doing = "Encoding"
|
||||
};
|
||||
|
||||
QueueManager.Instance.Queue.Refresh();
|
||||
}
|
||||
}
|
||||
} catch (Exception e){
|
||||
Console.Error.WriteLine("Failed to calculate encoding progess");
|
||||
Console.Error.WriteLine(e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<TimeSpan?> GetMediaDurationAsync(string ffmpegPath, string inputFilePath){
|
||||
try{
|
||||
using (var process = new Process()){
|
||||
process.StartInfo.FileName = ffmpegPath;
|
||||
process.StartInfo.Arguments = $"-i \"{inputFilePath}\"";
|
||||
process.StartInfo.UseShellExecute = false;
|
||||
process.StartInfo.RedirectStandardError = true;
|
||||
process.StartInfo.CreateNoWindow = true;
|
||||
|
||||
string output = string.Empty;
|
||||
process.ErrorDataReceived += (sender, e) => {
|
||||
if (!string.IsNullOrEmpty(e.Data)){
|
||||
output += e.Data + Environment.NewLine;
|
||||
}
|
||||
};
|
||||
|
||||
process.Start();
|
||||
process.BeginErrorReadLine();
|
||||
|
||||
await process.WaitForExitAsync();
|
||||
|
||||
Regex regex = new Regex(@"Duration: (\d{2}):(\d{2}):(\d{2}\.\d{2})");
|
||||
Match match = regex.Match(output);
|
||||
if (match.Success){
|
||||
int hours = int.Parse(match.Groups[1].Value);
|
||||
int minutes = int.Parse(match.Groups[2].Value);
|
||||
double seconds = double.Parse(match.Groups[3].Value, CultureInfo.InvariantCulture);
|
||||
|
||||
return new TimeSpan(hours, minutes, (int)seconds);
|
||||
}
|
||||
}
|
||||
} catch (Exception ex){
|
||||
Console.Error.WriteLine($"An error occurred while retrieving media duration: {ex.Message}");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static double CalculateCosineSimilarity(string text1, string text2){
|
||||
var vector1 = ComputeWordFrequency(text1);
|
||||
var vector2 = ComputeWordFrequency(text2);
|
||||
|
|
@ -438,26 +526,28 @@ public class Helpers{
|
|||
}
|
||||
|
||||
|
||||
public static async Task<Bitmap?> LoadImage(string imageUrl,int desiredWidth = 0,int desiredHeight = 0){
|
||||
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()){
|
||||
|
||||
var bitmap = new Bitmap(stream);
|
||||
var response = await HttpClientReq.Instance.GetHttpClient().GetAsync(imageUrl);
|
||||
|
||||
if (desiredWidth != 0 && desiredHeight != 0){
|
||||
var scaledBitmap = bitmap.CreateScaledBitmap(new PixelSize(desiredWidth, desiredHeight));
|
||||
if (ChallengeDetector.IsClearanceRequired(response)){
|
||||
Console.Error.WriteLine($"Cloudflare Challenge detected ");
|
||||
}
|
||||
|
||||
bitmap.Dispose();
|
||||
|
||||
return scaledBitmap;
|
||||
}
|
||||
|
||||
|
||||
return bitmap;
|
||||
response.EnsureSuccessStatusCode();
|
||||
using (var stream = await response.Content.ReadAsStreamAsync()){
|
||||
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){
|
||||
Console.Error.WriteLine("Failed to load image: " + ex.Message);
|
||||
|
|
@ -513,4 +603,120 @@ public class Helpers{
|
|||
string uuid = Guid.NewGuid().ToString();
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public static string LimitFileNameLength(string fileName, int maxFileNameLength){
|
||||
string directory = Path.GetDirectoryName(fileName) ?? string.Empty;
|
||||
string name = Path.GetFileNameWithoutExtension(fileName);
|
||||
string extension = Path.GetExtension(fileName);
|
||||
|
||||
if (name.Length > maxFileNameLength - extension.Length){
|
||||
name = name.Substring(0, maxFileNameLength - extension.Length);
|
||||
}
|
||||
|
||||
return Path.Combine(directory, name + extension);
|
||||
}
|
||||
|
||||
public static string AddUncPrefixIfNeeded(string path){
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !IsLongPathEnabled()){
|
||||
if (!string.IsNullOrEmpty(path) && !path.StartsWith(@"\\?\")){
|
||||
return $@"\\?\{Path.GetFullPath(path)}";
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
private static bool IsLongPathEnabled(){
|
||||
try{
|
||||
using (var key = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Control\FileSystem")){
|
||||
if (key != null){
|
||||
var value = key.GetValue("LongPathsEnabled", 0);
|
||||
return value is int intValue && intValue == 1;
|
||||
}
|
||||
}
|
||||
} catch (Exception ex){
|
||||
Console.Error.WriteLine($"Failed to check if long paths are enabled: {ex.Message}");
|
||||
}
|
||||
|
||||
return false; // Default to false if unable to read the registry
|
||||
}
|
||||
|
||||
|
||||
private static Avalonia.Controls.Image? _backgroundImageLayer;
|
||||
|
||||
public static void SetBackgroundImage(string backgroundImagePath, double? imageOpacity = 0.5, double? blurRadius = 10){
|
||||
try{
|
||||
var activeWindow = GetActiveWindow();
|
||||
if (activeWindow == null)
|
||||
return;
|
||||
|
||||
|
||||
if (activeWindow.Content is not Panel rootPanel){
|
||||
rootPanel = new Grid();
|
||||
activeWindow.Content = rootPanel;
|
||||
}
|
||||
|
||||
|
||||
if (string.IsNullOrEmpty(backgroundImagePath)){
|
||||
if (_backgroundImageLayer != null){
|
||||
rootPanel.Children.Remove(_backgroundImageLayer);
|
||||
_backgroundImageLayer = null;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_backgroundImageLayer == null){
|
||||
_backgroundImageLayer = new Avalonia.Controls.Image{
|
||||
Stretch = Stretch.UniformToFill,
|
||||
ZIndex = -1,
|
||||
};
|
||||
rootPanel.Children.Add(_backgroundImageLayer);
|
||||
}
|
||||
|
||||
_backgroundImageLayer.Source = new Bitmap(backgroundImagePath);
|
||||
_backgroundImageLayer.Opacity = imageOpacity ?? 0.5;
|
||||
|
||||
_backgroundImageLayer.Effect = new BlurEffect{
|
||||
Radius = blurRadius ?? 10
|
||||
};
|
||||
} catch (Exception ex){
|
||||
Console.WriteLine($"Failed to set background image: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static Window? GetActiveWindow(){
|
||||
// Ensure the application is running with a Classic Desktop Lifetime
|
||||
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime){
|
||||
// Return the first active window found in the desktop application's window list
|
||||
return desktopLifetime.Windows.FirstOrDefault(window => window.IsActive);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static bool IsInstalled(string checkFor, string versionString){
|
||||
try{
|
||||
// Create a new process for mkvmerge
|
||||
Process process = new Process();
|
||||
process.StartInfo.FileName = checkFor;
|
||||
process.StartInfo.Arguments = versionString; // A harmless command to check if mkvmerge is available
|
||||
process.StartInfo.RedirectStandardOutput = true;
|
||||
process.StartInfo.RedirectStandardError = true;
|
||||
process.StartInfo.UseShellExecute = false;
|
||||
process.StartInfo.CreateNoWindow = true;
|
||||
|
||||
// Start the process and wait for it to exit
|
||||
process.Start();
|
||||
process.WaitForExit();
|
||||
|
||||
// If the exit code is 0, mkvmerge was found and executed successfully
|
||||
return process.ExitCode == 0;
|
||||
} catch (Exception){
|
||||
// If an exception is caught, mkvmerge is not installed or accessible
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
54
CRD/Utils/Http/ChallengeDetector.cs
Normal file
54
CRD/Utils/Http/ChallengeDetector.cs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace CRD.Utils;
|
||||
|
||||
public class ChallengeDetector{
|
||||
private static readonly HashSet<string> CloudflareServerNames = new HashSet<string>{
|
||||
"cloudflare",
|
||||
"cloudflare-nginx",
|
||||
"ddos-guard"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Checks if clearance is required.
|
||||
/// </summary>
|
||||
/// <param name="response">The HttpResponseMessage to check.</param>
|
||||
/// <returns>True if the site requires clearance</returns>
|
||||
public static bool IsClearanceRequired(HttpResponseMessage response) => IsCloudflareProtected(response);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the site is protected by Cloudflare
|
||||
/// </summary>
|
||||
/// <param name="response">The HttpResponseMessage to check.</param>
|
||||
/// <returns>True if the site is protected</returns>
|
||||
private static bool IsCloudflareProtected(HttpResponseMessage response){
|
||||
// check response headers
|
||||
if (!response.Headers.Server.Any(i =>
|
||||
i.Product != null && CloudflareServerNames.Contains(i.Product.Name.ToLower())))
|
||||
return false;
|
||||
|
||||
// detect CloudFlare and DDoS-GUARD
|
||||
if (response.StatusCode.Equals(HttpStatusCode.ServiceUnavailable) ||
|
||||
response.StatusCode.Equals(HttpStatusCode.Forbidden)){
|
||||
var responseHtml = response.Content.ReadAsStringAsync().Result;
|
||||
if (responseHtml.Contains("<title>Just a moment...</title>") || // Cloudflare
|
||||
responseHtml.Contains("<title>Access denied</title>") || // Cloudflare Blocked
|
||||
responseHtml.Contains("<title>Attention Required! | Cloudflare</title>") || // Cloudflare Blocked
|
||||
responseHtml.Trim().Equals("error code: 1020") || // Cloudflare Blocked
|
||||
responseHtml.IndexOf("<title>DDOS-GUARD</title>", StringComparison.OrdinalIgnoreCase) > -1) // DDOS-GUARD
|
||||
return true;
|
||||
}
|
||||
|
||||
// detect Custom CloudFlare for EbookParadijs, Film-Paleis, MuziekFabriek and Puur-Hollands
|
||||
if (response.Headers.Vary.ToString() == "Accept-Encoding,User-Agent" &&
|
||||
response.Content.Headers.ContentEncoding.ToString() == "" &&
|
||||
response.Content.ReadAsStringAsync().Result.ToLower().Contains("ddos"))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,11 +3,11 @@ using System.Net;
|
|||
using System.Net.Http;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
|
||||
namespace CRD.Utils;
|
||||
|
|
@ -40,7 +40,6 @@ public class HttpClientReq{
|
|||
|
||||
private HttpClientHandler handler;
|
||||
|
||||
|
||||
public HttpClientReq(){
|
||||
cookieStore = new Dictionary<string, CookieCollection>();
|
||||
|
||||
|
|
@ -64,7 +63,7 @@ public class HttpClientReq{
|
|||
Console.Error.WriteLine("No proxy will be used.");
|
||||
handler = CreateHandler(false);
|
||||
}
|
||||
|
||||
|
||||
client = new HttpClient(handler);
|
||||
} else{
|
||||
Console.Error.WriteLine("No proxy is being used.");
|
||||
|
|
@ -74,10 +73,19 @@ public class HttpClientReq{
|
|||
Console.Error.WriteLine("No proxy is being used.");
|
||||
client = new HttpClient(CreateHttpClientHandler());
|
||||
}
|
||||
|
||||
client.Timeout = TimeSpan.FromSeconds(100);
|
||||
|
||||
// client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0");
|
||||
client.DefaultRequestHeaders.UserAgent.ParseAdd("Crunchyroll/1.9.0 Nintendo Switch/18.1.0.0 UE4/4.27");
|
||||
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");
|
||||
// client.DefaultRequestHeaders.UserAgent.ParseAdd("Crunchyroll/3.60.0 Android/9 okhttp/4.12.0");
|
||||
|
||||
client.DefaultRequestHeaders.Accept.ParseAdd("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
|
||||
client.DefaultRequestHeaders.AcceptEncoding.ParseAdd("gzip, deflate, br");
|
||||
client.DefaultRequestHeaders.AcceptLanguage.ParseAdd("en-US,en;q=0.5");
|
||||
client.DefaultRequestHeaders.Connection.ParseAdd("keep-alive");
|
||||
|
||||
}
|
||||
|
||||
private HttpMessageHandler CreateHttpClientHandler(){
|
||||
|
|
@ -132,8 +140,8 @@ public class HttpClientReq{
|
|||
// handler.CookieContainer.Add(cookie);
|
||||
// handler.CookieContainer.Add(cookie2);
|
||||
|
||||
AddCookie("crunchyroll.com", new Cookie("etp_rt", refreshToken));
|
||||
AddCookie("crunchyroll.com", new Cookie("c_locale", "en-US"));
|
||||
AddCookie(".crunchyroll.com", new Cookie("etp_rt", refreshToken));
|
||||
AddCookie(".crunchyroll.com", new Cookie("c_locale", "en-US"));
|
||||
}
|
||||
|
||||
private void AddCookie(string domain, Cookie cookie){
|
||||
|
|
@ -141,16 +149,26 @@ public class HttpClientReq{
|
|||
cookieStore[domain] = new CookieCollection();
|
||||
}
|
||||
|
||||
var existingCookie = cookieStore[domain].FirstOrDefault(c => c.Name == cookie.Name);
|
||||
|
||||
if (existingCookie != null){
|
||||
cookieStore[domain].Remove(existingCookie);
|
||||
}
|
||||
|
||||
cookieStore[domain].Add(cookie);
|
||||
}
|
||||
|
||||
public async Task<(bool IsOk, string ResponseContent)> SendHttpRequest(HttpRequestMessage request){
|
||||
public async Task<(bool IsOk, string ResponseContent)> SendHttpRequest(HttpRequestMessage request, bool suppressError = false){
|
||||
string content = string.Empty;
|
||||
try{
|
||||
AttachCookies(request);
|
||||
|
||||
HttpResponseMessage response = await client.SendAsync(request);
|
||||
|
||||
if (ChallengeDetector.IsClearanceRequired(response)){
|
||||
Console.Error.WriteLine($" Cloudflare Challenge detected");
|
||||
}
|
||||
|
||||
content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
|
@ -158,28 +176,40 @@ public class HttpClientReq{
|
|||
return (IsOk: true, ResponseContent: content);
|
||||
} catch (Exception e){
|
||||
// Console.Error.WriteLine($"Error: {e} \n Response: {(content.Length < 500 ? content : "error to long")}");
|
||||
Console.Error.WriteLine($"Error: {e} \n Response: {content}");
|
||||
if (!suppressError){
|
||||
Console.Error.WriteLine($"Error: {e} \n Response: {(content.Length < 500 ? content : "error to long")}");
|
||||
}
|
||||
|
||||
return (IsOk: false, ResponseContent: content);
|
||||
}
|
||||
}
|
||||
|
||||
private void AttachCookies(HttpRequestMessage request){
|
||||
if (cookieStore.TryGetValue(request.RequestUri.Host, out CookieCollection cookies)){
|
||||
var cookieHeader = new StringBuilder();
|
||||
foreach (Cookie cookie in cookies){
|
||||
var cookieHeader = new StringBuilder();
|
||||
|
||||
if (request.Headers.TryGetValues("Cookie", out var existingCookies)){
|
||||
cookieHeader.Append(string.Join("; ", existingCookies));
|
||||
}
|
||||
|
||||
foreach (var cookie in cookieStore.SelectMany(keyValuePair => keyValuePair.Value)){
|
||||
string cookieString = $"{cookie.Name}={cookie.Value}";
|
||||
|
||||
if (!cookieHeader.ToString().Contains(cookieString)){
|
||||
if (cookieHeader.Length > 0){
|
||||
cookieHeader.Append("; ");
|
||||
}
|
||||
|
||||
cookieHeader.Append($"{cookie.Name}={cookie.Value}");
|
||||
}
|
||||
|
||||
if (cookieHeader.Length > 0){
|
||||
request.Headers.Add("Cookie", cookieHeader.ToString());
|
||||
cookieHeader.Append(cookieString);
|
||||
}
|
||||
}
|
||||
|
||||
if (cookieHeader.Length > 0){
|
||||
request.Headers.Remove("Cookie");
|
||||
request.Headers.Add("Cookie", cookieHeader.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static HttpRequestMessage CreateRequestMessage(string uri, HttpMethod requestMethod, bool authHeader, bool disableDrmHeader, NameValueCollection? query){
|
||||
UriBuilder uriBuilder = new UriBuilder(uri);
|
||||
|
||||
|
|
@ -210,10 +240,12 @@ public class HttpClientReq{
|
|||
}
|
||||
}
|
||||
|
||||
public static class Api{
|
||||
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 BetaAuth = ApiBeta + "/auth/v1/token";
|
||||
public static readonly string BetaProfile = ApiBeta + "/accounts/v1/me/profile";
|
||||
public static readonly string BetaCmsToken = ApiBeta + "/index/v2";
|
||||
|
|
@ -228,8 +260,10 @@ public static class Api{
|
|||
public static readonly string Subscription = ApiBeta + "/subs/v3/subscriptions/";
|
||||
public static readonly string CmsN = ApiN + "/content/v2/cms";
|
||||
|
||||
public static readonly string authBasic = "Basic bm9haWhkZXZtXzZpeWcwYThsMHE6";
|
||||
|
||||
public static readonly string authBasic = "bm9haWhkZXZtXzZpeWcwYThsMHE6";
|
||||
public static readonly string authBasicMob = "bm12anNoZmtueW14eGtnN2ZiaDk6WllJVnJCV1VQYmNYRHRiRDIyVlNMYTZiNFdRb3Mzelg=";
|
||||
public static readonly string authBasicSwitch = "dC1rZGdwMmg4YzNqdWI4Zm4wZnE6eWZMRGZNZnJZdktYaDRKWFMxTEVJMmNDcXUxdjVXYW4=";
|
||||
public static readonly string authBasicMob = "Basic dXU4aG0wb2g4dHFpOWV0eXl2aGo6SDA2VnVjRnZUaDJ1dEYxM0FBS3lLNE85UTRhX3BlX1o=";
|
||||
public static readonly string authBasicSwitch = "Basic dC1rZGdwMmg4YzNqdWI4Zm4wZnE6eWZMRGZNZnJZdktYaDRKWFMxTEVJMmNDcXUxdjVXYW4=";
|
||||
|
||||
public static readonly string ChromeUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36";
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using CRD.Utils.Structs;
|
||||
|
||||
namespace CRD.Utils.Muxing;
|
||||
|
|
@ -54,6 +55,53 @@ public class FontsManager{
|
|||
|
||||
public string root = "https://static.crunchyroll.com/vilos-v2/web/vilos/assets/libass-fonts/";
|
||||
|
||||
|
||||
public async Task GetFontsAsync(){
|
||||
Console.WriteLine("Downloading fonts...");
|
||||
var fonts = Fonts.Values.SelectMany(f => f).ToList();
|
||||
|
||||
foreach (var font in fonts){
|
||||
var fontLoc = Path.Combine(CfgManager.PathFONTS_DIR, font);
|
||||
|
||||
if (File.Exists(fontLoc) && new FileInfo(fontLoc).Length != 0){
|
||||
Console.WriteLine($"{font} already downloaded!");
|
||||
} else{
|
||||
var fontFolder = Path.GetDirectoryName(fontLoc);
|
||||
if (File.Exists(fontLoc) && new FileInfo(fontLoc).Length == 0){
|
||||
File.Delete(fontLoc);
|
||||
}
|
||||
|
||||
try{
|
||||
if (!Directory.Exists(fontFolder)){
|
||||
Directory.CreateDirectory(fontFolder);
|
||||
}
|
||||
} catch (Exception e){
|
||||
Console.WriteLine($"Failed to create directory: {e.Message}");
|
||||
}
|
||||
|
||||
var fontUrl = root + font;
|
||||
|
||||
using (var httpClient = HttpClientReq.Instance.GetHttpClient()){
|
||||
try{
|
||||
var response = await httpClient.GetAsync(fontUrl);
|
||||
if (response.IsSuccessStatusCode){
|
||||
var fontData = await response.Content.ReadAsByteArrayAsync();
|
||||
await File.WriteAllBytesAsync(fontLoc, fontData);
|
||||
Console.WriteLine($"Downloaded: {font}");
|
||||
} else{
|
||||
Console.Error.WriteLine($"Failed to download: {font}");
|
||||
}
|
||||
} catch (Exception e){
|
||||
Console.Error.WriteLine($"Error downloading {font}: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("All required fonts downloaded!");
|
||||
}
|
||||
|
||||
|
||||
public static List<string> ExtractFontsFromAss(string ass){
|
||||
var lines = ass.Replace("\r", "").Split('\n');
|
||||
var styles = new List<string>();
|
||||
|
|
@ -76,7 +124,6 @@ public class FontsManager{
|
|||
}
|
||||
|
||||
public Dictionary<string, List<string>> GetDictFromKeyList(List<string> keysList){
|
||||
|
||||
Dictionary<string, List<string>> filteredDictionary = new Dictionary<string, List<string>>();
|
||||
|
||||
foreach (string key in keysList){
|
||||
|
|
@ -86,9 +133,8 @@ public class FontsManager{
|
|||
}
|
||||
|
||||
return filteredDictionary;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static string GetFontMimeType(string fontFile){
|
||||
if (Regex.IsMatch(fontFile, @"\.otf$"))
|
||||
|
|
@ -99,7 +145,7 @@ public class FontsManager{
|
|||
return "application/octet-stream";
|
||||
}
|
||||
|
||||
public List<ParsedFont> MakeFontsList(string fontsDir, List<SubtitleFonts> subs){
|
||||
public List<ParsedFont> MakeFontsList(string fontsDir, List<SubtitleFonts> subs){
|
||||
Dictionary<string, List<string>> fontsNameList = new Dictionary<string, List<string>>();
|
||||
List<string> subsList = new List<string>();
|
||||
List<ParsedFont> fontsList = new List<ParsedFont>();
|
||||
|
|
@ -108,12 +154,13 @@ public class FontsManager{
|
|||
foreach (var s in subs){
|
||||
foreach (var keyValuePair in s.Fonts){
|
||||
if (!fontsNameList.ContainsKey(keyValuePair.Key)){
|
||||
fontsNameList.Add(keyValuePair.Key,keyValuePair.Value);
|
||||
fontsNameList.Add(keyValuePair.Key, keyValuePair.Value);
|
||||
}
|
||||
}
|
||||
|
||||
subsList.Add(s.Language.Locale);
|
||||
}
|
||||
|
||||
|
||||
if (subsList.Count > 0){
|
||||
Console.WriteLine("\nSubtitles: {0} (Total: {1})", string.Join(", ", subsList), subsList.Count);
|
||||
isNstr = false;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
|
@ -9,7 +8,6 @@ using System.Threading.Tasks;
|
|||
using System.Xml;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils.Structs;
|
||||
using DynamicData;
|
||||
|
||||
namespace CRD.Utils.Muxing;
|
||||
|
||||
|
|
@ -37,7 +35,7 @@ public class Merger{
|
|||
var hasVideo = false;
|
||||
|
||||
args.Add("-loglevel warning");
|
||||
|
||||
|
||||
if (!options.mp3){
|
||||
foreach (var vid in options.OnlyVid){
|
||||
if (!hasVideo || options.KeepAllVideos == true){
|
||||
|
|
@ -76,6 +74,8 @@ public class Merger{
|
|||
index++;
|
||||
}
|
||||
|
||||
bool hasSignsSub = options.Subtitles.Any(sub => sub.Signs && options.Defaults.Sub.Code == sub.Language.Code);
|
||||
|
||||
foreach (var sub in options.Subtitles.Select((value, i) => new{ value, i })){
|
||||
if (sub.value.Delay != null && sub.value.Delay != 0){
|
||||
double delay = sub.value.Delay / 1000.0 ?? 0;
|
||||
|
|
@ -84,7 +84,9 @@ 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 && sub.value.ClosedCaption == false){
|
||||
if (options.Defaults.Sub.Code == sub.value.Language.Code &&
|
||||
(CrunchyrollManager.Instance.CrunOptions.DefaultSubSigns == sub.value.Signs || CrunchyrollManager.Instance.CrunOptions.DefaultSubSigns && !hasSignsSub)
|
||||
&& sub.value.ClosedCaption == false){
|
||||
metaData.Add($"-disposition:s:{sub.i} default");
|
||||
} else{
|
||||
metaData.Add($"-disposition:s:{sub.i} 0");
|
||||
|
|
@ -146,7 +148,7 @@ public class Merger{
|
|||
|
||||
bool hasVideo = false;
|
||||
|
||||
args.Add($"-o \"{options.Output}\"");
|
||||
args.Add($"-o \"{Helpers.AddUncPrefixIfNeeded(options.Output)}\"");
|
||||
if (options.Options.mkvmerge != null){
|
||||
args.AddRange(options.Options.mkvmerge);
|
||||
}
|
||||
|
|
@ -162,11 +164,15 @@ public class Merger{
|
|||
args.Add($"--language 0:{vid.Language.Code}");
|
||||
|
||||
hasVideo = true;
|
||||
args.Add($"\"{vid.Path}\"");
|
||||
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)
|
||||
.ToList();
|
||||
|
||||
foreach (var aud in options.OnlyAudio){
|
||||
foreach (var aud in sortedAudio){
|
||||
string trackName = aud.Language.Name;
|
||||
args.Add("--audio-tracks 0");
|
||||
args.Add("--no-video");
|
||||
|
|
@ -184,11 +190,19 @@ public class Merger{
|
|||
args.Add($"--sync 0:{aud.Delay}");
|
||||
}
|
||||
|
||||
args.Add($"\"{aud.Path}\"");
|
||||
args.Add($"\"{Helpers.AddUncPrefixIfNeeded(aud.Path)}\"");
|
||||
}
|
||||
|
||||
if (options.Subtitles.Count > 0){
|
||||
foreach (var subObj in options.Subtitles){
|
||||
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)
|
||||
.ToList();
|
||||
|
||||
foreach (var subObj in sortedSubtitles){
|
||||
bool isForced = false;
|
||||
if (subObj.Delay.HasValue){
|
||||
double delay = subObj.Delay ?? 0;
|
||||
|
|
@ -202,7 +216,8 @@ public class Merger{
|
|||
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 && subObj.ClosedCaption == false){
|
||||
if (options.Defaults.Sub.Code == subObj.Language.Code &&
|
||||
(CrunchyrollManager.Instance.CrunOptions.DefaultSubSigns == subObj.Signs || CrunchyrollManager.Instance.CrunOptions.DefaultSubSigns && !hasSignsSub) && subObj.ClosedCaption == false){
|
||||
args.Add("--default-track 0");
|
||||
if (CrunchyrollManager.Instance.CrunOptions.DefaultSubForcedDisplay){
|
||||
args.Add("--forced-track 0:yes");
|
||||
|
|
@ -211,16 +226,16 @@ public class Merger{
|
|||
} 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){
|
||||
if (subObj.Signs && CrunchyrollManager.Instance.CrunOptions.SignsSubsAsForced && !isForced){
|
||||
args.Add("--forced-track 0:yes");
|
||||
}
|
||||
|
||||
args.Add($"\"{subObj.File}\"");
|
||||
args.Add($"\"{Helpers.AddUncPrefixIfNeeded(subObj.File)}\"");
|
||||
}
|
||||
} else{
|
||||
args.Add("--no-subtitles");
|
||||
|
|
@ -230,14 +245,14 @@ public class Merger{
|
|||
foreach (var font in options.Fonts){
|
||||
args.Add($"--attachment-name \"{font.Name}\"");
|
||||
args.Add($"--attachment-mime-type \"{font.Mime}\"");
|
||||
args.Add($"--attach-file \"{font.Path}\"");
|
||||
args.Add($"--attach-file \"{Helpers.AddUncPrefixIfNeeded(font.Path)}\"");
|
||||
}
|
||||
} else{
|
||||
args.Add("--no-attachments");
|
||||
}
|
||||
|
||||
if (options.Chapters is{ Count: > 0 }){
|
||||
args.Add($"--chapters \"{options.Chapters[0].Path}\"");
|
||||
args.Add($"--chapters \"{Helpers.AddUncPrefixIfNeeded(options.Chapters[0].Path)}\"");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(options.VideoTitle)){
|
||||
|
|
@ -245,55 +260,105 @@ public class Merger{
|
|||
}
|
||||
|
||||
if (options.Description is{ Count: > 0 }){
|
||||
args.Add($"--global-tags \"{options.Description[0].Path}\"");
|
||||
args.Add($"--global-tags \"{Helpers.AddUncPrefixIfNeeded(options.Description[0].Path)}\"");
|
||||
}
|
||||
|
||||
|
||||
return string.Join(" ", args);
|
||||
}
|
||||
|
||||
|
||||
public async Task<double> ProcessVideo(string baseVideoPath, string compareVideoPath){
|
||||
string baseFramesDir;
|
||||
string compareFramesDir;
|
||||
string baseFramesDir, baseFramesDirEnd;
|
||||
string compareFramesDir, compareFramesDirEnd;
|
||||
string cleanupDir;
|
||||
try{
|
||||
var tempDir = CfgManager.PathTEMP_DIR;
|
||||
baseFramesDir = Path.Combine(tempDir, "base_frames");
|
||||
compareFramesDir = Path.Combine(tempDir, "compare_frames");
|
||||
|
||||
string uuid = Guid.NewGuid().ToString();
|
||||
|
||||
cleanupDir = Path.Combine(tempDir, uuid);
|
||||
baseFramesDir = Path.Combine(tempDir, uuid, "base_frames_start");
|
||||
baseFramesDirEnd = Path.Combine(tempDir, uuid, "base_frames_end");
|
||||
compareFramesDir = Path.Combine(tempDir, uuid, "compare_frames_start");
|
||||
compareFramesDirEnd = Path.Combine(tempDir, uuid, "compare_frames_end");
|
||||
|
||||
Directory.CreateDirectory(baseFramesDir);
|
||||
Directory.CreateDirectory(baseFramesDirEnd);
|
||||
Directory.CreateDirectory(compareFramesDir);
|
||||
|
||||
Directory.CreateDirectory(compareFramesDirEnd);
|
||||
} 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);
|
||||
|
||||
if (!extractFramesBase.IsOk || !extractFramesCompare.IsOk){
|
||||
Console.Error.WriteLine("Failed to extract Frames to Compare");
|
||||
return 0;
|
||||
return -100;
|
||||
}
|
||||
|
||||
var baseFrames = Directory.GetFiles(baseFramesDir).Select(fp => new FrameData{
|
||||
FilePath = fp,
|
||||
Time = GetTimeFromFileName(fp, extractFramesBase.frameRate)
|
||||
}).ToList();
|
||||
try{
|
||||
var extractFramesBaseStart = await SyncingHelper.ExtractFrames(baseVideoPath, baseFramesDir, 0, 120);
|
||||
var extractFramesCompareStart = await SyncingHelper.ExtractFrames(compareVideoPath, compareFramesDir, 0, 120);
|
||||
|
||||
var compareFrames = Directory.GetFiles(compareFramesDir).Select(fp => new FrameData{
|
||||
FilePath = fp,
|
||||
Time = GetTimeFromFileName(fp, extractFramesBase.frameRate)
|
||||
}).ToList();
|
||||
TimeSpan? baseVideoDurationTimeSpan = await Helpers.GetMediaDurationAsync(CfgManager.PathFFMPEG, baseVideoPath);
|
||||
TimeSpan? compareVideoDurationTimeSpan = await Helpers.GetMediaDurationAsync(CfgManager.PathFFMPEG, compareVideoPath);
|
||||
|
||||
var offset = SyncingHelper.CalculateOffset(baseFrames, compareFrames);
|
||||
Console.WriteLine($"Calculated offset: {offset} seconds");
|
||||
if (baseVideoDurationTimeSpan == null || compareVideoDurationTimeSpan == null){
|
||||
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);
|
||||
|
||||
CleanupDirectory(baseFramesDir);
|
||||
CleanupDirectory(compareFramesDir);
|
||||
if (!extractFramesBaseStart.IsOk || !extractFramesCompareStart.IsOk || !extractFramesBaseEnd.IsOk || !extractFramesCompareEnd.IsOk){
|
||||
Console.Error.WriteLine("Failed to extract Frames to Compare");
|
||||
return -100;
|
||||
}
|
||||
|
||||
return offset;
|
||||
// Load frames from start of the videos
|
||||
var baseFramesStart = Directory.GetFiles(baseFramesDir).Select(fp => new FrameData{
|
||||
FilePath = fp,
|
||||
Time = GetTimeFromFileName(fp, extractFramesBaseStart.frameRate)
|
||||
}).ToList();
|
||||
|
||||
var compareFramesStart = Directory.GetFiles(compareFramesDir).Select(fp => new FrameData{
|
||||
FilePath = fp,
|
||||
Time = GetTimeFromFileName(fp, extractFramesCompareStart.frameRate)
|
||||
}).ToList();
|
||||
|
||||
// Load frames from end of the videos
|
||||
var baseFramesEnd = Directory.GetFiles(baseFramesDirEnd).Select(fp => new FrameData{
|
||||
FilePath = fp,
|
||||
Time = GetTimeFromFileName(fp, extractFramesBaseEnd.frameRate)
|
||||
}).ToList();
|
||||
|
||||
var compareFramesEnd = Directory.GetFiles(compareFramesDirEnd).Select(fp => new FrameData{
|
||||
FilePath = fp,
|
||||
Time = GetTimeFromFileName(fp, extractFramesCompareEnd.frameRate)
|
||||
}).ToList();
|
||||
|
||||
// Calculate offsets
|
||||
var startOffset = SyncingHelper.CalculateOffset(baseFramesStart, compareFramesStart);
|
||||
var endOffset = SyncingHelper.CalculateOffset(baseFramesEnd, compareFramesEnd,true);
|
||||
|
||||
var lengthDiff = Math.Abs(baseVideoDurationTimeSpan.Value.TotalMicroseconds - compareVideoDurationTimeSpan.Value.TotalMicroseconds) / 1000000;
|
||||
|
||||
endOffset += lengthDiff;
|
||||
|
||||
Console.WriteLine($"Start offset: {startOffset} seconds");
|
||||
Console.WriteLine($"End offset: {endOffset} seconds");
|
||||
|
||||
CleanupDirectory(cleanupDir);
|
||||
|
||||
var difference = Math.Abs(startOffset - endOffset);
|
||||
|
||||
switch (difference){
|
||||
case < 0.1:
|
||||
return startOffset;
|
||||
case > 1:
|
||||
return -100;
|
||||
default:
|
||||
return endOffset;
|
||||
}
|
||||
} catch (Exception e){
|
||||
Console.Error.WriteLine(e);
|
||||
return -100;
|
||||
}
|
||||
}
|
||||
|
||||
private static void CleanupDirectory(string dirPath){
|
||||
|
|
@ -365,8 +430,8 @@ public class MergerInput{
|
|||
public class SubtitleInput{
|
||||
public LanguageItem Language{ get; set; }
|
||||
public string File{ get; set; }
|
||||
public bool? ClosedCaption{ get; set; }
|
||||
public bool? Signs{ get; set; }
|
||||
public bool ClosedCaption{ get; set; }
|
||||
public bool Signs{ get; set; }
|
||||
public int? Delay{ get; set; }
|
||||
|
||||
public DownloadedMedia? RelatedVideoDownloadMedia;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ namespace CRD.Utils.Muxing;
|
|||
public class SyncingHelper{
|
||||
public static async Task<(bool IsOk, int ErrorCode, double frameRate)> ExtractFrames(string videoPath, string outputDir, double offset, double duration){
|
||||
var ffmpegPath = CfgManager.PathFFMPEG;
|
||||
var arguments = $"-i \"{videoPath}\" -vf \"select='gt(scene,0.1)',showinfo\" -vsync vfr -frame_pts true -t {duration} -ss {offset} \"{outputDir}\\frame%03d.png\"";
|
||||
var arguments = $"-i \"{videoPath}\" -vf \"select='gt(scene,0.1)',showinfo\" -fps_mode vfr -frame_pts true -t {duration} -ss {offset} \"{outputDir}\\frame%03d.png\"";
|
||||
|
||||
var output = "";
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ public class SyncingHelper{
|
|||
|
||||
process.ErrorDataReceived += (sender, e) => {
|
||||
if (!string.IsNullOrEmpty(e.Data)){
|
||||
Console.WriteLine($"{e.Data}");
|
||||
// Console.WriteLine($"{e.Data}");
|
||||
output += e.Data;
|
||||
}
|
||||
};
|
||||
|
|
@ -128,18 +128,35 @@ public class SyncingHelper{
|
|||
float[] pixels1 = ExtractPixels(image1, targetWidth, targetHeight);
|
||||
float[] pixels2 = ExtractPixels(image2, targetWidth, targetHeight);
|
||||
|
||||
// Check if any frame is completely black, if so, skip SSIM calculation
|
||||
if (IsBlackFrame(pixels1) || IsBlackFrame(pixels2)){
|
||||
// Return a negative value or zero to indicate no SSIM comparison for black frames.
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
// Compute SSIM
|
||||
return CalculateSSIM(pixels1, pixels2, targetWidth, targetHeight);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsBlackFrame(float[] pixels, float threshold = 1.0f){
|
||||
// Check if all pixel values are below the threshold, indicating a black frame.
|
||||
return pixels.All(p => p <= threshold);
|
||||
}
|
||||
|
||||
public static bool AreFramesSimilar(string imagePath1, string imagePath2, double ssimThreshold){
|
||||
double ssim = ComputeSSIM(imagePath1, imagePath2, 256, 256);
|
||||
// Console.WriteLine($"SSIM: {ssim}");
|
||||
return ssim > ssimThreshold;
|
||||
}
|
||||
|
||||
public static double CalculateOffset(List<FrameData> baseFrames, List<FrameData> compareFrames, double ssimThreshold = 0.9){
|
||||
public static double CalculateOffset(List<FrameData> baseFrames, List<FrameData> compareFrames,bool reverseCompare = false, double ssimThreshold = 0.9){
|
||||
|
||||
if (reverseCompare){
|
||||
baseFrames.Reverse();
|
||||
compareFrames.Reverse();
|
||||
}
|
||||
|
||||
foreach (var baseFrame in baseFrames){
|
||||
var matchingFrame = compareFrames.FirstOrDefault(f => AreFramesSimilar(baseFrame.FilePath, f.FilePath, ssimThreshold));
|
||||
if (matchingFrame != null){
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
using System.Xml;
|
||||
using CRD.Utils.Parser.Utils;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CRD.Utils.Parser;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using CRD.Downloader;
|
||||
using CRD.Utils.HLS;
|
||||
using CRD.Utils.Parser;
|
||||
using CRD.Utils.Parser.Utils;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
|||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using Avalonia.Logging;
|
||||
using CRD.Utils.Parser.Utils;
|
||||
|
||||
namespace CRD.Utils.Parser;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using CRD.Utils.Parser.Utils;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace CRD.Utils.Parser.Utils;
|
||||
namespace CRD.Utils.Parser.Utils;
|
||||
|
||||
public class ManifestInfo{
|
||||
public dynamic locations{ get; set; }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
|
||||
namespace CRD.Utils.Parser.Utils;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
using System.Text.RegularExpressions;
|
||||
using System;
|
||||
|
||||
namespace CRD.Utils.Parser.Utils;
|
||||
namespace CRD.Utils.Parser.Utils;
|
||||
|
||||
public class UrlResolver{
|
||||
|
||||
|
|
|
|||
|
|
@ -2,17 +2,14 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils.Sonarr.Models;
|
||||
using CRD.Views;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CRD.Utils.Sonarr;
|
||||
|
||||
|
|
|
|||
41
CRD/Utils/Structs/AnilistResponse.cs
Normal file
41
CRD/Utils/Structs/AnilistResponse.cs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace CRD.Utils.Structs.History;
|
||||
|
||||
public class AniListResponse{
|
||||
public Data? Data{ get; set; }
|
||||
}
|
||||
|
||||
public class Data{
|
||||
public Page? Page{ get; set; }
|
||||
}
|
||||
|
||||
public class Page{
|
||||
public PageInfo? PageInfo{ get; set; }
|
||||
public List<AnilistSeries>? Media{ get; set; }
|
||||
}
|
||||
|
||||
public class PageInfo{
|
||||
public bool HasNextPage{ get; set; }
|
||||
public int Total{ get; set; }
|
||||
}
|
||||
|
||||
public class AniListResponseCalendar{
|
||||
public Data2? Data{ get; set; }
|
||||
}
|
||||
|
||||
public class Data2{
|
||||
public Page2? Page{ get; set; }
|
||||
}
|
||||
|
||||
public class Page2{
|
||||
public PageInfo? PageInfo{ get; set; }
|
||||
public List<AiringSchedule>? AiringSchedules{ get; set; }
|
||||
}
|
||||
|
||||
public class AiringSchedule{
|
||||
public int Id{ get; set; }
|
||||
public int Episode{ get; set; }
|
||||
public int AiringAt{ get; set; }
|
||||
public AnilistSeries? Media{ get; set; }
|
||||
}
|
||||
126
CRD/Utils/Structs/AnilistUpcoming.cs
Normal file
126
CRD/Utils/Structs/AnilistUpcoming.cs
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
using System.Collections.Generic;
|
||||
using Avalonia.Media.Imaging;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CRD.Utils.Structs;
|
||||
|
||||
public partial class AnilistSeries : ObservableObject{
|
||||
public int Id{ get; set; }
|
||||
public int? IdMal{ get; set; }
|
||||
public Title Title{ get; set; }
|
||||
public Date StartDate{ get; set; }
|
||||
public Date EndDate{ get; set; }
|
||||
public string Status{ get; set; }
|
||||
public string Season{ get; set; }
|
||||
public string Format{ get; set; }
|
||||
public List<string> Genres{ get; set; }
|
||||
public List<string> Synonyms{ get; set; }
|
||||
public int? Duration{ get; set; }
|
||||
public int Popularity{ get; set; }
|
||||
public int? Episodes{ get; set; }
|
||||
public string Source{ get; set; }
|
||||
public string CountryOfOrigin{ get; set; }
|
||||
public string Hashtag{ get; set; }
|
||||
public int? AverageScore{ get; set; }
|
||||
public string SiteUrl{ get; set; }
|
||||
public string Description{ get; set; }
|
||||
public string BannerImage{ get; set; }
|
||||
public bool IsAdult{ get; set; }
|
||||
public CoverImage CoverImage{ get; set; }
|
||||
public Trailer Trailer{ get; set; }
|
||||
public List<ExternalLink>? ExternalLinks{ get; set; }
|
||||
public List<Ranking> Rankings{ get; set; }
|
||||
public Studios Studios{ get; set; }
|
||||
public Relations Relations{ get; set; }
|
||||
public AiringSchedule AiringSchedule{ get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public Bitmap? ThumbnailImage{ get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string StartDateForm => $"{StartDate.Day}.{StartDate.Month}.{StartDate.Year}";
|
||||
|
||||
[JsonIgnore]
|
||||
public string? CrunchyrollID;
|
||||
|
||||
[JsonIgnore]
|
||||
[ObservableProperty]
|
||||
public bool _hasCrID;
|
||||
|
||||
[JsonIgnore]
|
||||
[ObservableProperty]
|
||||
public bool _isInHistory;
|
||||
|
||||
}
|
||||
|
||||
public class Title{
|
||||
public string Romaji{ get; set; }
|
||||
public string Native{ get; set; }
|
||||
public string English{ get; set; }
|
||||
}
|
||||
|
||||
public class Date{
|
||||
public int? Year{ get; set; }
|
||||
public int? Month{ get; set; }
|
||||
public int? Day{ get; set; }
|
||||
}
|
||||
|
||||
public class CoverImage{
|
||||
public string ExtraLarge{ get; set; }
|
||||
public string Color{ get; set; }
|
||||
}
|
||||
|
||||
public class Trailer{
|
||||
public string Id{ get; set; }
|
||||
public string Site{ get; set; }
|
||||
public string Thumbnail{ get; set; }
|
||||
}
|
||||
|
||||
public class ExternalLink{
|
||||
public string Site{ get; set; }
|
||||
public string Icon{ get; set; }
|
||||
public string Color{ get; set; }
|
||||
public string Url{ get; set; }
|
||||
}
|
||||
|
||||
public class Ranking{
|
||||
public int Rank{ get; set; }
|
||||
public string Type{ get; set; }
|
||||
public string Season{ get; set; }
|
||||
public bool AllTime{ get; set; }
|
||||
}
|
||||
|
||||
public class Studios{
|
||||
public List<StudioNode> Nodes{ get; set; }
|
||||
}
|
||||
|
||||
public class StudioNode{
|
||||
public int Id{ get; set; }
|
||||
public string Name{ get; set; }
|
||||
public string SiteUrl{ get; set; }
|
||||
}
|
||||
|
||||
public class Relations{
|
||||
public List<RelationEdge> Edges{ get; set; }
|
||||
}
|
||||
|
||||
public class RelationEdge{
|
||||
public string RelationType{ get; set; }
|
||||
public RelationNode Node{ get; set; }
|
||||
}
|
||||
|
||||
public class RelationNode{
|
||||
public int Id{ get; set; }
|
||||
public Title Title{ get; set; }
|
||||
public string SiteUrl{ get; set; }
|
||||
}
|
||||
|
||||
public class AiringSchedule{
|
||||
public List<AiringNode> Nodes{ get; set; }
|
||||
}
|
||||
|
||||
public class AiringNode{
|
||||
public int Episode{ get; set; }
|
||||
public long AiringAt{ get; set; }
|
||||
}
|
||||
|
|
@ -1,13 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Platform;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
|
|
@ -15,19 +11,19 @@ using CRD.Downloader.Crunchyroll;
|
|||
namespace CRD.Utils.Structs;
|
||||
|
||||
public class CalendarWeek{
|
||||
public DateTime? FirstDayOfWeek{ get; set; }
|
||||
public DateTime FirstDayOfWeek{ get; set; }
|
||||
public string? FirstDayOfWeekString{ get; set; }
|
||||
public List<CalendarDay>? CalendarDays{ get; set; }
|
||||
}
|
||||
|
||||
public class CalendarDay{
|
||||
public DateTime? DateTime{ get; set; }
|
||||
public DateTime DateTime{ get; set; }
|
||||
public string? DayName{ get; set; }
|
||||
public List<CalendarEpisode>? CalendarEpisodes{ get; set; }
|
||||
public List<CalendarEpisode> CalendarEpisodes{ get; set; } =[];
|
||||
}
|
||||
|
||||
public partial class CalendarEpisode : INotifyPropertyChanged{
|
||||
public DateTime? DateTime{ get; set; }
|
||||
public DateTime DateTime{ get; set; }
|
||||
public bool? HasPassed{ get; set; }
|
||||
public string? EpisodeName{ get; set; }
|
||||
public string? SeriesUrl{ get; set; }
|
||||
|
|
@ -42,32 +38,38 @@ public partial class CalendarEpisode : INotifyPropertyChanged{
|
|||
|
||||
public string? SeasonName{ get; set; }
|
||||
|
||||
public string? CrSeriesID{ get; set; }
|
||||
|
||||
public bool AnilistEpisode{ get; set; }
|
||||
|
||||
public List<CalendarEpisode> CalendarEpisodes{ get; set; } =[];
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
[RelayCommand]
|
||||
public void AddEpisodeToQue(string episodeUrl){
|
||||
var match = Regex.Match(episodeUrl, "/([^/]+)/watch/([^/]+)");
|
||||
public void AddEpisodeToQue(){
|
||||
if (CalendarEpisodes.Count > 0){
|
||||
foreach (var calendarEpisode in CalendarEpisodes){
|
||||
calendarEpisode.AddEpisodeToQue();
|
||||
}
|
||||
}
|
||||
|
||||
if (match.Success){
|
||||
var locale = match.Groups[1].Value; // Capture the locale part
|
||||
var id = match.Groups[2].Value; // Capture the ID part
|
||||
QueueManager.Instance.CrAddEpisodeToQueue(id, Languages.Locale2language(locale).CrLocale, CrunchyrollManager.Instance.CrunOptions.DubLang, true);
|
||||
if (EpisodeUrl != null){
|
||||
var match = Regex.Match(EpisodeUrl, "/([^/]+)/watch/([^/]+)");
|
||||
|
||||
if (match.Success){
|
||||
var locale = match.Groups[1].Value; // Capture the locale part
|
||||
var id = match.Groups[2].Value; // Capture the ID part
|
||||
QueueManager.Instance.CrAddEpisodeToQueue(id, Languages.Locale2language(locale).CrLocale, CrunchyrollManager.Instance.CrunOptions.DubLang, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task LoadImage(){
|
||||
public async Task LoadImage(int width = 0, int height = 0){
|
||||
try{
|
||||
if (string.IsNullOrEmpty(ThumbnailUrl)){
|
||||
|
||||
} else{
|
||||
using (var client = new HttpClient()){
|
||||
var response = await client.GetAsync(ThumbnailUrl);
|
||||
response.EnsureSuccessStatusCode();
|
||||
using (var stream = await response.Content.ReadAsStreamAsync()){
|
||||
ImageBitmap = new Bitmap(stream);
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ImageBitmap)));
|
||||
}
|
||||
}
|
||||
if (!string.IsNullOrEmpty(ThumbnailUrl)){
|
||||
ImageBitmap = await Helpers.LoadImage(ThumbnailUrl, width, height);
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ImageBitmap)));
|
||||
}
|
||||
} catch (Exception ex){
|
||||
// Handle exceptions
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ namespace CRD.Utils.Structs;
|
|||
|
||||
public struct CrunchyChapters{
|
||||
public List<CrunchyChapter> Chapters { get; set; }
|
||||
public DateTime? lastUpdate { get; set; }
|
||||
public DateTime lastUpdate { get; set; }
|
||||
public string? mediaId { get; set; }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,15 +6,98 @@ using YamlDotNet.Serialization;
|
|||
namespace CRD.Utils.Structs;
|
||||
|
||||
public class CrDownloadOptions{
|
||||
|
||||
#region General Settings
|
||||
|
||||
[YamlMember(Alias = "auto_download", ApplyNamingConventions = false)]
|
||||
public bool AutoDownload{ get; set; }
|
||||
|
||||
|
||||
[YamlMember(Alias = "remove_finished_downloads", ApplyNamingConventions = false)]
|
||||
public bool RemoveFinishedDownload{ get; set; }
|
||||
|
||||
|
||||
[YamlIgnore]
|
||||
public int Timeout{ get; set; }
|
||||
|
||||
[YamlIgnore]
|
||||
public int FsRetryTime{ get; set; }
|
||||
|
||||
[YamlIgnore]
|
||||
public string Force{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "simultaneous_downloads", ApplyNamingConventions = false)]
|
||||
public int SimultaneousDownloads{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "theme", ApplyNamingConventions = false)]
|
||||
public string Theme{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "accent_color", ApplyNamingConventions = false)]
|
||||
public string? AccentColor{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "background_image_path", ApplyNamingConventions = false)]
|
||||
public string? BackgroundImagePath{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "background_image_opacity", ApplyNamingConventions = false)]
|
||||
public double BackgroundImageOpacity{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "background_image_blur_radius", ApplyNamingConventions = false)]
|
||||
public double BackgroundImageBlurRadius{ get; set; }
|
||||
|
||||
|
||||
[YamlIgnore]
|
||||
public List<string> Override{ get; set; }
|
||||
|
||||
[YamlIgnore]
|
||||
public string CcTag{ get; set; }
|
||||
|
||||
[YamlIgnore]
|
||||
public bool Nocleanup{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "history", ApplyNamingConventions = false)]
|
||||
public bool History{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "history_lang", ApplyNamingConventions = false)]
|
||||
public string? HistoryLang{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "history_add_specials", ApplyNamingConventions = false)]
|
||||
public bool HistoryAddSpecials{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "history_count_sonarr", ApplyNamingConventions = false)]
|
||||
public bool HistoryCountSonarr{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "sonarr_properties", ApplyNamingConventions = false)]
|
||||
public SonarrProperties? SonarrProperties{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "log_mode", ApplyNamingConventions = false)]
|
||||
public bool LogMode{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "download_dir_path", ApplyNamingConventions = false)]
|
||||
public string? DownloadDirPath{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "download_temp_dir_path", ApplyNamingConventions = false)]
|
||||
public string? DownloadTempDirPath{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "download_to_temp_folder", ApplyNamingConventions = false)]
|
||||
public bool DownloadToTempFolder{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "history_page_properties", ApplyNamingConventions = false)]
|
||||
public HistoryPageProperties? HistoryPageProperties{ get; set; }
|
||||
|
||||
[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; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Crunchyroll Settings
|
||||
|
||||
[YamlMember(Alias = "hard_sub_lang", ApplyNamingConventions = false)]
|
||||
public string Hslang{ get; set; }
|
||||
|
||||
|
|
@ -45,14 +128,6 @@ public class CrDownloadOptions{
|
|||
[YamlIgnore]
|
||||
public int Partsize{ get; set; }
|
||||
|
||||
[YamlIgnore]
|
||||
public int Timeout{ get; set; }
|
||||
|
||||
[YamlIgnore]
|
||||
public int Waittime{ get; set; }
|
||||
|
||||
[YamlIgnore]
|
||||
public int FsRetryTime{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "soft_subs", ApplyNamingConventions = false)]
|
||||
public List<string> DlSubs{ get; set; }
|
||||
|
|
@ -62,43 +137,37 @@ public class CrDownloadOptions{
|
|||
|
||||
[YamlMember(Alias = "mux_skip_subs", ApplyNamingConventions = false)]
|
||||
public bool SkipSubsMux{ get; set; }
|
||||
|
||||
|
||||
[YamlMember(Alias = "subs_add_scaled_border", ApplyNamingConventions = false)]
|
||||
public ScaledBorderAndShadowSelection SubsAddScaledBorder{ get; set; }
|
||||
|
||||
|
||||
[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; }
|
||||
|
||||
[YamlIgnore]
|
||||
public List<string> Override{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "mux_video_title", ApplyNamingConventions = false)]
|
||||
public string? VideoTitle{ get; set; }
|
||||
|
||||
|
||||
[YamlMember(Alias = "mux_video_description", ApplyNamingConventions = false)]
|
||||
public bool IncludeVideoDescription{ get; set; }
|
||||
|
||||
|
||||
[YamlMember(Alias = "mux_description_lang", ApplyNamingConventions = false)]
|
||||
public string? DescriptionLang{ get; set; }
|
||||
|
||||
[YamlIgnore]
|
||||
public string Force{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "mux_ffmpeg", ApplyNamingConventions = false)]
|
||||
public List<string> FfmpegOptions{ get; set; }
|
||||
|
||||
|
|
@ -107,22 +176,19 @@ public class CrDownloadOptions{
|
|||
|
||||
[YamlMember(Alias = "mux_default_sub", ApplyNamingConventions = false)]
|
||||
public string DefaultSub{ get; set; }
|
||||
|
||||
|
||||
[YamlMember(Alias = "mux_default_sub_signs", ApplyNamingConventions = false)]
|
||||
public bool DefaultSubSigns{ get; set; }
|
||||
|
||||
|
||||
[YamlMember(Alias = "mux_default_sub_forced_display", ApplyNamingConventions = false)]
|
||||
public bool DefaultSubForcedDisplay{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "mux_default_dub", ApplyNamingConventions = false)]
|
||||
public string DefaultAudio{ get; set; }
|
||||
|
||||
[YamlIgnore]
|
||||
public string CcTag{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "dl_video_once", ApplyNamingConventions = false)]
|
||||
public bool DlVideoOnce{ get; set; }
|
||||
|
||||
|
||||
[YamlMember(Alias = "keep_dubs_seperate", ApplyNamingConventions = false)]
|
||||
public bool KeepDubsSeperate{ get; set; }
|
||||
|
||||
|
|
@ -131,88 +197,37 @@ public class CrDownloadOptions{
|
|||
|
||||
[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; }
|
||||
|
||||
[YamlMember(Alias = "chapters", ApplyNamingConventions = false)]
|
||||
public bool Chapters{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "dub_lang", ApplyNamingConventions = false)]
|
||||
public List<string> DubLang{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "simultaneous_downloads", ApplyNamingConventions = false)]
|
||||
public int SimultaneousDownloads{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "theme", ApplyNamingConventions = false)]
|
||||
public string Theme{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "accent_color", ApplyNamingConventions = false)]
|
||||
public string? AccentColor{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "calendar_language", ApplyNamingConventions = false)]
|
||||
public string? SelectedCalendarLanguage{ get; set; }
|
||||
|
||||
|
||||
[YamlMember(Alias = "calendar_dub_filter", ApplyNamingConventions = false)]
|
||||
public string? CalendarDubFilter{ get; set; }
|
||||
|
||||
|
||||
[YamlMember(Alias = "calendar_custom", ApplyNamingConventions = false)]
|
||||
public bool CustomCalendar{ get; set; }
|
||||
|
||||
|
||||
[YamlMember(Alias = "calendar_hide_dubs", ApplyNamingConventions = false)]
|
||||
public bool CalendarHideDubs{ get; set; }
|
||||
|
||||
|
||||
[YamlMember(Alias = "calendar_filter_by_air_date", ApplyNamingConventions = false)]
|
||||
public bool CalendarFilterByAirDate{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "history", ApplyNamingConventions = false)]
|
||||
public bool History{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "history_lang", ApplyNamingConventions = false)]
|
||||
public string? HistoryLang{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "history_add_specials", ApplyNamingConventions = false)]
|
||||
public bool HistoryAddSpecials{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "history_count_sonarr", ApplyNamingConventions = false)]
|
||||
public bool HistoryCountSonarr{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "sonarr_properties", ApplyNamingConventions = false)]
|
||||
public SonarrProperties? SonarrProperties{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "log_mode", ApplyNamingConventions = false)]
|
||||
public bool LogMode{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "stream_endpoint", ApplyNamingConventions = false)]
|
||||
public string? StreamEndpoint{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "download_dir_path", ApplyNamingConventions = false)]
|
||||
public string? DownloadDirPath{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "download_temp_dir_path", ApplyNamingConventions = false)]
|
||||
public string? DownloadTempDirPath{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "download_to_temp_folder", ApplyNamingConventions = false)]
|
||||
public bool DownloadToTempFolder{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "history_page_properties", ApplyNamingConventions = false)]
|
||||
public HistoryPageProperties? HistoryPageProperties{ get; set; }
|
||||
|
||||
[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; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -27,16 +27,16 @@ public class CrunchyMovie{
|
|||
public bool IsMature{ get; set; }
|
||||
|
||||
[JsonProperty("free_available_date")]
|
||||
public DateTime? FreeAvailableDate{ get; set; }
|
||||
public DateTime FreeAvailableDate{ get; set; }
|
||||
|
||||
[JsonProperty("premium_available_date")]
|
||||
public DateTime? PremiumAvailableDate{ get; set; }
|
||||
public DateTime PremiumAvailableDate{ get; set; }
|
||||
|
||||
[JsonProperty("availability_starts")]
|
||||
public DateTime? AvailabilityStarts{ get; set; }
|
||||
public DateTime AvailabilityStarts{ get; set; }
|
||||
|
||||
[JsonProperty("availability_ends")]
|
||||
public DateTime? AvailabilityEnds{ get; set; }
|
||||
public DateTime AvailabilityEnds{ get; set; }
|
||||
|
||||
[JsonProperty("maturity_ratings")]
|
||||
public List<string> MaturityRatings{ get; set; }
|
||||
|
|
@ -55,7 +55,7 @@ public class CrunchyMovie{
|
|||
public string ListingId{ get; set; }
|
||||
|
||||
[JsonProperty("available_date")]
|
||||
public DateTime? AvailableDate{ get; set; }
|
||||
public DateTime AvailableDate{ get; set; }
|
||||
|
||||
[JsonProperty("is_subbed")]
|
||||
public bool IsSubbed{ get; set; }
|
||||
|
|
@ -94,5 +94,5 @@ public class CrunchyMovie{
|
|||
public Dictionary<string, object> ExtendedMaturityRating{ get; set; }
|
||||
|
||||
[JsonProperty("premium_date")]
|
||||
public DateTime? PremiumDate{ get; set; }
|
||||
public DateTime PremiumDate{ get; set; }
|
||||
}
|
||||
|
|
@ -8,6 +8,8 @@ public class CrProfile{
|
|||
public string? Avatar{ get; set; }
|
||||
public string? Email{ get; set; }
|
||||
public string? Username{ get; set; }
|
||||
[JsonProperty("profile_name")]
|
||||
public string? ProfileName{ get; set; }
|
||||
|
||||
[JsonProperty("preferred_content_audio_language")]
|
||||
public string? PreferredContentAudioLanguage{ get; set; }
|
||||
|
|
|
|||
|
|
@ -11,5 +11,6 @@ public class CrToken{
|
|||
public string? country { get; set; }
|
||||
public string? account_id { get; set; }
|
||||
public string? profile_id { get; set; }
|
||||
public DateTime? expires { get; set; }
|
||||
public string? device_id { get; set; }
|
||||
public DateTime expires { get; set; }
|
||||
}
|
||||
|
|
@ -121,25 +121,25 @@ public class CrBrowseEpisodeMetaData{
|
|||
public double SequenceNumber{ get; set; }
|
||||
|
||||
[JsonProperty("upload_date")]
|
||||
public DateTime? UploadDate{ get; set; }
|
||||
public DateTime UploadDate{ get; set; }
|
||||
|
||||
[JsonProperty("subtitle_locales")]
|
||||
public List<Locale>? SubtitleLocales{ get; set; }
|
||||
|
||||
[JsonProperty("premium_available_date")]
|
||||
public DateTime? PremiumAvailableDate{ get; set; }
|
||||
public DateTime PremiumAvailableDate{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("availability_ends")]
|
||||
public DateTime? AvailabilityEnds{ get; set; }
|
||||
public DateTime AvailabilityEnds{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("availability_starts")]
|
||||
public DateTime? AvailabilityStarts{ get; set; }
|
||||
public DateTime AvailabilityStarts{ get; set; }
|
||||
|
||||
|
||||
[JsonProperty("free_available_date")]
|
||||
public DateTime? FreeAvailableDate{ get; set; }
|
||||
public DateTime FreeAvailableDate{ get; set; }
|
||||
|
||||
[JsonProperty("identifier")]
|
||||
public string? Identifier{ get; set; }
|
||||
|
|
@ -157,10 +157,10 @@ public class CrBrowseEpisodeMetaData{
|
|||
public string? EligibleRegion{ get; set; }
|
||||
|
||||
[JsonProperty("available_date")]
|
||||
public DateTime? AvailableDate{ get; set; }
|
||||
public DateTime AvailableDate{ get; set; }
|
||||
|
||||
[JsonProperty("premium_date")]
|
||||
public DateTime? PremiumDate{ get; set; }
|
||||
public DateTime PremiumDate{ get; set; }
|
||||
|
||||
[JsonProperty("available_offline")]
|
||||
public bool AvailableOffline{ get; set; }
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ public struct CrunchyEpisode{
|
|||
public string EligibleRegion{ get; set; }
|
||||
|
||||
[JsonProperty("availability_starts")]
|
||||
public DateTime? AvailabilityStarts{ get; set; }
|
||||
public DateTime AvailabilityStarts{ get; set; }
|
||||
|
||||
public Images? Images{ get; set; }
|
||||
|
||||
|
|
@ -70,7 +70,7 @@ public struct CrunchyEpisode{
|
|||
public string ProductionEpisodeId{ get; set; }
|
||||
|
||||
[JsonProperty("premium_available_date")]
|
||||
public DateTime? PremiumAvailableDate{ get; set; }
|
||||
public DateTime PremiumAvailableDate{ get; set; }
|
||||
|
||||
[JsonProperty("season_title")]
|
||||
public string SeasonTitle{ get; set; }
|
||||
|
|
@ -87,10 +87,10 @@ public struct CrunchyEpisode{
|
|||
public string? MediaType{ get; set; }
|
||||
|
||||
[JsonProperty("availability_ends")]
|
||||
public DateTime? AvailabilityEnds{ get; set; }
|
||||
public DateTime AvailabilityEnds{ get; set; }
|
||||
|
||||
[JsonProperty("free_available_date")]
|
||||
public DateTime? FreeAvailableDate{ get; set; }
|
||||
public DateTime FreeAvailableDate{ get; set; }
|
||||
|
||||
public string Playback{ get; set; }
|
||||
|
||||
|
|
@ -106,12 +106,12 @@ public struct CrunchyEpisode{
|
|||
public string ListingId{ get; set; }
|
||||
|
||||
[JsonProperty("episode_air_date")]
|
||||
public DateTime? EpisodeAirDate{ get; set; }
|
||||
public DateTime EpisodeAirDate{ get; set; }
|
||||
|
||||
public string Slug{ get; set; }
|
||||
|
||||
[JsonProperty("available_date")]
|
||||
public DateTime? AvailableDate{ get; set; }
|
||||
public DateTime AvailableDate{ get; set; }
|
||||
|
||||
[JsonProperty("subtitle_locales")]
|
||||
public List<string> SubtitleLocales{ get; set; }
|
||||
|
|
@ -128,10 +128,10 @@ public struct CrunchyEpisode{
|
|||
public bool IsSubbed{ get; set; }
|
||||
|
||||
[JsonProperty("premium_date")]
|
||||
public DateTime? PremiumDate{ get; set; }
|
||||
public DateTime PremiumDate{ get; set; }
|
||||
|
||||
[JsonProperty("upload_date")]
|
||||
public DateTime? UploadDate{ get; set; }
|
||||
public DateTime UploadDate{ get; set; }
|
||||
|
||||
[JsonProperty("season_slug_title")]
|
||||
public string SeasonSlugTitle{ get; set; }
|
||||
|
|
@ -247,13 +247,18 @@ public class CrunchyEpMeta{
|
|||
|
||||
public List<string>? SelectedDubs{ get; set; }
|
||||
|
||||
public string Hslang{ get; set; } = "none";
|
||||
|
||||
public List<string>? AvailableSubs{ get; set; }
|
||||
|
||||
|
||||
public string? DownloadPath{ get; set; }
|
||||
public string? VideoQuality{ get; set; }
|
||||
public List<string> DownloadSubs{ get; set; } =[];
|
||||
|
||||
public bool Music{ get; set; }
|
||||
|
||||
public string Resolution{ get; set; }
|
||||
|
||||
public List<string> downloadedFiles{ get; set; } =[];
|
||||
}
|
||||
|
||||
public class DownloadProgress{
|
||||
|
|
@ -274,7 +279,6 @@ public struct CrunchyEpMetaData{
|
|||
public List<EpisodeVersion>? Versions{ get; set; }
|
||||
public bool IsSubbed{ get; set; }
|
||||
public bool IsDubbed{ get; set; }
|
||||
|
||||
}
|
||||
|
||||
public struct CrunchyRollEpisodeData{
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ public class CrunchyMusicVideo{
|
|||
public bool MatureBlocked{ get; set; }
|
||||
|
||||
[JsonProperty("originalRelease")]
|
||||
public DateTime? OriginalRelease{ get; set; }
|
||||
public DateTime OriginalRelease{ get; set; }
|
||||
|
||||
[JsonProperty("sequenceNumber")]
|
||||
public int SequenceNumber{ get; set; }
|
||||
|
|
@ -76,7 +76,7 @@ public class CrunchyMusicVideo{
|
|||
public bool IsPublic{ get; set; }
|
||||
|
||||
[JsonProperty("publishDate")]
|
||||
public DateTime? PublishDate{ get; set; }
|
||||
public DateTime PublishDate{ get; set; }
|
||||
|
||||
[JsonProperty("displayArtistName")]
|
||||
public string? DisplayArtistName{ get; set; }
|
||||
|
|
@ -91,12 +91,12 @@ public class CrunchyMusicVideo{
|
|||
public string? Id{ get; set; }
|
||||
|
||||
[JsonProperty("createdAt")]
|
||||
public DateTime? CreatedAt{ get; set; }
|
||||
public DateTime CreatedAt{ get; set; }
|
||||
|
||||
public MusicImages? Images{ get; set; }
|
||||
|
||||
[JsonProperty("updatedAt")]
|
||||
public DateTime? UpdatedAt{ get; set; }
|
||||
public DateTime UpdatedAt{ get; set; }
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -125,8 +125,8 @@ public struct MusicVideoArtist{
|
|||
|
||||
public struct MusicVideoAvailability{
|
||||
[JsonProperty("endDate")]
|
||||
public DateTime? EndDate{ get; set; }
|
||||
public DateTime EndDate{ get; set; }
|
||||
[JsonProperty("startDate")]
|
||||
public DateTime? StartDate{ get; set; }
|
||||
public DateTime StartDate{ get; set; }
|
||||
|
||||
}
|
||||
|
|
@ -53,6 +53,9 @@ public class CrBrowseSeries : INotifyPropertyChanged{
|
|||
|
||||
[JsonIgnore]
|
||||
public Bitmap? ImageBitmap{ get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsInHistory{ get; set; }
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
public async void LoadImage(string url){
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CRD.Utils.Structs.Crunchyroll;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
using CRD.Downloader;
|
||||
|
|
@ -23,6 +24,9 @@ public class HistoryEpisode : INotifyPropertyChanged{
|
|||
|
||||
[JsonProperty("episode_cr_season_number")]
|
||||
public string? EpisodeSeasonNum{ get; set; }
|
||||
|
||||
[JsonProperty("episode_cr_premium_air_date")]
|
||||
public DateTime? EpisodeCrPremiumAirDate{ get; set; }
|
||||
|
||||
[JsonProperty("episode_was_downloaded")]
|
||||
public bool WasDownloaded{ get; set; }
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using Avalonia.Controls;
|
||||
using CRD.Downloader;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CRD.Utils.Structs.History;
|
||||
|
|
@ -31,6 +29,9 @@ public class HistorySeason : INotifyPropertyChanged{
|
|||
[JsonProperty("series_download_path")]
|
||||
public string? SeasonDownloadPath{ get; set; }
|
||||
|
||||
[JsonProperty("history_season_video_quality_override")]
|
||||
public string HistorySeasonVideoQualityOverride{ get; set; } = "";
|
||||
|
||||
[JsonProperty("history_season_soft_subs_override")]
|
||||
public List<string> HistorySeasonSoftSubsOverride{ get; set; } =[];
|
||||
|
||||
|
|
@ -45,7 +46,22 @@ public class HistorySeason : INotifyPropertyChanged{
|
|||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
#region Language Override
|
||||
#region Settings Override
|
||||
|
||||
[JsonIgnore]
|
||||
public StringItem? _selectedVideoQualityItem;
|
||||
|
||||
[JsonIgnore]
|
||||
public StringItem? SelectedVideoQualityItem{
|
||||
get => _selectedVideoQualityItem;
|
||||
set{
|
||||
_selectedVideoQualityItem = value;
|
||||
|
||||
HistorySeasonVideoQualityOverride = value?.stringValue ?? "";
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedVideoQualityItem)));
|
||||
CfgManager.UpdateHistoryFile();
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public string SelectedSubs{ get; set; } = "";
|
||||
|
|
@ -68,7 +84,18 @@ public class HistorySeason : INotifyPropertyChanged{
|
|||
new StringItem(){ stringValue = "all" },
|
||||
new StringItem(){ stringValue = "none" },
|
||||
};
|
||||
|
||||
|
||||
[JsonIgnore]
|
||||
public ObservableCollection<StringItem> VideoQualityList{ get; } = new(){
|
||||
new StringItem(){ stringValue = "best" },
|
||||
new StringItem(){ stringValue = "1080p" },
|
||||
new StringItem(){ stringValue = "720p" },
|
||||
new StringItem(){ stringValue = "480p" },
|
||||
new StringItem(){ stringValue = "360p" },
|
||||
new StringItem(){ stringValue = "240p" },
|
||||
new StringItem(){ stringValue = "worst" },
|
||||
};
|
||||
|
||||
private void UpdateSubAndDubString(){
|
||||
HistorySeasonSoftSubsOverride.Clear();
|
||||
HistorySeasonDubLangOverride.Clear();
|
||||
|
|
@ -88,6 +115,7 @@ public class HistorySeason : INotifyPropertyChanged{
|
|||
SelectedDubs = string.Join(", ", HistorySeasonDubLangOverride) ?? "";
|
||||
SelectedSubs = string.Join(", ", HistorySeasonSoftSubsOverride) ?? "";
|
||||
|
||||
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedSubs)));
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedDubs)));
|
||||
|
||||
|
|
@ -99,35 +127,35 @@ public class HistorySeason : INotifyPropertyChanged{
|
|||
}
|
||||
|
||||
public void Init(){
|
||||
|
||||
if (!(SubLangList.Count > 2 || DubLangList.Count > 0)){
|
||||
foreach (var languageItem in Languages.languages){
|
||||
SubLangList.Add(new StringItem{ stringValue = languageItem.CrLocale });
|
||||
DubLangList.Add(new StringItem{ stringValue = languageItem.CrLocale });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
SelectedVideoQualityItem = VideoQualityList.FirstOrDefault(a => HistorySeasonVideoQualityOverride.Equals(a.stringValue)) ?? new StringItem(){ stringValue = "" };
|
||||
|
||||
var softSubLang = SubLangList.Where(a => HistorySeasonSoftSubsOverride.Contains(a.stringValue)).ToList();
|
||||
var dubLang = DubLangList.Where(a => HistorySeasonDubLangOverride.Contains(a.stringValue)).ToList();
|
||||
|
||||
|
||||
SelectedSubLang.Clear();
|
||||
foreach (var listBoxItem in softSubLang){
|
||||
SelectedSubLang.Add(listBoxItem);
|
||||
}
|
||||
|
||||
|
||||
SelectedDubLang.Clear();
|
||||
foreach (var listBoxItem in dubLang){
|
||||
SelectedDubLang.Add(listBoxItem);
|
||||
}
|
||||
|
||||
|
||||
SelectedDubs = string.Join(", ", HistorySeasonDubLangOverride) ?? "";
|
||||
SelectedSubs = string.Join(", ", HistorySeasonSoftSubsOverride) ?? "";
|
||||
|
||||
|
||||
SelectedSubLang.CollectionChanged += Changes;
|
||||
SelectedDubLang.CollectionChanged += Changes;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
public void UpdateDownloaded(string? EpisodeId){
|
||||
|
|
@ -151,5 +179,4 @@ public class HistorySeason : INotifyPropertyChanged{
|
|||
DownloadedEpisodes = EpisodesList.FindAll(e => e.WasDownloaded).Count;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(DownloadedEpisodes)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,14 +1,11 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media.Imaging;
|
||||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils.CustomList;
|
||||
using Newtonsoft.Json;
|
||||
|
|
@ -52,12 +49,15 @@ public class HistorySeries : INotifyPropertyChanged{
|
|||
[JsonProperty("history_series_add_date")]
|
||||
public DateTime? HistorySeriesAddDate{ get; set; }
|
||||
|
||||
[JsonProperty("history_series_video_quality_override")]
|
||||
public string HistorySeriesVideoQualityOverride{ get; set; } = "";
|
||||
|
||||
[JsonProperty("history_series_available_soft_subs")]
|
||||
public List<string> HistorySeriesAvailableSoftSubs{ get; set; } =[];
|
||||
|
||||
[JsonProperty("history_series_available_dub_lang")]
|
||||
public List<string> HistorySeriesAvailableDubLang{ get; set; } =[];
|
||||
|
||||
|
||||
[JsonProperty("history_series_soft_subs_override")]
|
||||
public List<string> HistorySeriesSoftSubsOverride{ get; set; } =[];
|
||||
|
||||
|
|
@ -92,7 +92,22 @@ public class HistorySeries : INotifyPropertyChanged{
|
|||
[JsonIgnore]
|
||||
private bool _editModeEnabled;
|
||||
|
||||
#region Language Override
|
||||
#region Settings Override
|
||||
|
||||
[JsonIgnore]
|
||||
public StringItem? _selectedVideoQualityItem;
|
||||
|
||||
[JsonIgnore]
|
||||
public StringItem? SelectedVideoQualityItem{
|
||||
get => _selectedVideoQualityItem;
|
||||
set{
|
||||
_selectedVideoQualityItem = value;
|
||||
|
||||
HistorySeriesVideoQualityOverride = value?.stringValue ?? "";
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedVideoQualityItem)));
|
||||
CfgManager.UpdateHistoryFile();
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public string SelectedSubs{ get; set; } = "";
|
||||
|
|
@ -107,6 +122,27 @@ public class HistorySeries : INotifyPropertyChanged{
|
|||
public ObservableCollection<StringItem> SelectedDubLang{ get; set; } = new();
|
||||
|
||||
|
||||
[JsonIgnore]
|
||||
public ObservableCollection<StringItem> DubLangList{ get; } = new(){
|
||||
};
|
||||
|
||||
[JsonIgnore]
|
||||
public ObservableCollection<StringItem> SubLangList{ get; } = new(){
|
||||
new StringItem(){ stringValue = "all" },
|
||||
new StringItem(){ stringValue = "none" },
|
||||
};
|
||||
|
||||
[JsonIgnore]
|
||||
public ObservableCollection<StringItem> VideoQualityList{ get; } = new(){
|
||||
new StringItem(){ stringValue = "best" },
|
||||
new StringItem(){ stringValue = "1080p" },
|
||||
new StringItem(){ stringValue = "720p" },
|
||||
new StringItem(){ stringValue = "480p" },
|
||||
new StringItem(){ stringValue = "360p" },
|
||||
new StringItem(){ stringValue = "240p" },
|
||||
new StringItem(){ stringValue = "worst" },
|
||||
};
|
||||
|
||||
private void UpdateSubAndDubString(){
|
||||
HistorySeriesSoftSubsOverride.Clear();
|
||||
HistorySeriesDubLangOverride.Clear();
|
||||
|
|
@ -136,16 +172,6 @@ public class HistorySeries : INotifyPropertyChanged{
|
|||
UpdateSubAndDubString();
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public ObservableCollection<StringItem> DubLangList{ get; } = new(){
|
||||
};
|
||||
|
||||
[JsonIgnore]
|
||||
public ObservableCollection<StringItem> SubLangList{ get; } = new(){
|
||||
new StringItem(){ stringValue = "all" },
|
||||
new StringItem(){ stringValue = "none" },
|
||||
};
|
||||
|
||||
public void Init(){
|
||||
if (!(SubLangList.Count > 2 || DubLangList.Count > 0)){
|
||||
foreach (var languageItem in Languages.languages){
|
||||
|
|
@ -154,6 +180,8 @@ public class HistorySeries : INotifyPropertyChanged{
|
|||
}
|
||||
}
|
||||
|
||||
SelectedVideoQualityItem = VideoQualityList.FirstOrDefault(a => HistorySeriesVideoQualityOverride.Equals(a.stringValue)) ?? new StringItem(){ stringValue = "" };
|
||||
|
||||
var softSubLang = SubLangList.Where(a => HistorySeriesSoftSubsOverride.Contains(a.stringValue)).ToList();
|
||||
var dubLang = DubLangList.Where(a => HistorySeriesDubLangOverride.Contains(a.stringValue)).ToList();
|
||||
|
||||
|
|
@ -181,11 +209,7 @@ public class HistorySeries : INotifyPropertyChanged{
|
|||
return;
|
||||
|
||||
try{
|
||||
using var client = new HttpClient();
|
||||
var response = await client.GetAsync(ThumbnailImageUrl);
|
||||
response.EnsureSuccessStatusCode();
|
||||
using var stream = await response.Content.ReadAsStreamAsync();
|
||||
ThumbnailImage = new Bitmap(stream);
|
||||
ThumbnailImage = await Helpers.LoadImage(ThumbnailImageUrl);
|
||||
IsImageLoaded = true;
|
||||
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ThumbnailImage)));
|
||||
|
|
@ -320,10 +344,15 @@ public class HistorySeries : INotifyPropertyChanged{
|
|||
Console.WriteLine($"Fetching Data for: {SeriesTitle}");
|
||||
FetchingData = true;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FetchingData)));
|
||||
await CrunchyrollManager.Instance.History.CRUpdateSeries(SeriesId, seasonId);
|
||||
try{
|
||||
await CrunchyrollManager.Instance.History.CRUpdateSeries(SeriesId, seasonId);
|
||||
} catch (Exception e){
|
||||
Console.Error.WriteLine("Failed to update History series");
|
||||
Console.Error.WriteLine(e);
|
||||
}
|
||||
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SeriesTitle)));
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SeriesDescription)));
|
||||
// CrunchyrollManager.Instance.History.MatchHistoryEpisodesWithSonarr(false, this);
|
||||
UpdateNewEpisodes();
|
||||
FetchingData = false;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FetchingData)));
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CRD.Views;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
|
|
@ -130,4 +131,14 @@ public class NavigationMessage{
|
|||
Back = back;
|
||||
Refresh = refresh;
|
||||
}
|
||||
}
|
||||
|
||||
public partial class SeasonViewModel : ObservableObject{
|
||||
[ObservableProperty]
|
||||
private bool _isSelected;
|
||||
|
||||
public string Season{ get; set; }
|
||||
public int Year{ get; set; }
|
||||
|
||||
public string Display => $"{Season}\n{Year}";
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using Avalonia.Data.Converters;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
|
||||
namespace CRD.Utils.UI;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using Avalonia.Data.Converters;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
|
||||
namespace CRD.Utils.UI;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using Avalonia.Data.Converters;
|
||||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
|
||||
namespace CRD.Utils.UI;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,20 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using Avalonia;
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Media;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Avalonia.Styling;
|
||||
|
||||
namespace CRD.Utils.UI;
|
||||
|
||||
public class UiValueConverterCalendarBackground : IValueConverter{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture){
|
||||
if (value is bool boolValue){
|
||||
return boolValue ? new SolidColorBrush(Color.Parse("#10f5d800")) : new SolidColorBrush(Color.Parse("#10FFFFFF"));
|
||||
var currentThemeVariant = Application.Current?.RequestedThemeVariant;
|
||||
|
||||
return boolValue ? currentThemeVariant == ThemeVariant.Dark ? new SolidColorBrush(Color.Parse("#583819")) : new SolidColorBrush(Color.Parse("#ffd8a1")) :
|
||||
currentThemeVariant == ThemeVariant.Dark ? new SolidColorBrush(Color.Parse("#353535")) : new SolidColorBrush(Color.Parse("#d7d7d7"));
|
||||
// return boolValue ? new SolidColorBrush(Color.Parse("#10f5d800")) : new SolidColorBrush(Color.Parse("#10FFFFFF"));
|
||||
}
|
||||
|
||||
return new SolidColorBrush(Color.Parse("#10FFFFFF"));
|
||||
|
|
|
|||
|
|
@ -1,24 +1,25 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
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 static Updater Instance{
|
||||
get{
|
||||
if (_instance == null){
|
||||
|
|
@ -49,20 +50,53 @@ public class Updater : INotifyPropertyChanged{
|
|||
|
||||
public async Task<bool> CheckForUpdatesAsync(){
|
||||
try{
|
||||
var platformAssetMapping = new Dictionary<OSPlatform, string>{
|
||||
{ OSPlatform.Windows, "windows" },
|
||||
{ OSPlatform.Linux, "linux" },
|
||||
{ OSPlatform.OSX, "macos" }
|
||||
};
|
||||
|
||||
//windows-x64 windows-arm64
|
||||
//linux-x64 linux-arm64
|
||||
//macos-x64 macos-arm64
|
||||
|
||||
string platformName = platformAssetMapping.FirstOrDefault(p => RuntimeInformation.IsOSPlatform(p.Key)).Value;
|
||||
|
||||
string architecture = RuntimeInformation.OSArchitecture switch{
|
||||
Architecture.X64 => "x64",
|
||||
Architecture.Arm64 => "arm64",
|
||||
_ => ""
|
||||
};
|
||||
|
||||
platformName = $"{platformName}-{architecture}";
|
||||
|
||||
Console.WriteLine($"Running on {platformName}");
|
||||
|
||||
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);
|
||||
var releaseInfo = Helpers.Deserialize<dynamic>(response, null);
|
||||
|
||||
var latestVersion = releaseInfo.tag_name;
|
||||
downloadUrl = releaseInfo.assets[0].browser_download_url;
|
||||
|
||||
foreach (var asset in releaseInfo.assets){
|
||||
string assetName = (string)asset.name;
|
||||
if (assetName.Contains(platformName)){
|
||||
downloadUrl = asset.browser_download_url;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(downloadUrl)){
|
||||
Console.WriteLine($"Failed to get Update url for {platformName}");
|
||||
return false;
|
||||
}
|
||||
|
||||
var version = Assembly.GetExecutingAssembly().GetName().Version;
|
||||
var currentVersion = $"v{version?.Major}.{version?.Minor}.{version?.Build}";
|
||||
|
||||
|
||||
if (latestVersion != currentVersion){
|
||||
Console.WriteLine("Update available: " + latestVersion + " - Current Version: " + currentVersion);
|
||||
return true;
|
||||
|
|
@ -80,43 +114,45 @@ public class Updater : INotifyPropertyChanged{
|
|||
|
||||
public async Task DownloadAndUpdateAsync(){
|
||||
try{
|
||||
using (var client = new HttpClient()){
|
||||
// Download the zip file
|
||||
var response = await client.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead);
|
||||
// Download the zip file
|
||||
var response = await HttpClientReq.Instance.GetHttpClient().GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead);
|
||||
|
||||
if (response.IsSuccessStatusCode){
|
||||
var totalBytes = response.Content.Headers.ContentLength ?? -1L;
|
||||
var totalBytesRead = 0L;
|
||||
var buffer = new byte[8192];
|
||||
var isMoreToRead = true;
|
||||
if (response.IsSuccessStatusCode){
|
||||
var totalBytes = response.Content.Headers.ContentLength ?? -1L;
|
||||
var totalBytesRead = 0L;
|
||||
var buffer = new byte[8192];
|
||||
var isMoreToRead = true;
|
||||
|
||||
using (var stream = await response.Content.ReadAsStreamAsync())
|
||||
using (var fileStream = new FileStream(tempPath, FileMode.Create, FileAccess.Write, FileShare.None)){
|
||||
do{
|
||||
var bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
|
||||
if (bytesRead == 0){
|
||||
isMoreToRead = false;
|
||||
progress = 100;
|
||||
OnPropertyChanged(nameof(progress));
|
||||
continue;
|
||||
}
|
||||
using (var stream = await response.Content.ReadAsStreamAsync())
|
||||
using (var fileStream = new FileStream(tempPath, FileMode.Create, FileAccess.Write, FileShare.None)){
|
||||
do{
|
||||
var bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
|
||||
if (bytesRead == 0){
|
||||
isMoreToRead = false;
|
||||
progress = 100;
|
||||
OnPropertyChanged(nameof(progress));
|
||||
continue;
|
||||
}
|
||||
|
||||
await fileStream.WriteAsync(buffer, 0, bytesRead);
|
||||
await fileStream.WriteAsync(buffer, 0, bytesRead);
|
||||
|
||||
totalBytesRead += bytesRead;
|
||||
if (totalBytes != -1){
|
||||
progress = (double)totalBytesRead / totalBytes * 100;
|
||||
OnPropertyChanged(nameof(progress));
|
||||
}
|
||||
} while (isMoreToRead);
|
||||
}
|
||||
|
||||
ZipFile.ExtractToDirectory(tempPath, extractPath, true);
|
||||
|
||||
ApplyUpdate(extractPath);
|
||||
} else{
|
||||
Console.Error.WriteLine("Failed to get Update");
|
||||
totalBytesRead += bytesRead;
|
||||
if (totalBytes != -1){
|
||||
progress = (double)totalBytesRead / totalBytes * 100;
|
||||
OnPropertyChanged(nameof(progress));
|
||||
}
|
||||
} while (isMoreToRead);
|
||||
}
|
||||
|
||||
if (Directory.Exists(extractPath)){
|
||||
Directory.Delete(extractPath, true);
|
||||
}
|
||||
|
||||
ZipFile.ExtractToDirectory(tempPath, extractPath, true);
|
||||
|
||||
ApplyUpdate(extractPath);
|
||||
} else{
|
||||
Console.Error.WriteLine("Failed to get Update");
|
||||
}
|
||||
} catch (Exception e){
|
||||
Console.Error.WriteLine($"Failed to get Update: {e.Message}");
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Threading;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils;
|
||||
using CRD.Views.Utils;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Newtonsoft.Json;
|
||||
|
|
@ -61,9 +59,10 @@ public partial class AccountPageViewModel : ViewModelBase{
|
|||
}
|
||||
|
||||
public void UpdatetProfile(){
|
||||
ProfileName = CrunchyrollManager.Instance.Profile.Username ?? "???"; // Default or fetched user name
|
||||
ProfileName = CrunchyrollManager.Instance.Profile.Username ?? CrunchyrollManager.Instance.Profile.ProfileName ?? "???"; // Default or fetched user name
|
||||
LoginLogoutText = CrunchyrollManager.Instance.Profile.Username == "???" ? "Login" : "Logout"; // Default state
|
||||
LoadProfileImage("https://static.crunchyroll.com/assets/avatar/170x170/" + CrunchyrollManager.Instance.Profile.Avatar);
|
||||
LoadProfileImage("https://static.crunchyroll.com/assets/avatar/170x170/" +
|
||||
(string.IsNullOrEmpty(CrunchyrollManager.Instance.Profile.Avatar) ? "crbrand_avatars_logo_marks_mangagirl_taupe.png" : CrunchyrollManager.Instance.Profile.Avatar));
|
||||
|
||||
|
||||
var subscriptions = CrunchyrollManager.Instance.Profile.Subscription;
|
||||
|
|
@ -139,13 +138,7 @@ public partial class AccountPageViewModel : ViewModelBase{
|
|||
|
||||
public async void LoadProfileImage(string imageUrl){
|
||||
try{
|
||||
using (var client = new HttpClient()){
|
||||
var response = await client.GetAsync(imageUrl);
|
||||
response.EnsureSuccessStatusCode();
|
||||
using (var stream = await response.Content.ReadAsStreamAsync()){
|
||||
ProfileImage = new Bitmap(stream);
|
||||
}
|
||||
}
|
||||
ProfileImage = await Helpers.LoadImage(imageUrl);
|
||||
} catch (Exception ex){
|
||||
// Handle exceptions
|
||||
Console.Error.WriteLine("Failed to load image: " + ex.Message);
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ using CRD.Downloader.Crunchyroll;
|
|||
using CRD.Utils;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.Crunchyroll.Music;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace CRD.ViewModels;
|
||||
|
|
@ -56,7 +57,7 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
|
||||
public ObservableCollection<ItemModel> Items{ get; set; } = new();
|
||||
public ObservableCollection<CrBrowseSeries> SearchItems{ get; set; } = new();
|
||||
public ObservableCollection<ItemModel> SelectedItems{ get; set;} = new();
|
||||
public ObservableCollection<ItemModel> SelectedItems{ get; set; } = new();
|
||||
|
||||
[ObservableProperty]
|
||||
public CrBrowseSeries _selectedSearchItem;
|
||||
|
|
@ -64,7 +65,7 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
[ObservableProperty]
|
||||
public ComboBoxItem _currentSelectedSeason;
|
||||
|
||||
public ObservableCollection<ComboBoxItem> SeasonList{ get;set; } = new();
|
||||
public ObservableCollection<ComboBoxItem> SeasonList{ get; set; } = new();
|
||||
|
||||
private Dictionary<string, List<ItemModel>> episodesBySeason = new();
|
||||
|
||||
|
|
@ -75,7 +76,7 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
private CrunchyMusicVideoList? currentMusicVideoList;
|
||||
|
||||
private bool CurrentSeasonFullySelected = false;
|
||||
|
||||
|
||||
public AddDownloadPageViewModel(){
|
||||
SelectedItems.CollectionChanged += OnSelectedItemsChanged;
|
||||
}
|
||||
|
|
@ -200,7 +201,7 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
|
||||
if (music != null){
|
||||
var meta = musicClass.EpisodeMeta(music);
|
||||
QueueManager.Instance.CrAddEpMetaToQueue(meta);
|
||||
QueueManager.Instance.CrAddMusicMetaToQueue(meta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -243,11 +244,10 @@ 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(){
|
||||
|
|
@ -426,7 +426,7 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
}
|
||||
|
||||
partial void OnCurrentSelectedSeasonChanging(ComboBoxItem? oldValue, ComboBoxItem newValue){
|
||||
if(SelectedItems == null) return;
|
||||
if (SelectedItems == null) return;
|
||||
foreach (var selectedItem in SelectedItems){
|
||||
if (!selectedEpisodes.Contains(selectedItem.AbsolutNum)){
|
||||
selectedEpisodes.Add(selectedItem.AbsolutNum);
|
||||
|
|
@ -443,8 +443,8 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
}
|
||||
|
||||
private void OnSelectedItemsChanged(object? sender, NotifyCollectionChangedEventArgs e){
|
||||
if(Items == null) return;
|
||||
|
||||
if (Items == null) return;
|
||||
|
||||
CurrentSeasonFullySelected = Items.All(item => SelectedItems.Contains(item));
|
||||
|
||||
if (CurrentSeasonFullySelected){
|
||||
|
|
@ -523,7 +523,7 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
SelectedItems.Clear();
|
||||
episodesBySeason.Clear();
|
||||
SeasonList.Clear();
|
||||
|
||||
|
||||
//TODO - find a better way to reduce ram usage
|
||||
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
|
||||
GC.Collect();
|
||||
|
|
@ -597,28 +597,25 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
|||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// 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{
|
||||
|
|
@ -640,7 +637,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,208,117);
|
||||
ImageBitmap = await Helpers.LoadImage(url, 208, 117);
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ImageBitmap)));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +1,33 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Platform;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.History;
|
||||
using DynamicData;
|
||||
using DynamicData.Kernel;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CRD.ViewModels;
|
||||
|
||||
public partial class CalendarPageViewModel : ViewModelBase{
|
||||
public ObservableCollection<CalendarDay> CalendarDays{ get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _prevButtonEnabled = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _nextButtonEnabled = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _showLoading;
|
||||
|
|
@ -62,9 +68,8 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
|||
private CalendarWeek? currentWeek;
|
||||
|
||||
private bool loading = true;
|
||||
|
||||
public CalendarPageViewModel(){
|
||||
|
||||
public CalendarPageViewModel(){
|
||||
CalendarDays = new ObservableCollection<CalendarDay>();
|
||||
|
||||
foreach (var languageItem in Languages.languages){
|
||||
|
|
@ -80,39 +85,46 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
|||
|
||||
CurrentCalendarLanguage = CalendarLanguage.FirstOrDefault(a => a.Content != null && (string)a.Content == CrunchyrollManager.Instance.CrunOptions.SelectedCalendarLanguage) ?? CalendarLanguage[0];
|
||||
loading = false;
|
||||
LoadCalendar(GetThisWeeksMondayDate(), false);
|
||||
LoadCalendar(GetThisWeeksMondayDate(), DateTime.Now, false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private string GetThisWeeksMondayDate(){
|
||||
// Get today's date
|
||||
DateTime today = DateTime.Today;
|
||||
|
||||
// Calculate the number of days to subtract to get to Monday
|
||||
// DayOfWeek.Monday is 1, so if today is Monday, subtract 0 days, if it's Tuesday subtract 1 day, etc.
|
||||
int daysToSubtract = (int)today.DayOfWeek - (int)DayOfWeek.Monday;
|
||||
|
||||
// If today is Sunday (0), it will subtract -1, which we need to adjust to 6 to go back to the previous Monday
|
||||
if (daysToSubtract < 0){
|
||||
daysToSubtract += 7;
|
||||
}
|
||||
|
||||
// Get the date of the most recent Monday
|
||||
DateTime monday = today.AddDays(-daysToSubtract);
|
||||
|
||||
// Format and print the date
|
||||
string formattedDate = monday.ToString("yyyy-MM-dd");
|
||||
|
||||
return formattedDate;
|
||||
}
|
||||
|
||||
public async void LoadCalendar(string mondayDate, bool forceUpdate){
|
||||
public async void LoadCalendar(string mondayDate,DateTime customCalDate, bool forceUpdate){
|
||||
ShowLoading = true;
|
||||
|
||||
|
||||
CalendarWeek week;
|
||||
|
||||
if (CustomCalendar){
|
||||
week = await CalendarManager.Instance.BuildCustomCalendar(forceUpdate);
|
||||
|
||||
if (customCalDate.Date == DateTime.Now.Date){
|
||||
PrevButtonEnabled = false;
|
||||
NextButtonEnabled = true;
|
||||
} else{
|
||||
PrevButtonEnabled = true;
|
||||
NextButtonEnabled = false;
|
||||
}
|
||||
|
||||
week = await CalendarManager.Instance.BuildCustomCalendar(customCalDate, forceUpdate);
|
||||
} else{
|
||||
PrevButtonEnabled = true;
|
||||
NextButtonEnabled = true;
|
||||
week = await CalendarManager.Instance.GetCalendarForDate(mondayDate, forceUpdate);
|
||||
if (currentWeek != null && currentWeek == week){
|
||||
ShowLoading = false;
|
||||
|
|
@ -129,7 +141,12 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
|||
foreach (var calendarDay in CalendarDays){
|
||||
foreach (var calendarDayCalendarEpisode in calendarDay.CalendarEpisodes){
|
||||
if (calendarDayCalendarEpisode.ImageBitmap == null){
|
||||
calendarDayCalendarEpisode.LoadImage();
|
||||
if (calendarDayCalendarEpisode.AnilistEpisode){
|
||||
calendarDayCalendarEpisode.LoadImage(100,150);
|
||||
} else{
|
||||
calendarDayCalendarEpisode.LoadImage();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -143,7 +160,11 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
|||
}
|
||||
|
||||
if (calendarDayCalendarEpisode.ImageBitmap == null){
|
||||
calendarDayCalendarEpisode.LoadImage();
|
||||
if (calendarDayCalendarEpisode.AnilistEpisode){
|
||||
calendarDayCalendarEpisode.LoadImage(100,150);
|
||||
} else{
|
||||
calendarDayCalendarEpisode.LoadImage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -175,7 +196,12 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
|||
mondayDate = GetThisWeeksMondayDate();
|
||||
}
|
||||
|
||||
LoadCalendar(mondayDate, true);
|
||||
var refreshDate = DateTime.Now;
|
||||
if (currentWeek?.FirstDayOfWeek != null){
|
||||
refreshDate = currentWeek.FirstDayOfWeek.AddDays(6);
|
||||
}
|
||||
|
||||
LoadCalendar(mondayDate,refreshDate, true);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
|
@ -186,13 +212,18 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
|||
|
||||
string mondayDate;
|
||||
|
||||
if (currentWeek is{ FirstDayOfWeek: not null }){
|
||||
mondayDate = PreviousMonday((DateTime)currentWeek.FirstDayOfWeek);
|
||||
if (currentWeek is{ FirstDayOfWeek: var firstDay } && firstDay != DateTime.MinValue){
|
||||
mondayDate = PreviousMonday(currentWeek.FirstDayOfWeek);
|
||||
} else{
|
||||
mondayDate = GetThisWeeksMondayDate();
|
||||
}
|
||||
|
||||
var refreshDate = DateTime.Now;
|
||||
if (currentWeek?.FirstDayOfWeek != null){
|
||||
refreshDate = currentWeek.FirstDayOfWeek.AddDays(-1);
|
||||
}
|
||||
|
||||
LoadCalendar(mondayDate, false);
|
||||
LoadCalendar(mondayDate,refreshDate, false);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
|
@ -203,13 +234,20 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
|||
|
||||
string mondayDate;
|
||||
|
||||
if (currentWeek is{ FirstDayOfWeek: not null }){
|
||||
mondayDate = NextMonday((DateTime)currentWeek.FirstDayOfWeek);
|
||||
if (currentWeek is{ FirstDayOfWeek: var firstDay } && firstDay != DateTime.MinValue){
|
||||
mondayDate = NextMonday(currentWeek.FirstDayOfWeek);
|
||||
} else{
|
||||
mondayDate = GetThisWeeksMondayDate();
|
||||
}
|
||||
|
||||
var refreshDate = DateTime.Now;
|
||||
if (currentWeek?.FirstDayOfWeek != null){
|
||||
refreshDate = currentWeek.FirstDayOfWeek.AddDays(13);
|
||||
}
|
||||
|
||||
LoadCalendar(mondayDate, false);
|
||||
LoadCalendar(mondayDate,refreshDate, false);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -232,7 +270,7 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
|||
|
||||
CrunchyrollManager.Instance.CrunOptions.CustomCalendar = value;
|
||||
|
||||
LoadCalendar(GetThisWeeksMondayDate(), true);
|
||||
LoadCalendar(GetThisWeeksMondayDate(),DateTime.Now, true);
|
||||
|
||||
CfgManager.WriteSettingsToFile();
|
||||
}
|
||||
|
|
@ -265,4 +303,7 @@ public partial class CalendarPageViewModel : ViewModelBase{
|
|||
CfgManager.WriteSettingsToFile();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Media.Imaging;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
|
@ -59,7 +59,6 @@ public partial class DownloadItemModel : INotifyPropertyChanged{
|
|||
public string DoingWhat{ get; set; }
|
||||
public string DownloadSpeed{ get; set; }
|
||||
public string InfoText{ get; set; }
|
||||
|
||||
public CrunchyEpMeta epMeta{ get; set; }
|
||||
|
||||
|
||||
|
|
@ -78,9 +77,11 @@ public partial class DownloadItemModel : INotifyPropertyChanged{
|
|||
Time = "Estimated Time: " + TimeSpan.FromSeconds(epMeta.DownloadProgress.Time).ToString(@"hh\:mm\:ss");
|
||||
DownloadSpeed = $"{epMeta.DownloadProgress.DownloadSpeed / 1000000.0:F2}Mb/s";
|
||||
Paused = epMeta.Paused || !isDownloading && !epMeta.Paused;
|
||||
DoingWhat = epMeta.Paused ? "Paused" : Done ? "Done" : epMeta.DownloadProgress.Doing != string.Empty ? epMeta.DownloadProgress.Doing : "Waiting";
|
||||
DoingWhat = epMeta.Paused ? "Paused" :
|
||||
Done ? (epMeta.DownloadProgress.Doing != string.Empty ? epMeta.DownloadProgress.Doing : "Done") :
|
||||
epMeta.DownloadProgress.Doing != string.Empty ? epMeta.DownloadProgress.Doing : "Waiting";
|
||||
|
||||
if (epMeta.Data != null) InfoText = GetDubString() + " - " + GetSubtitleString();
|
||||
if (epMeta.Data != null) InfoText = GetDubString() + " - " + GetSubtitleString() + (!string.IsNullOrEmpty(epMeta.Resolution) ? "- " + epMeta.Resolution : "");
|
||||
|
||||
Error = epMeta.DownloadProgress.Error;
|
||||
}
|
||||
|
|
@ -94,9 +95,9 @@ public partial class DownloadItemModel : INotifyPropertyChanged{
|
|||
}
|
||||
|
||||
private string GetSubtitleString(){
|
||||
var hardSubs = CrunchyrollManager.Instance.CrunOptions.Hslang != "none" ? "Hardsub: " : "";
|
||||
var hardSubs = epMeta.Hslang != "none" ? "Hardsub: " : "";
|
||||
if (hardSubs != string.Empty){
|
||||
var locale = Languages.Locale2language(CrunchyrollManager.Instance.CrunOptions.Hslang).CrLocale;
|
||||
var locale = Languages.Locale2language(epMeta.Hslang).CrLocale;
|
||||
if (epMeta.AvailableSubs != null && epMeta.AvailableSubs.Contains(locale)){
|
||||
hardSubs += locale + " ";
|
||||
}
|
||||
|
|
@ -133,9 +134,11 @@ public partial class DownloadItemModel : INotifyPropertyChanged{
|
|||
DownloadSpeed = $"{epMeta.DownloadProgress.DownloadSpeed / 1000000.0:F2}Mb/s";
|
||||
|
||||
Paused = epMeta.Paused || !isDownloading && !epMeta.Paused;
|
||||
DoingWhat = epMeta.Paused ? "Paused" : Done ? "Done" : epMeta.DownloadProgress.Doing != string.Empty ? epMeta.DownloadProgress.Doing : "Waiting";
|
||||
DoingWhat = epMeta.Paused ? "Paused" :
|
||||
Done ? (epMeta.DownloadProgress.Doing != string.Empty ? epMeta.DownloadProgress.Doing : "Done") :
|
||||
epMeta.DownloadProgress.Doing != string.Empty ? epMeta.DownloadProgress.Doing : "Waiting";
|
||||
|
||||
if (epMeta.Data != null) InfoText = GetDubString() + " - " + GetSubtitleString();
|
||||
if (epMeta.Data != null) InfoText = GetDubString() + " - " + GetSubtitleString() + (!string.IsNullOrEmpty(epMeta.Resolution) ? "- " + epMeta.Resolution : "");
|
||||
|
||||
Error = epMeta.DownloadProgress.Error;
|
||||
|
||||
|
|
@ -192,6 +195,16 @@ public partial class DownloadItemModel : INotifyPropertyChanged{
|
|||
CrunchyEpMeta? downloadItem = QueueManager.Instance.Queue.FirstOrDefault(e => e.Equals(epMeta)) ?? null;
|
||||
if (downloadItem != null){
|
||||
QueueManager.Instance.Queue.Remove(downloadItem);
|
||||
if (!Done){
|
||||
foreach (var downloadItemDownloadedFile in downloadItem.downloadedFiles){
|
||||
try{
|
||||
if (File.Exists(downloadItemDownloadedFile)){
|
||||
File.Delete(downloadItemDownloadedFile);
|
||||
}
|
||||
} catch (Exception e){
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,7 @@ using System;
|
|||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
|
|
@ -19,9 +16,7 @@ using CRD.Utils;
|
|||
using CRD.Utils.Sonarr;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.History;
|
||||
using CRD.Views;
|
||||
using DynamicData;
|
||||
using HarfBuzzSharp;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace CRD.ViewModels;
|
||||
|
|
@ -29,10 +24,10 @@ namespace CRD.ViewModels;
|
|||
public partial class HistoryPageViewModel : ViewModelBase{
|
||||
public ObservableCollection<HistorySeries> Items{ get; }
|
||||
public ObservableCollection<HistorySeries> FilteredItems{ get; }
|
||||
|
||||
|
||||
[ObservableProperty]
|
||||
private ProgramManager _programManager;
|
||||
|
||||
|
||||
[ObservableProperty]
|
||||
private HistorySeries _selectedSeries;
|
||||
|
||||
|
|
@ -107,14 +102,15 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
|
||||
[ObservableProperty]
|
||||
private static bool _sonarrAvailable;
|
||||
|
||||
|
||||
[ObservableProperty]
|
||||
private static string _progressText;
|
||||
|
||||
public HistoryPageViewModel(){
|
||||
|
||||
ProgramManager = ProgramManager.Instance;
|
||||
|
||||
_storageProvider = ProgramManager.StorageProvider ?? throw new ArgumentNullException(nameof(ProgramManager.StorageProvider));
|
||||
|
||||
if (CrunchyrollManager.Instance.CrunOptions.SonarrProperties != null){
|
||||
SonarrAvailable = CrunchyrollManager.Instance.CrunOptions.SonarrProperties.SonarrEnabled;
|
||||
} else{
|
||||
|
|
@ -149,11 +145,10 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
}
|
||||
|
||||
foreach (FilterType filterType in Enum.GetValues(typeof(FilterType))){
|
||||
|
||||
if (!SonarrAvailable && (filterType == FilterType.MissingEpisodesSonarr || filterType == FilterType.ContinuingOnly)){
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
var item = new FilterListElement(){ FilterTitle = filterType.GetEnumMemberValue(), SelectedType = filterType };
|
||||
FilterList.Add(item);
|
||||
if (filterType == currentFilterType){
|
||||
|
|
@ -224,7 +219,6 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
if (SelectedFilter != null){
|
||||
OnSelectedFilterChanged(SelectedFilter);
|
||||
}
|
||||
|
||||
} else{
|
||||
Console.Error.WriteLine("Invalid viewtype selected");
|
||||
}
|
||||
|
|
@ -235,11 +229,10 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
|
||||
|
||||
partial void OnSelectedFilterChanged(FilterListElement? value){
|
||||
|
||||
if (value == null){
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
currentFilterType = value.SelectedType;
|
||||
if (CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties != null) CrunchyrollManager.Instance.CrunOptions.HistoryPageProperties.SelectedFilter = currentFilterType;
|
||||
|
||||
|
|
@ -293,7 +286,11 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
}
|
||||
|
||||
|
||||
partial void OnSelectedSeriesChanged(HistorySeries value){
|
||||
partial void OnSelectedSeriesChanged(HistorySeries? value){
|
||||
if (value == null){
|
||||
return;
|
||||
}
|
||||
|
||||
CrunchyrollManager.Instance.SelectedSeries = value;
|
||||
|
||||
NavToSeries();
|
||||
|
|
@ -399,17 +396,17 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
Console.Error.WriteLine($"[Sonarr Match] {series.Title} already matched");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var seriesIds = concurrentSeriesIds.ToList();
|
||||
var totalSeries = seriesIds.Count;
|
||||
|
||||
|
||||
for (int count = 0; count < totalSeries; count++){
|
||||
ProgressText = $"{count + 1}/{totalSeries}";
|
||||
|
||||
|
||||
// Await the CRUpdateSeries task for each seriesId
|
||||
await crInstance.History.CRUpdateSeries(seriesIds[count], "");
|
||||
}
|
||||
|
||||
|
||||
// var updateTasks = seriesIds.Select(seriesId => crInstance.History.CRUpdateSeries(seriesId, ""));
|
||||
// await Task.WhenAll(updateTasks);
|
||||
}
|
||||
|
|
@ -495,11 +492,6 @@ public partial class HistoryPageViewModel : ViewModelBase{
|
|||
|
||||
await Task.WhenAll(downloadTasks);
|
||||
}
|
||||
|
||||
|
||||
public void SetStorageProvider(IStorageProvider storageProvider){
|
||||
_storageProvider = storageProvider ?? throw new ArgumentNullException(nameof(storageProvider));
|
||||
}
|
||||
}
|
||||
|
||||
public class HistoryPageProperties(){
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
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;
|
||||
|
|
@ -13,7 +10,6 @@ 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;
|
||||
using CRD.ViewModels.Utils;
|
||||
|
|
@ -21,7 +17,6 @@ using CRD.Views;
|
|||
using CRD.Views.Utils;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using ReactiveUI;
|
||||
using Path = Avalonia.Controls.Shapes.Path;
|
||||
|
||||
namespace CRD.ViewModels;
|
||||
|
||||
|
|
@ -53,6 +48,9 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
public bool _seriesFolderPathExists;
|
||||
|
||||
public SeriesPageViewModel(){
|
||||
|
||||
_storageProvider = ProgramManager.Instance.StorageProvider ?? throw new ArgumentNullException(nameof(ProgramManager.Instance.StorageProvider));
|
||||
|
||||
_selectedSeries = CrunchyrollManager.Instance.SelectedSeries;
|
||||
|
||||
if (_selectedSeries.ThumbnailImage == null){
|
||||
|
|
@ -155,11 +153,7 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
|||
|
||||
UpdateSeriesFolderPath();
|
||||
}
|
||||
|
||||
public void SetStorageProvider(IStorageProvider storageProvider){
|
||||
_storageProvider = storageProvider ?? throw new ArgumentNullException(nameof(storageProvider));
|
||||
}
|
||||
|
||||
|
||||
[RelayCommand]
|
||||
public async Task MatchSonarrSeries_Button(){
|
||||
var dialog = new ContentDialog(){
|
||||
|
|
|
|||
|
|
@ -1,904 +1,54 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Styling;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.Ffmpeg_Encoding;
|
||||
using CRD.Utils.Sonarr;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.History;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media.Imaging;
|
||||
using CRD.Downloader.Crunchyroll.ViewModels;
|
||||
using CRD.Downloader.Crunchyroll.Views;
|
||||
using CRD.ViewModels.Utils;
|
||||
using CRD.Views.Utils;
|
||||
using FluentAvalonia.Styling;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Image = Avalonia.Controls.Image;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace CRD.ViewModels;
|
||||
|
||||
public partial class SettingsPageViewModel : ViewModelBase{
|
||||
[ObservableProperty]
|
||||
private string _currentVersion;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _downloadVideo = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _downloadAudio = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _downloadChapters = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _addScaledBorderAndShadow;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _includeSignSubs;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _includeCcSubs;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _downloadToTempFolder;
|
||||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem _selectedScaledBorderAndShadow;
|
||||
|
||||
public ObservableCollection<ComboBoxItem> ScaledBorderAndShadow{ get; } = new(){
|
||||
new ComboBoxItem(){ Content = "ScaledBorderAndShadow: yes" },
|
||||
new ComboBoxItem(){ Content = "ScaledBorderAndShadow: no" },
|
||||
};
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _skipMuxing;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _muxToMp4;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _syncTimings;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _defaultSubSigns;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _defaultSubForcedDisplay;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _includeEpisodeDescription;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _downloadVideoForEveryDub;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _keepDubsSeparate;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _skipSubMux;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _history;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _historyAddSpecials;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _historyCountSonarr;
|
||||
|
||||
[ObservableProperty]
|
||||
private double? _leadingNumbers;
|
||||
|
||||
[ObservableProperty]
|
||||
private double? _simultaneousDownloads;
|
||||
|
||||
[ObservableProperty]
|
||||
private double? _downloadSpeed;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _fileName = "";
|
||||
|
||||
[ObservableProperty]
|
||||
private string _fileTitle = "";
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<StringItem> _mkvMergeOptions = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private string _mkvMergeOption = "";
|
||||
|
||||
[ObservableProperty]
|
||||
private string _ffmpegOption = "";
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<StringItem> _ffmpegOptions = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private string _selectedSubs = "all";
|
||||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem _selectedHSLang;
|
||||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem _selectedHistoryLang;
|
||||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem _selectedDescriptionLang;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _selectedDubs = "ja-JP";
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<ListBoxItem> _selectedDubLang = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem _selectedStreamEndpoint;
|
||||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem _selectedDefaultDubLang;
|
||||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem _selectedDefaultSubLang;
|
||||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem? _selectedVideoQuality;
|
||||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem? _selectedAudioQuality;
|
||||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem? _currentAppTheme;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<ListBoxItem> _selectedSubLang = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _useCustomAccent;
|
||||
|
||||
[ObservableProperty]
|
||||
private Color _listBoxColor;
|
||||
|
||||
[ObservableProperty]
|
||||
private Color _customAccentColor = Colors.SlateBlue;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _sonarrHost = "localhost";
|
||||
|
||||
[ObservableProperty]
|
||||
private string _sonarrPort = "8989";
|
||||
|
||||
[ObservableProperty]
|
||||
private string _sonarrApiKey = "";
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _sonarrUseSsl = false;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _sonarrUseSonarrNumbering = false;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _logMode = false;
|
||||
|
||||
public ObservableCollection<Color> PredefinedColors{ get; } = new(){
|
||||
Color.FromRgb(255, 185, 0),
|
||||
Color.FromRgb(255, 140, 0),
|
||||
Color.FromRgb(247, 99, 12),
|
||||
Color.FromRgb(202, 80, 16),
|
||||
Color.FromRgb(218, 59, 1),
|
||||
Color.FromRgb(239, 105, 80),
|
||||
Color.FromRgb(209, 52, 56),
|
||||
Color.FromRgb(255, 67, 67),
|
||||
Color.FromRgb(231, 72, 86),
|
||||
Color.FromRgb(232, 17, 35),
|
||||
Color.FromRgb(234, 0, 94),
|
||||
Color.FromRgb(195, 0, 82),
|
||||
Color.FromRgb(227, 0, 140),
|
||||
Color.FromRgb(191, 0, 119),
|
||||
Color.FromRgb(194, 57, 179),
|
||||
Color.FromRgb(154, 0, 137),
|
||||
Color.FromRgb(0, 120, 212),
|
||||
Color.FromRgb(0, 99, 177),
|
||||
Color.FromRgb(142, 140, 216),
|
||||
Color.FromRgb(107, 105, 214),
|
||||
Colors.SlateBlue,
|
||||
Color.FromRgb(135, 100, 184),
|
||||
Color.FromRgb(116, 77, 169),
|
||||
Color.FromRgb(177, 70, 194),
|
||||
Color.FromRgb(136, 23, 152),
|
||||
Color.FromRgb(0, 153, 188),
|
||||
Color.FromRgb(45, 125, 154),
|
||||
Color.FromRgb(0, 183, 195),
|
||||
Color.FromRgb(3, 131, 135),
|
||||
Color.FromRgb(0, 178, 148),
|
||||
Color.FromRgb(1, 133, 116),
|
||||
Color.FromRgb(0, 204, 106),
|
||||
Color.FromRgb(16, 137, 62),
|
||||
Color.FromRgb(122, 117, 116),
|
||||
Color.FromRgb(93, 90, 88),
|
||||
Color.FromRgb(104, 118, 138),
|
||||
Color.FromRgb(81, 92, 107),
|
||||
Color.FromRgb(86, 124, 115),
|
||||
Color.FromRgb(72, 104, 96),
|
||||
Color.FromRgb(73, 130, 5),
|
||||
Color.FromRgb(16, 124, 16),
|
||||
Color.FromRgb(118, 118, 118),
|
||||
Color.FromRgb(76, 74, 72),
|
||||
Color.FromRgb(105, 121, 126),
|
||||
Color.FromRgb(74, 84, 89),
|
||||
Color.FromRgb(100, 124, 100),
|
||||
Color.FromRgb(82, 94, 84),
|
||||
Color.FromRgb(132, 117, 69),
|
||||
Color.FromRgb(126, 115, 95)
|
||||
};
|
||||
|
||||
public ObservableCollection<ComboBoxItem> AppThemes{ get; } = new(){
|
||||
new ComboBoxItem(){ Content = "System" },
|
||||
new ComboBoxItem(){ Content = "Light" },
|
||||
new ComboBoxItem(){ Content = "Dark" },
|
||||
};
|
||||
|
||||
public ObservableCollection<ComboBoxItem> VideoQualityList{ get; } = new(){
|
||||
new ComboBoxItem(){ Content = "best" },
|
||||
new ComboBoxItem(){ Content = "1080" },
|
||||
new ComboBoxItem(){ Content = "720" },
|
||||
new ComboBoxItem(){ Content = "480" },
|
||||
new ComboBoxItem(){ Content = "360" },
|
||||
new ComboBoxItem(){ Content = "240" },
|
||||
new ComboBoxItem(){ Content = "worst" },
|
||||
};
|
||||
|
||||
public ObservableCollection<ComboBoxItem> AudioQualityList{ get; } = new(){
|
||||
new ComboBoxItem(){ Content = "best" },
|
||||
new ComboBoxItem(){ Content = "128kB/s" },
|
||||
new ComboBoxItem(){ Content = "96kB/s" },
|
||||
new ComboBoxItem(){ Content = "64kB/s" },
|
||||
new ComboBoxItem(){ Content = "worst" },
|
||||
};
|
||||
|
||||
public ObservableCollection<ComboBoxItem> HardSubLangList{ get; } = new(){
|
||||
new ComboBoxItem(){ Content = "none" },
|
||||
};
|
||||
|
||||
public ObservableCollection<ComboBoxItem> HistoryLangList{ get; } = new(){
|
||||
new ComboBoxItem(){ Content = "default" },
|
||||
new ComboBoxItem(){ Content = "de-DE" },
|
||||
new ComboBoxItem(){ Content = "en-US" },
|
||||
new ComboBoxItem(){ Content = "es-419" },
|
||||
new ComboBoxItem(){ Content = "es-ES" },
|
||||
new ComboBoxItem(){ Content = "fr-FR" },
|
||||
new ComboBoxItem(){ Content = "it-IT" },
|
||||
new ComboBoxItem(){ Content = "pt-BR" },
|
||||
new ComboBoxItem(){ Content = "pt-PT" },
|
||||
new ComboBoxItem(){ Content = "ru-RU" },
|
||||
new ComboBoxItem(){ Content = "hi-IN" },
|
||||
new ComboBoxItem(){ Content = "ar-SA" },
|
||||
};
|
||||
|
||||
public ObservableCollection<ComboBoxItem> DescriptionLangList{ get; } = new(){
|
||||
new ComboBoxItem(){ Content = "default" },
|
||||
new ComboBoxItem(){ Content = "de-DE" },
|
||||
new ComboBoxItem(){ Content = "en-US" },
|
||||
new ComboBoxItem(){ Content = "es-419" },
|
||||
new ComboBoxItem(){ Content = "es-ES" },
|
||||
new ComboBoxItem(){ Content = "fr-FR" },
|
||||
new ComboBoxItem(){ Content = "it-IT" },
|
||||
new ComboBoxItem(){ Content = "pt-BR" },
|
||||
new ComboBoxItem(){ Content = "pt-PT" },
|
||||
new ComboBoxItem(){ Content = "ru-RU" },
|
||||
new ComboBoxItem(){ Content = "hi-IN" },
|
||||
new ComboBoxItem(){ Content = "ar-SA" },
|
||||
};
|
||||
|
||||
public ObservableCollection<ListBoxItem> DubLangList{ get; } = new(){
|
||||
};
|
||||
|
||||
|
||||
public ObservableCollection<ComboBoxItem> DefaultDubLangList{ get; } = new(){
|
||||
};
|
||||
|
||||
public ObservableCollection<ComboBoxItem> DefaultSubLangList{ get; } = new(){
|
||||
};
|
||||
|
||||
|
||||
public ObservableCollection<ListBoxItem> SubLangList{ get; } = new(){
|
||||
new ListBoxItem(){ Content = "all" },
|
||||
new ListBoxItem(){ Content = "none" },
|
||||
};
|
||||
|
||||
public ObservableCollection<ComboBoxItem> StreamEndpoints{ get; } = new(){
|
||||
new ComboBoxItem(){ Content = "web/firefox" },
|
||||
new ComboBoxItem(){ Content = "console/switch" },
|
||||
new ComboBoxItem(){ Content = "console/ps4" },
|
||||
new ComboBoxItem(){ Content = "console/ps5" },
|
||||
new ComboBoxItem(){ Content = "console/xbox_one" },
|
||||
new ComboBoxItem(){ Content = "web/edge" },
|
||||
// new ComboBoxItem(){ Content = "web/safari" },
|
||||
new ComboBoxItem(){ Content = "web/chrome" },
|
||||
new ComboBoxItem(){ Content = "web/fallback" },
|
||||
// new ComboBoxItem(){ Content = "ios/iphone" },
|
||||
// new ComboBoxItem(){ Content = "ios/ipad" },
|
||||
new ComboBoxItem(){ Content = "android/phone" },
|
||||
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;
|
||||
|
||||
private IStorageProvider _storageProvider;
|
||||
|
||||
public ObservableCollection<TabViewItem> Tabs{ get; } = new();
|
||||
|
||||
private TabViewItem CreateTab(string header, string iconPath, UserControl content, object viewModel){
|
||||
content.DataContext = viewModel;
|
||||
|
||||
Bitmap bitmap = null;
|
||||
try{
|
||||
// Load the image using AssetLoader.Open
|
||||
bitmap = new Bitmap(Avalonia.Platform.AssetLoader.Open(new Uri(iconPath)));
|
||||
} catch (Exception ex){
|
||||
Console.WriteLine($"Error loading image: {ex.Message}");
|
||||
}
|
||||
|
||||
return new TabViewItem{
|
||||
Header = new StackPanel{
|
||||
Orientation = Orientation.Horizontal,
|
||||
Spacing = 5,
|
||||
Children ={
|
||||
new Image{ Source = bitmap, Width = 18, Height = 18 },
|
||||
new TextBlock{ Text = header, FontSize = 16}
|
||||
}
|
||||
},
|
||||
IsClosable = false,
|
||||
Content = content
|
||||
};
|
||||
}
|
||||
|
||||
public SettingsPageViewModel(){
|
||||
var version = Assembly.GetExecutingAssembly().GetName().Version;
|
||||
_currentVersion = $"{version?.Major}.{version?.Minor}.{version?.Build}";
|
||||
|
||||
_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 });
|
||||
DubLangList.Add(new ListBoxItem{ Content = languageItem.CrLocale });
|
||||
DefaultDubLangList.Add(new ComboBoxItem{ Content = languageItem.CrLocale });
|
||||
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];
|
||||
|
||||
ComboBoxItem? historyLang = HistoryLangList.FirstOrDefault(a => a.Content != null && (string)a.Content == options.HistoryLang) ?? null;
|
||||
SelectedHistoryLang = historyLang ?? HistoryLangList[0];
|
||||
|
||||
ComboBoxItem? hsLang = HardSubLangList.FirstOrDefault(a => a.Content != null && (string)a.Content == Languages.Locale2language(options.Hslang).CrLocale) ?? null;
|
||||
SelectedHSLang = hsLang ?? HardSubLangList[0];
|
||||
|
||||
ComboBoxItem? defaultDubLang = DefaultDubLangList.FirstOrDefault(a => a.Content != null && (string)a.Content == (options.DefaultAudio ?? "")) ?? null;
|
||||
SelectedDefaultDubLang = defaultDubLang ?? DefaultDubLangList[0];
|
||||
|
||||
ComboBoxItem? defaultSubLang = DefaultSubLangList.FirstOrDefault(a => a.Content != null && (string)a.Content == (options.DefaultSub ?? "")) ?? null;
|
||||
SelectedDefaultSubLang = defaultSubLang ?? DefaultSubLangList[0];
|
||||
|
||||
ComboBoxItem? streamEndpoint = StreamEndpoints.FirstOrDefault(a => a.Content != null && (string)a.Content == (options.StreamEndpoint ?? "")) ?? null;
|
||||
SelectedStreamEndpoint = streamEndpoint ?? StreamEndpoints[0];
|
||||
|
||||
var softSubLang = SubLangList.Where(a => options.DlSubs.Contains(a.Content)).ToList();
|
||||
|
||||
SelectedSubLang.Clear();
|
||||
foreach (var listBoxItem in softSubLang){
|
||||
SelectedSubLang.Add(listBoxItem);
|
||||
}
|
||||
|
||||
var dubLang = DubLangList.Where(a => options.DubLang.Contains(a.Content)).ToList();
|
||||
|
||||
SelectedDubLang.Clear();
|
||||
foreach (var listBoxItem in dubLang){
|
||||
SelectedDubLang.Add(listBoxItem);
|
||||
}
|
||||
|
||||
var props = options.SonarrProperties;
|
||||
|
||||
if (props != null){
|
||||
SonarrUseSsl = props.UseSsl;
|
||||
SonarrUseSonarrNumbering = props.UseSonarrNumbering;
|
||||
SonarrHost = props.Host + "";
|
||||
SonarrPort = props.Port + "";
|
||||
SonarrApiKey = props.ApiKey + "";
|
||||
}
|
||||
|
||||
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;
|
||||
HistoryCountSonarr = options.HistoryCountSonarr;
|
||||
DownloadSpeed = options.DownloadSpeedLimit;
|
||||
IncludeEpisodeDescription = options.IncludeVideoDescription;
|
||||
FileTitle = options.VideoTitle ?? "";
|
||||
IncludeSignSubs = options.IncludeSignsSubs;
|
||||
IncludeCcSubs = options.IncludeCcSubs;
|
||||
DownloadVideo = !options.Novids;
|
||||
DownloadAudio = !options.Noaudio;
|
||||
DownloadVideoForEveryDub = !options.DlVideoOnce;
|
||||
DownloadToTempFolder = options.DownloadToTempFolder;
|
||||
KeepDubsSeparate = options.KeepDubsSeperate;
|
||||
DownloadChapters = options.Chapters;
|
||||
MuxToMp4 = options.Mp4;
|
||||
SyncTimings = options.SyncTiming;
|
||||
SkipSubMux = options.SkipSubsMux;
|
||||
LeadingNumbers = options.Numbers;
|
||||
FileName = options.FileName;
|
||||
SimultaneousDownloads = options.SimultaneousDownloads;
|
||||
LogMode = options.LogMode;
|
||||
|
||||
ComboBoxItem? qualityAudio = AudioQualityList.FirstOrDefault(a => a.Content != null && (string)a.Content == options.QualityAudio) ?? null;
|
||||
SelectedAudioQuality = qualityAudio ?? AudioQualityList[0];
|
||||
|
||||
ComboBoxItem? qualityVideo = VideoQualityList.FirstOrDefault(a => a.Content != null && (string)a.Content == options.QualityVideo) ?? null;
|
||||
SelectedVideoQuality = qualityVideo ?? VideoQualityList[0];
|
||||
|
||||
ComboBoxItem? theme = AppThemes.FirstOrDefault(a => a.Content != null && (string)a.Content == options.Theme) ?? null;
|
||||
CurrentAppTheme = theme ?? AppThemes[0];
|
||||
|
||||
if (!string.IsNullOrEmpty(options.AccentColor) && options.AccentColor != Application.Current?.PlatformSettings?.GetColorValues().AccentColor1.ToString()){
|
||||
UseCustomAccent = true;
|
||||
}
|
||||
|
||||
History = options.History;
|
||||
|
||||
MkvMergeOptions.Clear();
|
||||
if (options.MkvmergeOptions != null){
|
||||
foreach (var mkvmergeParam in options.MkvmergeOptions){
|
||||
MkvMergeOptions.Add(new StringItem(){ stringValue = mkvmergeParam });
|
||||
}
|
||||
}
|
||||
|
||||
FfmpegOptions.Clear();
|
||||
if (options.FfmpegOptions != null){
|
||||
foreach (var ffmpegParam in options.FfmpegOptions){
|
||||
FfmpegOptions.Add(new StringItem(){ stringValue = ffmpegParam });
|
||||
}
|
||||
}
|
||||
|
||||
var dubs = SelectedDubLang.Select(item => item.Content?.ToString());
|
||||
SelectedDubs = string.Join(", ", dubs) ?? "";
|
||||
|
||||
var subs = SelectedSubLang.Select(item => item.Content?.ToString());
|
||||
SelectedSubs = string.Join(", ", subs) ?? "";
|
||||
|
||||
SelectedSubLang.CollectionChanged += Changes;
|
||||
SelectedDubLang.CollectionChanged += Changes;
|
||||
|
||||
MkvMergeOptions.CollectionChanged += Changes;
|
||||
FfmpegOptions.CollectionChanged += Changes;
|
||||
|
||||
settingsLoaded = true;
|
||||
// Add initial tabs
|
||||
Tabs.Add(CreateTab("General Settings", "avares://CRD/Assets/app_icon.ico", new GeneralSettingsView(), new GeneralSettingsViewModel()));
|
||||
Tabs.Add(CreateTab("Crunchyroll Settings", "avares://CRD/Assets/crunchy_icon_round.png", new CrunchyrollSettingsView(), new CrunchyrollSettingsViewModel()));
|
||||
|
||||
}
|
||||
|
||||
private void UpdateSettings(){
|
||||
if (!settingsLoaded){
|
||||
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;
|
||||
CrunchyrollManager.Instance.CrunOptions.IncludeVideoDescription = IncludeEpisodeDescription;
|
||||
CrunchyrollManager.Instance.CrunOptions.HistoryAddSpecials = HistoryAddSpecials;
|
||||
CrunchyrollManager.Instance.CrunOptions.HistoryCountSonarr = HistoryCountSonarr;
|
||||
CrunchyrollManager.Instance.CrunOptions.VideoTitle = FileTitle;
|
||||
CrunchyrollManager.Instance.CrunOptions.Novids = !DownloadVideo;
|
||||
CrunchyrollManager.Instance.CrunOptions.Noaudio = !DownloadAudio;
|
||||
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;
|
||||
CrunchyrollManager.Instance.CrunOptions.Numbers = Math.Clamp((int)(LeadingNumbers ?? 0), 0, 10);
|
||||
CrunchyrollManager.Instance.CrunOptions.FileName = FileName;
|
||||
CrunchyrollManager.Instance.CrunOptions.IncludeSignsSubs = IncludeSignSubs;
|
||||
CrunchyrollManager.Instance.CrunOptions.IncludeCcSubs = IncludeCcSubs;
|
||||
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>();
|
||||
foreach (var listBoxItem in SelectedSubLang){
|
||||
softSubs.Add(listBoxItem.Content + "");
|
||||
}
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.DlSubs = softSubs;
|
||||
|
||||
string descLang = SelectedDescriptionLang.Content + "";
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.DescriptionLang = descLang != "default" ? descLang : CrunchyrollManager.Instance.DefaultLocale;
|
||||
|
||||
string historyLang = SelectedHistoryLang.Content + "";
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.HistoryLang = historyLang != "default" ? historyLang : CrunchyrollManager.Instance.DefaultLocale;
|
||||
|
||||
string hslang = SelectedHSLang.Content + "";
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.Hslang = hslang != "none" ? Languages.FindLang(hslang).Locale : hslang;
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.DefaultAudio = SelectedDefaultDubLang.Content + "";
|
||||
CrunchyrollManager.Instance.CrunOptions.DefaultSub = SelectedDefaultSubLang.Content + "";
|
||||
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.StreamEndpoint = SelectedStreamEndpoint.Content + "";
|
||||
|
||||
List<string> dubLangs = new List<string>();
|
||||
foreach (var listBoxItem in SelectedDubLang){
|
||||
dubLangs.Add(listBoxItem.Content + "");
|
||||
}
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.DubLang = dubLangs;
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.QualityAudio = SelectedAudioQuality?.Content + "";
|
||||
CrunchyrollManager.Instance.CrunOptions.QualityVideo = SelectedVideoQuality?.Content + "";
|
||||
CrunchyrollManager.Instance.CrunOptions.Theme = CurrentAppTheme?.Content + "";
|
||||
|
||||
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;
|
||||
|
||||
var props = new SonarrProperties();
|
||||
|
||||
props.UseSsl = SonarrUseSsl;
|
||||
props.UseSonarrNumbering = SonarrUseSonarrNumbering;
|
||||
props.Host = SonarrHost;
|
||||
|
||||
if (int.TryParse(SonarrPort, out var portNumber)){
|
||||
props.Port = portNumber;
|
||||
} else{
|
||||
props.Port = 8989;
|
||||
}
|
||||
|
||||
props.ApiKey = SonarrApiKey;
|
||||
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.SonarrProperties = props;
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.LogMode = LogMode;
|
||||
|
||||
List<string> mkvmergeParams = new List<string>();
|
||||
foreach (var mkvmergeParam in MkvMergeOptions){
|
||||
mkvmergeParams.Add(mkvmergeParam.stringValue);
|
||||
}
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.MkvmergeOptions = mkvmergeParams;
|
||||
|
||||
List<string> ffmpegParams = new List<string>();
|
||||
foreach (var ffmpegParam in FfmpegOptions){
|
||||
ffmpegParams.Add(ffmpegParam.stringValue);
|
||||
}
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.FfmpegOptions = ffmpegParams;
|
||||
|
||||
CfgManager.WriteSettingsToFile();
|
||||
}
|
||||
|
||||
|
||||
private ScaledBorderAndShadowSelection GetScaledBorderAndShadowSelection(){
|
||||
if (!AddScaledBorderAndShadow){
|
||||
return ScaledBorderAndShadowSelection.DontAdd;
|
||||
}
|
||||
|
||||
if (SelectedScaledBorderAndShadow.Content + "" == "ScaledBorderAndShadow: yes"){
|
||||
return ScaledBorderAndShadowSelection.ScaledBorderAndShadowYes;
|
||||
}
|
||||
|
||||
if (SelectedScaledBorderAndShadow.Content + "" == "ScaledBorderAndShadow: no"){
|
||||
return ScaledBorderAndShadowSelection.ScaledBorderAndShadowNo;
|
||||
}
|
||||
|
||||
return ScaledBorderAndShadowSelection.ScaledBorderAndShadowYes;
|
||||
}
|
||||
|
||||
private ComboBoxItem GetScaledBorderAndShadowFromOptions(CrDownloadOptions options){
|
||||
switch (options.SubsAddScaledBorder){
|
||||
case (ScaledBorderAndShadowSelection.ScaledBorderAndShadowYes):
|
||||
return ScaledBorderAndShadow.FirstOrDefault(a => a.Content != null && (string)a.Content == "ScaledBorderAndShadow: yes") ?? ScaledBorderAndShadow[0];
|
||||
case ScaledBorderAndShadowSelection.ScaledBorderAndShadowNo:
|
||||
return ScaledBorderAndShadow.FirstOrDefault(a => a.Content != null && (string)a.Content == "ScaledBorderAndShadow: no") ?? ScaledBorderAndShadow[0];
|
||||
default:
|
||||
return ScaledBorderAndShadow[0];
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void AddMkvMergeParam(){
|
||||
MkvMergeOptions.Add(new StringItem(){ stringValue = MkvMergeOption });
|
||||
MkvMergeOption = "";
|
||||
RaisePropertyChanged(nameof(MkvMergeOptions));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void RemoveMkvMergeParam(StringItem param){
|
||||
MkvMergeOptions.Remove(param);
|
||||
RaisePropertyChanged(nameof(MkvMergeOptions));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void AddFfmpegParam(){
|
||||
FfmpegOptions.Add(new StringItem(){ stringValue = FfmpegOption });
|
||||
FfmpegOption = "";
|
||||
RaisePropertyChanged(nameof(FfmpegOptions));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void RemoveFfmpegParam(StringItem param){
|
||||
FfmpegOptions.Remove(param);
|
||||
RaisePropertyChanged(nameof(FfmpegOptions));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task OpenFolderDialogAsync(){
|
||||
await OpenFolderDialogAsyncInternal(
|
||||
pathSetter: (path) => {
|
||||
CrunchyrollManager.Instance.CrunOptions.DownloadDirPath = path;
|
||||
DownloadDirPath = string.IsNullOrEmpty(path) ? CfgManager.PathVIDEOS_DIR : path;
|
||||
},
|
||||
pathGetter: () => CrunchyrollManager.Instance.CrunOptions.DownloadDirPath,
|
||||
defaultPath: CfgManager.PathVIDEOS_DIR
|
||||
);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task OpenFolderDialogTempFolderAsync(){
|
||||
await OpenFolderDialogAsyncInternal(
|
||||
pathSetter: (path) => {
|
||||
CrunchyrollManager.Instance.CrunOptions.DownloadTempDirPath = path;
|
||||
TempDownloadDirPath = string.IsNullOrEmpty(path) ? CfgManager.PathTEMP_DIR : path;
|
||||
},
|
||||
pathGetter: () => CrunchyrollManager.Instance.CrunOptions.DownloadTempDirPath,
|
||||
defaultPath: CfgManager.PathTEMP_DIR
|
||||
);
|
||||
}
|
||||
|
||||
private async Task OpenFolderDialogAsyncInternal(Action<string> pathSetter, Func<string> pathGetter, string defaultPath){
|
||||
if (_storageProvider == null){
|
||||
Console.Error.WriteLine("StorageProvider must be set before using the dialog.");
|
||||
throw new InvalidOperationException("StorageProvider must be set before using the dialog.");
|
||||
}
|
||||
|
||||
var result = await _storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions{
|
||||
Title = "Select Folder"
|
||||
});
|
||||
|
||||
if (result.Count > 0){
|
||||
var selectedFolder = result[0];
|
||||
Console.WriteLine($"Selected folder: {selectedFolder.Path.LocalPath}");
|
||||
pathSetter(selectedFolder.Path.LocalPath);
|
||||
var finalPath = string.IsNullOrEmpty(pathGetter()) ? defaultPath : pathGetter();
|
||||
pathSetter(finalPath);
|
||||
CfgManager.WriteSettingsToFile();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetStorageProvider(IStorageProvider storageProvider){
|
||||
_storageProvider = storageProvider ?? throw new ArgumentNullException(nameof(storageProvider));
|
||||
}
|
||||
|
||||
partial void OnCurrentAppThemeChanged(ComboBoxItem? value){
|
||||
if (value?.Content?.ToString() == "System"){
|
||||
_faTheme.PreferSystemTheme = true;
|
||||
} else if (value?.Content?.ToString() == "Dark"){
|
||||
_faTheme.PreferSystemTheme = false;
|
||||
Application.Current.RequestedThemeVariant = ThemeVariant.Dark;
|
||||
} else{
|
||||
_faTheme.PreferSystemTheme = false;
|
||||
Application.Current.RequestedThemeVariant = ThemeVariant.Light;
|
||||
}
|
||||
|
||||
UpdateSettings();
|
||||
}
|
||||
|
||||
partial void OnUseCustomAccentChanged(bool value){
|
||||
if (value){
|
||||
if (_faTheme.TryGetResource("SystemAccentColor", null, out var curColor)){
|
||||
CustomAccentColor = (Color)curColor;
|
||||
ListBoxColor = CustomAccentColor;
|
||||
|
||||
RaisePropertyChanged(nameof(CustomAccentColor));
|
||||
RaisePropertyChanged(nameof(ListBoxColor));
|
||||
}
|
||||
} else{
|
||||
CustomAccentColor = default;
|
||||
ListBoxColor = default;
|
||||
var color = Application.Current?.PlatformSettings?.GetColorValues().AccentColor1 ?? Colors.SlateBlue;
|
||||
UpdateAppAccentColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnListBoxColorChanged(Color value){
|
||||
if (value != null){
|
||||
CustomAccentColor = value;
|
||||
RaisePropertyChanged(nameof(CustomAccentColor));
|
||||
|
||||
UpdateAppAccentColor(value);
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnCustomAccentColorChanged(Color value){
|
||||
ListBoxColor = value;
|
||||
RaisePropertyChanged(nameof(ListBoxColor));
|
||||
UpdateAppAccentColor(value);
|
||||
}
|
||||
|
||||
private void UpdateAppAccentColor(Color? color){
|
||||
_faTheme.CustomAccentColor = color;
|
||||
UpdateSettings();
|
||||
}
|
||||
|
||||
private void Changes(object? sender, NotifyCollectionChangedEventArgs e){
|
||||
UpdateSettings();
|
||||
|
||||
var dubs = SelectedDubLang.Select(item => item.Content?.ToString());
|
||||
SelectedDubs = string.Join(", ", dubs) ?? "";
|
||||
|
||||
var subs = SelectedSubLang.Select(item => item.Content?.ToString());
|
||||
SelectedSubs = string.Join(", ", subs) ?? "";
|
||||
}
|
||||
|
||||
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)){
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateSettings();
|
||||
|
||||
if (e.PropertyName is nameof(History)){
|
||||
if (CrunchyrollManager.Instance.CrunOptions.History){
|
||||
if (File.Exists(CfgManager.PathCrHistory)){
|
||||
var decompressedJson = CfgManager.DecompressJsonFile(CfgManager.PathCrHistory);
|
||||
if (!string.IsNullOrEmpty(decompressedJson)){
|
||||
CrunchyrollManager.Instance.HistoryList = Helpers.Deserialize<ObservableCollection<HistorySeries>>(decompressedJson, CrunchyrollManager.Instance.SettingsJsonSerializerSettings) ??
|
||||
new ObservableCollection<HistorySeries>();
|
||||
|
||||
foreach (var historySeries in CrunchyrollManager.Instance.HistoryList){
|
||||
historySeries.Init();
|
||||
foreach (var historySeriesSeason in historySeries.Seasons){
|
||||
historySeriesSeason.Init();
|
||||
}
|
||||
}
|
||||
} else{
|
||||
CrunchyrollManager.Instance.HistoryList =[];
|
||||
}
|
||||
}
|
||||
|
||||
_ = SonarrClient.Instance.RefreshSonarrLite();
|
||||
} else{
|
||||
CrunchyrollManager.Instance.HistoryList =[];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[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){
|
||||
CfgManager.EnableLogMode();
|
||||
} else{
|
||||
CfgManager.DisableLogMode();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
394
CRD/ViewModels/UpcomingSeasonsPageViewModel.cs
Normal file
394
CRD/ViewModels/UpcomingSeasonsPageViewModel.cs
Normal file
|
|
@ -0,0 +1,394 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Threading.Tasks;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.History;
|
||||
using CRD.Views;
|
||||
using Newtonsoft.Json;
|
||||
using ReactiveUI;
|
||||
using JsonSerializer = Newtonsoft.Json.JsonSerializer;
|
||||
|
||||
namespace CRD.ViewModels;
|
||||
|
||||
public partial class UpcomingPageViewModel : ViewModelBase{
|
||||
#region Query
|
||||
|
||||
private string query = @"query (
|
||||
$season: MediaSeason,
|
||||
$year: Int,
|
||||
$format: MediaFormat,
|
||||
$excludeFormat: MediaFormat,
|
||||
$status: MediaStatus,
|
||||
$minEpisodes: Int,
|
||||
$page: Int,
|
||||
){
|
||||
Page(page: $page) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
total
|
||||
}
|
||||
media(
|
||||
season: $season
|
||||
seasonYear: $year
|
||||
format: $format,
|
||||
format_not: $excludeFormat,
|
||||
status: $status,
|
||||
episodes_greater: $minEpisodes,
|
||||
isAdult: false,
|
||||
type: ANIME,
|
||||
sort: TITLE_ENGLISH,
|
||||
) {
|
||||
id
|
||||
idMal
|
||||
title {
|
||||
romaji
|
||||
native
|
||||
english
|
||||
}
|
||||
startDate {
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
endDate {
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
status
|
||||
season
|
||||
format
|
||||
genres
|
||||
synonyms
|
||||
duration
|
||||
popularity
|
||||
episodes
|
||||
source(version: 2)
|
||||
countryOfOrigin
|
||||
hashtag
|
||||
averageScore
|
||||
siteUrl
|
||||
description
|
||||
bannerImage
|
||||
isAdult
|
||||
coverImage {
|
||||
extraLarge
|
||||
color
|
||||
}
|
||||
trailer {
|
||||
id
|
||||
site
|
||||
thumbnail
|
||||
}
|
||||
externalLinks {
|
||||
site
|
||||
icon
|
||||
color
|
||||
url
|
||||
}
|
||||
rankings {
|
||||
rank
|
||||
type
|
||||
season
|
||||
allTime
|
||||
}
|
||||
studios(isMain: true) {
|
||||
nodes {
|
||||
id
|
||||
name
|
||||
siteUrl
|
||||
}
|
||||
}
|
||||
relations {
|
||||
edges {
|
||||
relationType(version: 2)
|
||||
node {
|
||||
id
|
||||
title {
|
||||
romaji
|
||||
native
|
||||
english
|
||||
}
|
||||
siteUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
airingSchedule(
|
||||
notYetAired: true
|
||||
perPage: 2
|
||||
) {
|
||||
nodes {
|
||||
episode
|
||||
airingAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}";
|
||||
|
||||
#endregion
|
||||
|
||||
[ObservableProperty]
|
||||
private AnilistSeries _selectedSeries;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _selectedIndex;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isLoading;
|
||||
|
||||
public ObservableCollection<SeasonViewModel> Seasons{ get; set; } =[];
|
||||
|
||||
public ObservableCollection<AnilistSeries> SelectedSeason{ get; set; } =[];
|
||||
|
||||
private SeasonViewModel currentSelection;
|
||||
|
||||
public UpcomingPageViewModel(){
|
||||
LoadSeasons();
|
||||
}
|
||||
|
||||
private async void LoadSeasons(){
|
||||
Seasons = GetTargetSeasonsAndYears();
|
||||
|
||||
currentSelection = Seasons.Last();
|
||||
currentSelection.IsSelected = true;
|
||||
|
||||
var list = await GetSeriesForSeason(currentSelection.Season, currentSelection.Year, false);
|
||||
SelectedSeason.Clear();
|
||||
foreach (var anilistSeries in list){
|
||||
SelectedSeason.Add(anilistSeries);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task SelectSeasonCommand(SeasonViewModel selectedSeason){
|
||||
currentSelection.IsSelected = false;
|
||||
currentSelection = selectedSeason;
|
||||
currentSelection.IsSelected = true;
|
||||
|
||||
var list = await GetSeriesForSeason(currentSelection.Season, currentSelection.Year, false);
|
||||
SelectedSeason.Clear();
|
||||
foreach (var anilistSeries in list){
|
||||
SelectedSeason.Add(anilistSeries);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void OpenTrailer(AnilistSeries series){
|
||||
if (series.Trailer.Site.Equals("youtube")){
|
||||
var url = "https://www.youtube.com/watch?v=" + series.Trailer.Id; // Replace with your video URL
|
||||
Process.Start(new ProcessStartInfo{
|
||||
FileName = url,
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async void AddToHistory(AnilistSeries series){
|
||||
if (!string.IsNullOrEmpty(series.CrunchyrollID)){
|
||||
if (CrunchyrollManager.Instance.CrunOptions.History){
|
||||
series.IsInHistory = true;
|
||||
RaisePropertyChanged(nameof(series.IsInHistory));
|
||||
var sucess = await CrunchyrollManager.Instance.History.CRUpdateSeries(series.CrunchyrollID, "");
|
||||
series.IsInHistory = sucess;
|
||||
RaisePropertyChanged(nameof(series.IsInHistory));
|
||||
|
||||
if (sucess){
|
||||
MessageBus.Current.SendMessage(new ToastMessage($"Series added to History", ToastType.Information, 3));
|
||||
} else{
|
||||
MessageBus.Current.SendMessage(new ToastMessage($"Series couldn't get added to History\n(maybe not available in your region)", ToastType.Error, 3));
|
||||
}
|
||||
} else{
|
||||
MessageBus.Current.SendMessage(new ToastMessage($"Series couldn't get added to History", ToastType.Error, 3));
|
||||
}
|
||||
} else{
|
||||
MessageBus.Current.SendMessage(new ToastMessage($"Series couldn't get added to History", ToastType.Error, 3));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<List<AnilistSeries>> GetSeriesForSeason(string season, int year, bool forceRefresh){
|
||||
if (ProgramManager.Instance.AnilistSeasons.ContainsKey(season + year) && !forceRefresh){
|
||||
return ProgramManager.Instance.AnilistSeasons[season + year];
|
||||
}
|
||||
|
||||
IsLoading = true;
|
||||
|
||||
var variables = new{
|
||||
season,
|
||||
year,
|
||||
format = "TV",
|
||||
page = 1
|
||||
};
|
||||
|
||||
var payload = new{
|
||||
query,
|
||||
variables
|
||||
};
|
||||
|
||||
string jsonPayload = JsonConvert.SerializeObject(payload, Formatting.Indented);
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, ApiUrls.Anilist);
|
||||
request.Content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
|
||||
|
||||
var response = await HttpClientReq.Instance.SendHttpRequest(request);
|
||||
|
||||
if (!response.IsOk){
|
||||
Console.Error.WriteLine($"Anilist Request Failed for {season} {year}");
|
||||
return[];
|
||||
}
|
||||
|
||||
AniListResponse aniListResponse = Helpers.Deserialize<AniListResponse>(response.ResponseContent, CrunchyrollManager.Instance.SettingsJsonSerializerSettings) ?? new AniListResponse();
|
||||
|
||||
var list = aniListResponse.Data?.Page?.Media ??[];
|
||||
|
||||
list = list.Where(ele => ele.ExternalLinks != null && ele.ExternalLinks.Any(external =>
|
||||
string.Equals(external.Site, "Crunchyroll", StringComparison.OrdinalIgnoreCase))).ToList();
|
||||
|
||||
|
||||
foreach (var anilistEle in list){
|
||||
anilistEle.ThumbnailImage = await Helpers.LoadImage(anilistEle.CoverImage.ExtraLarge, 185, 265);
|
||||
anilistEle.Description = anilistEle.Description
|
||||
.Replace("<i>", "")
|
||||
.Replace("</i>", "")
|
||||
.Replace("<BR>", "")
|
||||
.Replace("<br>", "");
|
||||
|
||||
|
||||
if (anilistEle.ExternalLinks != null){
|
||||
var url = anilistEle.ExternalLinks.First(external =>
|
||||
string.Equals(external.Site, "Crunchyroll", StringComparison.OrdinalIgnoreCase)).Url;
|
||||
|
||||
string pattern = @"series\/([^\/]+)";
|
||||
|
||||
Match match = Regex.Match(url, pattern);
|
||||
if (match.Success){
|
||||
anilistEle.CrunchyrollID = match.Groups[1].Value;
|
||||
anilistEle.HasCrID = true;
|
||||
|
||||
if (CrunchyrollManager.Instance.CrunOptions.History){
|
||||
var historyIDs = new HashSet<string>(CrunchyrollManager.Instance.HistoryList.Select(item => item.SeriesId ?? ""));
|
||||
|
||||
if (historyIDs.Contains(anilistEle.CrunchyrollID)){
|
||||
anilistEle.IsInHistory = true;
|
||||
}
|
||||
}
|
||||
} else{
|
||||
Uri uri = new Uri(url);
|
||||
|
||||
if (uri.Host == "www.crunchyroll.com"
|
||||
&& uri.AbsolutePath != "/"
|
||||
&& (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)){
|
||||
HttpRequestMessage getUrlRequest = new HttpRequestMessage(HttpMethod.Head, url);
|
||||
|
||||
string? finalUrl = "";
|
||||
|
||||
try{
|
||||
HttpResponseMessage getUrlResponse = await HttpClientReq.Instance.GetHttpClient().SendAsync(getUrlRequest);
|
||||
|
||||
finalUrl = getUrlResponse.RequestMessage?.RequestUri?.ToString();
|
||||
|
||||
} catch (Exception ex){
|
||||
Console.WriteLine($"Error: {ex.Message}");
|
||||
}
|
||||
|
||||
Match match2 = Regex.Match(finalUrl ?? string.Empty, pattern);
|
||||
if (match2.Success){
|
||||
anilistEle.CrunchyrollID = match2.Groups[1].Value;
|
||||
anilistEle.HasCrID = true;
|
||||
|
||||
if (CrunchyrollManager.Instance.CrunOptions.History){
|
||||
var historyIDs = new HashSet<string>(CrunchyrollManager.Instance.HistoryList.Select(item => item.SeriesId ?? ""));
|
||||
|
||||
if (historyIDs.Contains(anilistEle.CrunchyrollID)){
|
||||
anilistEle.IsInHistory = true;
|
||||
}
|
||||
}
|
||||
} else{
|
||||
anilistEle.CrunchyrollID = "";
|
||||
anilistEle.HasCrID = false;
|
||||
}
|
||||
} else{
|
||||
anilistEle.CrunchyrollID = "";
|
||||
anilistEle.HasCrID = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ProgramManager.Instance.AnilistSeasons[season + year] = list;
|
||||
|
||||
IsLoading = false;
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private ObservableCollection<SeasonViewModel> GetTargetSeasonsAndYears(){
|
||||
DateTime now = DateTime.Now;
|
||||
int currentMonth = now.Month;
|
||||
int currentYear = now.Year;
|
||||
|
||||
string currentSeason;
|
||||
if (currentMonth >= 1 && currentMonth <= 3)
|
||||
currentSeason = "WINTER";
|
||||
else if (currentMonth >= 4 && currentMonth <= 6)
|
||||
currentSeason = "SPRING";
|
||||
else if (currentMonth >= 7 && currentMonth <= 9)
|
||||
currentSeason = "SUMMER";
|
||||
else
|
||||
currentSeason = "FALL";
|
||||
|
||||
|
||||
var seasons = new List<string>{ "WINTER", "SPRING", "SUMMER", "FALL" };
|
||||
|
||||
int currentSeasonIndex = seasons.IndexOf(currentSeason);
|
||||
|
||||
var targetSeasons = new ObservableCollection<SeasonViewModel>();
|
||||
|
||||
// Includes: -2 (two seasons ago), -1 (previous), 0 (current), 1 (next)
|
||||
for (int i = -2; i <= 1; i++){
|
||||
int targetIndex = (currentSeasonIndex + i + 4) % 4;
|
||||
string targetSeason = seasons[targetIndex];
|
||||
int targetYear = currentYear;
|
||||
|
||||
|
||||
if (i < 0 && targetIndex == 3){
|
||||
targetYear--;
|
||||
} else if (i > 0 && targetIndex == 0){
|
||||
targetYear++;
|
||||
}
|
||||
|
||||
|
||||
targetSeasons.Add(new SeasonViewModel(){ Season = targetSeason, Year = targetYear });
|
||||
}
|
||||
|
||||
return targetSeasons;
|
||||
}
|
||||
|
||||
public void SelectionChangedOfSeries(AnilistSeries? value){
|
||||
SelectedSeries = null;
|
||||
SelectedIndex = -1;
|
||||
}
|
||||
|
||||
partial void OnSelectedSeriesChanged(AnilistSeries? value){
|
||||
SelectionChangedOfSeries(value);
|
||||
}
|
||||
}
|
||||
42
CRD/ViewModels/Utils/ContentDialogDropdownSelectViewModel.cs
Normal file
42
CRD/ViewModels/Utils/ContentDialogDropdownSelectViewModel.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CRD.Utils.Structs;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
|
||||
namespace CRD.ViewModels.Utils;
|
||||
|
||||
public partial class ContentDialogDropdownSelectViewModel : ViewModelBase{
|
||||
private readonly ContentDialog dialog;
|
||||
|
||||
public ObservableCollection<StringItem> DropDownItemList{ get; } = new(){ };
|
||||
|
||||
[ObservableProperty]
|
||||
private StringItem _selectedDropdownItem = new StringItem();
|
||||
|
||||
[ObservableProperty]
|
||||
private string _episodeInfo;
|
||||
|
||||
public ContentDialogDropdownSelectViewModel(ContentDialog dialog, string episodeInfo, List<string> dropdownItems){
|
||||
if (dialog is null){
|
||||
throw new ArgumentNullException(nameof(dialog));
|
||||
}
|
||||
|
||||
this.dialog = dialog;
|
||||
dialog.Closed += DialogOnClosed;
|
||||
dialog.PrimaryButtonClick += SaveButton;
|
||||
EpisodeInfo = episodeInfo;
|
||||
foreach (var dropdownItem in dropdownItems){
|
||||
DropDownItemList.Add(new StringItem(){ stringValue = dropdownItem });
|
||||
}
|
||||
}
|
||||
|
||||
private async void SaveButton(ContentDialog sender, ContentDialogButtonClickEventArgs args){
|
||||
dialog.PrimaryButtonClick -= SaveButton;
|
||||
}
|
||||
|
||||
private void DialogOnClosed(ContentDialog sender, ContentDialogClosedEventArgs args){
|
||||
dialog.Closed -= DialogOnClosed;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
|
|
|||
516
CRD/ViewModels/Utils/GeneralSettingsViewModel.cs
Normal file
516
CRD/ViewModels/Utils/GeneralSettingsViewModel.cs
Normal file
|
|
@ -0,0 +1,516 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
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.Sonarr;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.History;
|
||||
using FluentAvalonia.Styling;
|
||||
|
||||
namespace CRD.ViewModels.Utils;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
public partial class GeneralSettingsViewModel : ViewModelBase{
|
||||
[ObservableProperty]
|
||||
private string _currentVersion;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _downloadToTempFolder;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _history;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _historyAddSpecials;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _historyCountSonarr;
|
||||
|
||||
[ObservableProperty]
|
||||
private double? _simultaneousDownloads;
|
||||
|
||||
[ObservableProperty]
|
||||
private double? _downloadSpeed;
|
||||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem _selectedHistoryLang;
|
||||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem? _currentAppTheme;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _useCustomAccent;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _backgroundImagePath;
|
||||
|
||||
[ObservableProperty]
|
||||
private double? _backgroundImageOpacity;
|
||||
|
||||
[ObservableProperty]
|
||||
private double? _backgroundImageBlurRadius;
|
||||
|
||||
[ObservableProperty]
|
||||
private Color _listBoxColor;
|
||||
|
||||
[ObservableProperty]
|
||||
private Color _customAccentColor = Colors.SlateBlue;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _sonarrHost = "localhost";
|
||||
|
||||
[ObservableProperty]
|
||||
private string _sonarrPort = "8989";
|
||||
|
||||
[ObservableProperty]
|
||||
private string _sonarrApiKey = "";
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _sonarrUseSsl = false;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _sonarrUseSonarrNumbering = false;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _logMode = false;
|
||||
|
||||
public ObservableCollection<Color> PredefinedColors{ get; } = new(){
|
||||
Color.FromRgb(255, 185, 0),
|
||||
Color.FromRgb(255, 140, 0),
|
||||
Color.FromRgb(247, 99, 12),
|
||||
Color.FromRgb(202, 80, 16),
|
||||
Color.FromRgb(218, 59, 1),
|
||||
Color.FromRgb(239, 105, 80),
|
||||
Color.FromRgb(209, 52, 56),
|
||||
Color.FromRgb(255, 67, 67),
|
||||
Color.FromRgb(231, 72, 86),
|
||||
Color.FromRgb(232, 17, 35),
|
||||
Color.FromRgb(234, 0, 94),
|
||||
Color.FromRgb(195, 0, 82),
|
||||
Color.FromRgb(227, 0, 140),
|
||||
Color.FromRgb(191, 0, 119),
|
||||
Color.FromRgb(194, 57, 179),
|
||||
Color.FromRgb(154, 0, 137),
|
||||
Color.FromRgb(0, 120, 212),
|
||||
Color.FromRgb(0, 99, 177),
|
||||
Color.FromRgb(142, 140, 216),
|
||||
Color.FromRgb(107, 105, 214),
|
||||
Colors.SlateBlue,
|
||||
Color.FromRgb(135, 100, 184),
|
||||
Color.FromRgb(116, 77, 169),
|
||||
Color.FromRgb(177, 70, 194),
|
||||
Color.FromRgb(136, 23, 152),
|
||||
Color.FromRgb(0, 153, 188),
|
||||
Color.FromRgb(45, 125, 154),
|
||||
Color.FromRgb(0, 183, 195),
|
||||
Color.FromRgb(3, 131, 135),
|
||||
Color.FromRgb(0, 178, 148),
|
||||
Color.FromRgb(1, 133, 116),
|
||||
Color.FromRgb(0, 204, 106),
|
||||
Color.FromRgb(16, 137, 62),
|
||||
Color.FromRgb(122, 117, 116),
|
||||
Color.FromRgb(93, 90, 88),
|
||||
Color.FromRgb(104, 118, 138),
|
||||
Color.FromRgb(81, 92, 107),
|
||||
Color.FromRgb(86, 124, 115),
|
||||
Color.FromRgb(72, 104, 96),
|
||||
Color.FromRgb(73, 130, 5),
|
||||
Color.FromRgb(16, 124, 16),
|
||||
Color.FromRgb(118, 118, 118),
|
||||
Color.FromRgb(76, 74, 72),
|
||||
Color.FromRgb(105, 121, 126),
|
||||
Color.FromRgb(74, 84, 89),
|
||||
Color.FromRgb(100, 124, 100),
|
||||
Color.FromRgb(82, 94, 84),
|
||||
Color.FromRgb(132, 117, 69),
|
||||
Color.FromRgb(126, 115, 95)
|
||||
};
|
||||
|
||||
public ObservableCollection<ComboBoxItem> AppThemes{ get; } = new(){
|
||||
new ComboBoxItem(){ Content = "System" },
|
||||
new ComboBoxItem(){ Content = "Light" },
|
||||
new ComboBoxItem(){ Content = "Dark" },
|
||||
};
|
||||
|
||||
public ObservableCollection<ComboBoxItem> HistoryLangList{ get; } = new(){
|
||||
new ComboBoxItem(){ Content = "default" },
|
||||
new ComboBoxItem(){ Content = "de-DE" },
|
||||
new ComboBoxItem(){ Content = "en-US" },
|
||||
new ComboBoxItem(){ Content = "es-419" },
|
||||
new ComboBoxItem(){ Content = "es-ES" },
|
||||
new ComboBoxItem(){ Content = "fr-FR" },
|
||||
new ComboBoxItem(){ Content = "it-IT" },
|
||||
new ComboBoxItem(){ Content = "pt-BR" },
|
||||
new ComboBoxItem(){ Content = "pt-PT" },
|
||||
new ComboBoxItem(){ Content = "ru-RU" },
|
||||
new ComboBoxItem(){ Content = "hi-IN" },
|
||||
new ComboBoxItem(){ Content = "ar-SA" },
|
||||
};
|
||||
|
||||
[ObservableProperty]
|
||||
private string _downloadDirPath;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _proxyEnabled;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _proxyHost;
|
||||
|
||||
[ObservableProperty]
|
||||
private double? _proxyPort;
|
||||
|
||||
|
||||
[ObservableProperty]
|
||||
private string _tempDownloadDirPath;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _currentIp = "";
|
||||
|
||||
private readonly FluentAvaloniaTheme _faTheme;
|
||||
|
||||
private bool settingsLoaded;
|
||||
|
||||
private IStorageProvider _storageProvider;
|
||||
|
||||
public GeneralSettingsViewModel(){
|
||||
_storageProvider = ProgramManager.Instance.StorageProvider ?? throw new ArgumentNullException(nameof(ProgramManager.Instance.StorageProvider));
|
||||
|
||||
var version = Assembly.GetExecutingAssembly().GetName().Version;
|
||||
_currentVersion = $"{version?.Major}.{version?.Minor}.{version?.Build}";
|
||||
|
||||
_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;
|
||||
}
|
||||
|
||||
CrDownloadOptions options = CrunchyrollManager.Instance.CrunOptions;
|
||||
|
||||
BackgroundImageBlurRadius = options.BackgroundImageBlurRadius;
|
||||
BackgroundImageOpacity = options.BackgroundImageOpacity;
|
||||
BackgroundImagePath = options.BackgroundImagePath ?? string.Empty;
|
||||
DownloadDirPath = string.IsNullOrEmpty(options.DownloadDirPath) ? CfgManager.PathVIDEOS_DIR : options.DownloadDirPath;
|
||||
TempDownloadDirPath = string.IsNullOrEmpty(options.DownloadTempDirPath) ? CfgManager.PathTEMP_DIR : options.DownloadTempDirPath;
|
||||
|
||||
ComboBoxItem? historyLang = HistoryLangList.FirstOrDefault(a => a.Content != null && (string)a.Content == options.HistoryLang) ?? null;
|
||||
SelectedHistoryLang = historyLang ?? HistoryLangList[0];
|
||||
|
||||
var props = options.SonarrProperties;
|
||||
|
||||
if (props != null){
|
||||
SonarrUseSsl = props.UseSsl;
|
||||
SonarrUseSonarrNumbering = props.UseSonarrNumbering;
|
||||
SonarrHost = props.Host + "";
|
||||
SonarrPort = props.Port + "";
|
||||
SonarrApiKey = props.ApiKey + "";
|
||||
}
|
||||
|
||||
ProxyEnabled = options.ProxyEnabled;
|
||||
ProxyHost = options.ProxyHost ?? "";
|
||||
ProxyPort = options.ProxyPort;
|
||||
HistoryAddSpecials = options.HistoryAddSpecials;
|
||||
HistoryCountSonarr = options.HistoryCountSonarr;
|
||||
DownloadSpeed = options.DownloadSpeedLimit;
|
||||
DownloadToTempFolder = options.DownloadToTempFolder;
|
||||
SimultaneousDownloads = options.SimultaneousDownloads;
|
||||
LogMode = options.LogMode;
|
||||
|
||||
ComboBoxItem? theme = AppThemes.FirstOrDefault(a => a.Content != null && (string)a.Content == options.Theme) ?? null;
|
||||
CurrentAppTheme = theme ?? AppThemes[0];
|
||||
|
||||
if (!string.IsNullOrEmpty(options.AccentColor) && options.AccentColor != Application.Current?.PlatformSettings?.GetColorValues().AccentColor1.ToString()){
|
||||
UseCustomAccent = true;
|
||||
}
|
||||
|
||||
History = options.History;
|
||||
|
||||
settingsLoaded = true;
|
||||
}
|
||||
|
||||
private void UpdateSettings(){
|
||||
if (!settingsLoaded){
|
||||
return;
|
||||
}
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.BackgroundImageBlurRadius = Math.Clamp((BackgroundImageBlurRadius ?? 0), 0, 40);
|
||||
CrunchyrollManager.Instance.CrunOptions.BackgroundImageOpacity = Math.Clamp((BackgroundImageOpacity ?? 0), 0, 1);
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.DownloadToTempFolder = DownloadToTempFolder;
|
||||
CrunchyrollManager.Instance.CrunOptions.HistoryAddSpecials = HistoryAddSpecials;
|
||||
CrunchyrollManager.Instance.CrunOptions.HistoryCountSonarr = HistoryCountSonarr;
|
||||
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);
|
||||
|
||||
string historyLang = SelectedHistoryLang.Content + "";
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.HistoryLang = historyLang != "default" ? historyLang : CrunchyrollManager.Instance.DefaultLocale;
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.Theme = CurrentAppTheme?.Content + "";
|
||||
|
||||
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;
|
||||
|
||||
var props = new SonarrProperties();
|
||||
|
||||
props.UseSsl = SonarrUseSsl;
|
||||
props.UseSonarrNumbering = SonarrUseSonarrNumbering;
|
||||
props.Host = SonarrHost;
|
||||
|
||||
if (int.TryParse(SonarrPort, out var portNumber)){
|
||||
props.Port = portNumber;
|
||||
} else{
|
||||
props.Port = 8989;
|
||||
}
|
||||
|
||||
props.ApiKey = SonarrApiKey;
|
||||
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.SonarrProperties = props;
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.LogMode = LogMode;
|
||||
|
||||
CfgManager.WriteSettingsToFile();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void ClearDownloadDirPath(){
|
||||
CrunchyrollManager.Instance.CrunOptions.DownloadDirPath = string.Empty;
|
||||
DownloadDirPath = CfgManager.PathVIDEOS_DIR;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void ClearDownloadTempDirPath(){
|
||||
CrunchyrollManager.Instance.CrunOptions.DownloadTempDirPath = string.Empty;
|
||||
TempDownloadDirPath = CfgManager.PathTEMP_DIR;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task OpenFolderDialogAsync(){
|
||||
await OpenFolderDialogAsyncInternal(
|
||||
pathSetter: (path) => {
|
||||
CrunchyrollManager.Instance.CrunOptions.DownloadDirPath = path;
|
||||
DownloadDirPath = string.IsNullOrEmpty(path) ? CfgManager.PathVIDEOS_DIR : path;
|
||||
},
|
||||
pathGetter: () => CrunchyrollManager.Instance.CrunOptions.DownloadDirPath,
|
||||
defaultPath: CfgManager.PathVIDEOS_DIR
|
||||
);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task OpenFolderDialogTempFolderAsync(){
|
||||
await OpenFolderDialogAsyncInternal(
|
||||
pathSetter: (path) => {
|
||||
CrunchyrollManager.Instance.CrunOptions.DownloadTempDirPath = path;
|
||||
TempDownloadDirPath = string.IsNullOrEmpty(path) ? CfgManager.PathTEMP_DIR : path;
|
||||
},
|
||||
pathGetter: () => CrunchyrollManager.Instance.CrunOptions.DownloadTempDirPath,
|
||||
defaultPath: CfgManager.PathTEMP_DIR
|
||||
);
|
||||
}
|
||||
|
||||
private async Task OpenFolderDialogAsyncInternal(Action<string> pathSetter, Func<string> pathGetter, string defaultPath){
|
||||
if (_storageProvider == null){
|
||||
Console.Error.WriteLine("StorageProvider must be set before using the dialog.");
|
||||
throw new InvalidOperationException("StorageProvider must be set before using the dialog.");
|
||||
}
|
||||
|
||||
var result = await _storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions{
|
||||
Title = "Select Folder"
|
||||
});
|
||||
|
||||
if (result.Count > 0){
|
||||
var selectedFolder = result[0];
|
||||
Console.WriteLine($"Selected folder: {selectedFolder.Path.LocalPath}");
|
||||
pathSetter(selectedFolder.Path.LocalPath);
|
||||
var finalPath = string.IsNullOrEmpty(pathGetter()) ? defaultPath : pathGetter();
|
||||
pathSetter(finalPath);
|
||||
CfgManager.WriteSettingsToFile();
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void ClearBackgroundImagePath(){
|
||||
CrunchyrollManager.Instance.CrunOptions.BackgroundImagePath = string.Empty;
|
||||
BackgroundImagePath = string.Empty;
|
||||
Helpers.SetBackgroundImage(string.Empty);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task OpenImageFileDialogAsyncInternalBackgroundImage(){
|
||||
await OpenImageFileDialogAsyncInternal(
|
||||
pathSetter: (path) => {
|
||||
CrunchyrollManager.Instance.CrunOptions.BackgroundImagePath = path;
|
||||
BackgroundImagePath = path;
|
||||
Helpers.SetBackgroundImage(path, BackgroundImageOpacity, BackgroundImageBlurRadius);
|
||||
},
|
||||
pathGetter: () => CrunchyrollManager.Instance.CrunOptions.BackgroundImagePath,
|
||||
defaultPath: string.Empty
|
||||
);
|
||||
}
|
||||
|
||||
private async Task OpenImageFileDialogAsyncInternal(Action<string> pathSetter, Func<string> pathGetter, string defaultPath){
|
||||
if (_storageProvider == null){
|
||||
Console.Error.WriteLine("StorageProvider must be set before using the dialog.");
|
||||
throw new InvalidOperationException("StorageProvider must be set before using the dialog.");
|
||||
}
|
||||
|
||||
// Open the file picker dialog with only image file types allowed
|
||||
var result = await _storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions{
|
||||
Title = "Select Image File",
|
||||
FileTypeFilter = new List<FilePickerFileType>{
|
||||
new FilePickerFileType("Image Files"){
|
||||
Patterns = new[]{ "*.png", "*.jpg", "*.jpeg", "*.bmp", "*.gif" }
|
||||
}
|
||||
},
|
||||
AllowMultiple = false
|
||||
});
|
||||
|
||||
if (result.Count > 0){
|
||||
var selectedFile = result[0];
|
||||
Console.WriteLine($"Selected file: {selectedFile.Path.LocalPath}");
|
||||
pathSetter(selectedFile.Path.LocalPath);
|
||||
var finalPath = string.IsNullOrEmpty(pathGetter()) ? defaultPath : pathGetter();
|
||||
pathSetter(finalPath);
|
||||
CfgManager.WriteSettingsToFile();
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnCurrentAppThemeChanged(ComboBoxItem? value){
|
||||
if (value?.Content?.ToString() == "System"){
|
||||
_faTheme.PreferSystemTheme = true;
|
||||
} else if (value?.Content?.ToString() == "Dark"){
|
||||
_faTheme.PreferSystemTheme = false;
|
||||
Application.Current.RequestedThemeVariant = ThemeVariant.Dark;
|
||||
} else{
|
||||
_faTheme.PreferSystemTheme = false;
|
||||
Application.Current.RequestedThemeVariant = ThemeVariant.Light;
|
||||
}
|
||||
|
||||
UpdateSettings();
|
||||
}
|
||||
|
||||
partial void OnUseCustomAccentChanged(bool value){
|
||||
if (value){
|
||||
if (_faTheme.TryGetResource("SystemAccentColor", null, out var curColor)){
|
||||
CustomAccentColor = (Color)curColor;
|
||||
ListBoxColor = CustomAccentColor;
|
||||
|
||||
RaisePropertyChanged(nameof(CustomAccentColor));
|
||||
RaisePropertyChanged(nameof(ListBoxColor));
|
||||
}
|
||||
} else{
|
||||
CustomAccentColor = default;
|
||||
ListBoxColor = default;
|
||||
var color = Application.Current?.PlatformSettings?.GetColorValues().AccentColor1 ?? Colors.SlateBlue;
|
||||
UpdateAppAccentColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnListBoxColorChanged(Color value){
|
||||
if (value != null){
|
||||
CustomAccentColor = value;
|
||||
RaisePropertyChanged(nameof(CustomAccentColor));
|
||||
|
||||
UpdateAppAccentColor(value);
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnCustomAccentColorChanged(Color value){
|
||||
ListBoxColor = value;
|
||||
RaisePropertyChanged(nameof(ListBoxColor));
|
||||
UpdateAppAccentColor(value);
|
||||
}
|
||||
|
||||
private void UpdateAppAccentColor(Color? color){
|
||||
_faTheme.CustomAccentColor = color;
|
||||
UpdateSettings();
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(PropertyChangedEventArgs e){
|
||||
base.OnPropertyChanged(e);
|
||||
|
||||
if (e.PropertyName is
|
||||
nameof(CustomAccentColor)
|
||||
or nameof(ListBoxColor)
|
||||
or nameof(CurrentAppTheme)
|
||||
or nameof(UseCustomAccent)
|
||||
or nameof(LogMode)){
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateSettings();
|
||||
|
||||
if (e.PropertyName is nameof(History)){
|
||||
if (CrunchyrollManager.Instance.CrunOptions.History){
|
||||
if (File.Exists(CfgManager.PathCrHistory)){
|
||||
var decompressedJson = CfgManager.DecompressJsonFile(CfgManager.PathCrHistory);
|
||||
if (!string.IsNullOrEmpty(decompressedJson)){
|
||||
CrunchyrollManager.Instance.HistoryList = Helpers.Deserialize<ObservableCollection<HistorySeries>>(decompressedJson, CrunchyrollManager.Instance.SettingsJsonSerializerSettings) ??
|
||||
new ObservableCollection<HistorySeries>();
|
||||
|
||||
foreach (var historySeries in CrunchyrollManager.Instance.HistoryList){
|
||||
historySeries.Init();
|
||||
foreach (var historySeriesSeason in historySeries.Seasons){
|
||||
historySeriesSeason.Init();
|
||||
}
|
||||
}
|
||||
} else{
|
||||
CrunchyrollManager.Instance.HistoryList =[];
|
||||
}
|
||||
}
|
||||
|
||||
_ = SonarrClient.Instance.RefreshSonarrLite();
|
||||
} else{
|
||||
CrunchyrollManager.Instance.HistoryList =[];
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(BackgroundImagePath) && e.PropertyName is nameof(BackgroundImageBlurRadius) or nameof(BackgroundImageOpacity)){
|
||||
Helpers.SetBackgroundImage(BackgroundImagePath, BackgroundImageOpacity, BackgroundImageBlurRadius);
|
||||
}
|
||||
}
|
||||
|
||||
[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){
|
||||
CfgManager.EnableLogMode();
|
||||
} else{
|
||||
CfgManager.DisableLogMode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace CRD.ViewModels;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace CRD.Views;
|
||||
|
||||
|
|
|
|||
|
|
@ -75,6 +75,10 @@
|
|||
Width="120"
|
||||
Height="180" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Grid>
|
||||
|
||||
|
||||
|
|
@ -91,6 +95,14 @@
|
|||
<TextBlock Grid.Row="1" FontSize="15" TextWrapping="Wrap"
|
||||
Text="{Binding Description}">
|
||||
</TextBlock>
|
||||
|
||||
<StackPanel Grid.Row="3" VerticalAlignment="Bottom" HorizontalAlignment="Left"
|
||||
IsVisible="{Binding IsInHistory}" Margin="0 10 10 10" >
|
||||
<controls:SymbolIcon Symbol="Library" FontSize="32" />
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="Series is in History" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Row="0" Grid.Column="0" Margin="10 10 0 0" HorizontalAlignment="Center"
|
||||
IsEnabled="{Binding !CustomCalendar}"
|
||||
IsEnabled="{Binding PrevButtonEnabled}"
|
||||
Command="{Binding PrevWeek}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="ChevronLeft" FontSize="18" />
|
||||
|
|
@ -65,19 +65,19 @@
|
|||
|
||||
<StackPanel>
|
||||
<controls:SettingsExpander IsVisible="{Binding !CustomCalendar}" Header="Simulcast Calendar Language">
|
||||
|
||||
|
||||
|
||||
|
||||
<controls:SettingsExpander.Footer>
|
||||
|
||||
|
||||
<ComboBox HorizontalAlignment="Center" Margin="10 0 0 0" MinWidth="200"
|
||||
SelectedItem="{Binding CurrentCalendarLanguage}"
|
||||
ItemsSource="{Binding CalendarLanguage}">
|
||||
</ComboBox>
|
||||
|
||||
|
||||
</controls:SettingsExpander.Footer>
|
||||
|
||||
|
||||
</controls:SettingsExpander>
|
||||
|
||||
|
||||
<controls:SettingsExpander Header="Custom Calendar">
|
||||
<controls:SettingsExpander.Footer>
|
||||
<CheckBox IsChecked="{Binding CustomCalendar}"
|
||||
|
|
@ -85,10 +85,10 @@
|
|||
</CheckBox>
|
||||
</controls:SettingsExpander.Footer>
|
||||
</controls:SettingsExpander>
|
||||
|
||||
|
||||
|
||||
|
||||
<controls:SettingsExpander IsVisible="{Binding CustomCalendar}" Header="Custom Calendar Dub Filter">
|
||||
|
||||
|
||||
<controls:SettingsExpander.Footer>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<ComboBox HorizontalAlignment="Center" Margin="5 0 0 5" MinWidth="200"
|
||||
|
|
@ -99,12 +99,12 @@
|
|||
Content="Filter by episode air date" Margin="5 5 0 0">
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
</controls:SettingsExpander.Footer>
|
||||
|
||||
|
||||
</controls:SettingsExpander>
|
||||
|
||||
|
||||
|
||||
|
||||
<controls:SettingsExpander Header="Calendar ">
|
||||
<controls:SettingsExpander.Footer>
|
||||
<CheckBox IsChecked="{Binding HideDubs}"
|
||||
|
|
@ -112,9 +112,9 @@
|
|||
</CheckBox>
|
||||
</controls:SettingsExpander.Footer>
|
||||
</controls:SettingsExpander>
|
||||
|
||||
|
||||
</StackPanel>
|
||||
|
||||
|
||||
</Border>
|
||||
</Popup>
|
||||
|
||||
|
|
@ -125,7 +125,7 @@
|
|||
|
||||
|
||||
<Button Grid.Row="0" Grid.Column="2" Margin="0 0 10 0" HorizontalAlignment="Center"
|
||||
IsEnabled="{Binding !CustomCalendar}"
|
||||
IsEnabled="{Binding NextButtonEnabled}"
|
||||
Command="{Binding NextWeek}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="ChevronRight" FontSize="18" />
|
||||
|
|
@ -178,17 +178,27 @@
|
|||
Margin="0,0,0,0" />
|
||||
<Grid HorizontalAlignment="Center">
|
||||
<Grid>
|
||||
<Image HorizontalAlignment="Center" Source="../Assets/coming_soon_ep.jpg" />
|
||||
<Image HorizontalAlignment="Center" Source="{Binding ImageBitmap}" />
|
||||
<Image HorizontalAlignment="Center" IsVisible="{Binding !AnilistEpisode}" Source="../Assets/coming_soon_ep.jpg" />
|
||||
<Image HorizontalAlignment="Center" MaxHeight="150" Source="{Binding ImageBitmap}" />
|
||||
</Grid>
|
||||
|
||||
|
||||
|
||||
|
||||
<StackPanel VerticalAlignment="Top" HorizontalAlignment="Left">
|
||||
<TextBlock VerticalAlignment="Center" TextAlignment="Center"
|
||||
Margin="0 0 5 0" Width="30" Height="30"
|
||||
Background="Black" Opacity="0.8"
|
||||
Text="{Binding EpisodeNumber}"
|
||||
Padding="0,5,0,0" />
|
||||
|
||||
<Border VerticalAlignment="Top" HorizontalAlignment="Right" CornerRadius="0 0 10 0"
|
||||
Background="#f78c25" Opacity="1"
|
||||
Margin="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).CornerMargin} ">
|
||||
<TextBlock VerticalAlignment="Center" TextAlignment="Center"
|
||||
Width="30" Height="30"
|
||||
Text="{Binding EpisodeNumber}"
|
||||
Padding="0,5,0,0" />
|
||||
</Border>
|
||||
|
||||
<!-- <TextBlock VerticalAlignment="Center" TextAlignment="Center" -->
|
||||
<!-- Margin="0 0 5 0" Width="30" Height="30" -->
|
||||
<!-- Background="Black" Opacity="0.8" -->
|
||||
<!-- Text="{Binding EpisodeNumber}" -->
|
||||
<!-- Padding="0,5,0,0" /> -->
|
||||
</StackPanel>
|
||||
<StackPanel VerticalAlignment="Bottom" HorizontalAlignment="Right"
|
||||
IsVisible="{Binding IsPremiumOnly}" Margin="0,0,5,5">
|
||||
|
|
@ -217,8 +227,9 @@
|
|||
</TextBlock>
|
||||
<Button HorizontalAlignment="Center" Content="Download"
|
||||
IsEnabled="{Binding HasPassed}"
|
||||
IsVisible="{Binding HasPassed}"
|
||||
Command="{Binding AddEpisodeToQue}"
|
||||
CommandParameter="{Binding EpisodeUrl}" />
|
||||
/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace CRD.Views;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
x:DataType="vm:DownloadsPageViewModel"
|
||||
x:Class="CRD.Views.DownloadsPageView"
|
||||
xmlns:local="clr-namespace:CRD.Utils"
|
||||
xmlns:ui="clr-namespace:CRD.Utils.UI">
|
||||
|
||||
<UserControl.Resources>
|
||||
|
|
@ -17,12 +16,11 @@
|
|||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" /> <!-- For the TextBox -->
|
||||
<RowDefinition Height="*" /> <!-- For the ListBox to take remaining space -->
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<!-- <Button Click="Button_OnClick">Test Download</Button> -->
|
||||
<ToggleSwitch HorizontalAlignment="Right" Margin="0 0 10 0 " IsChecked="{Binding RemoveFinished}" OffContent="Remove Finished" OnContent="Remove Finished"></ToggleSwitch>
|
||||
<ToggleSwitch HorizontalAlignment="Right" Margin="0 0 10 0 " IsChecked="{Binding AutoDownload}" OffContent="Auto Download" OnContent="Auto Download"></ToggleSwitch>
|
||||
</StackPanel>
|
||||
|
|
@ -38,9 +36,6 @@
|
|||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<!-- Image -->
|
||||
<!-- <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" />
|
||||
|
|
@ -97,7 +92,7 @@
|
|||
|
||||
<Grid Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="3" VerticalAlignment="Bottom" >
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" /> <!-- Takes up most space for the title -->
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,4 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using CRD.Downloader;
|
||||
using CRD.ViewModels;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace CRD.Views;
|
||||
|
||||
|
|
|
|||
|
|
@ -38,9 +38,9 @@
|
|||
VerticalAlignment="Center"
|
||||
Command="{Binding RefreshAll}"
|
||||
IsEnabled="{Binding !ProgramManager.FetchingData}">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center">
|
||||
<controls:SymbolIcon Symbol="Sync" FontSize="32" />
|
||||
<TextBlock Text="Refresh Filtered" TextWrapping="Wrap" FontSize="12"></TextBlock>
|
||||
<TextBlock Text="Refresh Filtered" HorizontalAlignment="Center" TextWrapping="Wrap" FontSize="12"></TextBlock>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
|
|
@ -50,7 +50,7 @@
|
|||
IsEnabled="{Binding !ProgramManager.FetchingData}">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<controls:SymbolIcon Symbol="Import" FontSize="32" />
|
||||
<TextBlock Text="Add To Queue" TextWrapping="Wrap" FontSize="12"></TextBlock>
|
||||
<TextBlock Text="Add To Queue" TextWrapping="Wrap" HorizontalAlignment="Center" FontSize="12"></TextBlock>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
|
|
@ -60,7 +60,7 @@
|
|||
IsEnabled="{Binding !ProgramManager.FetchingData}">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<controls:SymbolIcon Symbol="Edit" FontSize="32" />
|
||||
<TextBlock Text="Edit" TextWrapping="Wrap" FontSize="12"></TextBlock>
|
||||
<TextBlock Text="Edit" TextWrapping="Wrap" HorizontalAlignment="Center" FontSize="12"></TextBlock>
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
|
||||
|
|
@ -74,7 +74,7 @@
|
|||
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>
|
||||
<TextBlock Text="Sonarr" TextWrapping="Wrap" HorizontalAlignment="Center" FontSize="12"></TextBlock>
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
<Popup IsLightDismissEnabled="True"
|
||||
|
|
@ -123,7 +123,7 @@
|
|||
IsChecked="{Binding ViewSelectionOpen}">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<controls:SymbolIcon Symbol="View" FontSize="32" />
|
||||
<TextBlock Text="View" FontSize="12"></TextBlock>
|
||||
<TextBlock Text="View" HorizontalAlignment="Center" FontSize="12"></TextBlock>
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
<Popup IsLightDismissEnabled="True"
|
||||
|
|
@ -147,7 +147,7 @@
|
|||
IsChecked="{Binding SortingSelectionOpen}">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<controls:SymbolIcon Symbol="Sort" FontSize="32" />
|
||||
<TextBlock Text="Sort" FontSize="12"></TextBlock>
|
||||
<TextBlock Text="Sort" HorizontalAlignment="Center" FontSize="12"></TextBlock>
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
<Popup IsLightDismissEnabled="True"
|
||||
|
|
@ -178,7 +178,7 @@
|
|||
IsEnabled="{Binding !ProgramManager.FetchingData}">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<controls:SymbolIcon Symbol="Filter" FontSize="32" />
|
||||
<TextBlock Text="Filter" FontSize="12"></TextBlock>
|
||||
<TextBlock Text="Filter" HorizontalAlignment="Center" FontSize="12"></TextBlock>
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
<Popup IsLightDismissEnabled="True"
|
||||
|
|
@ -214,80 +214,82 @@
|
|||
</ListBox.ItemsPanel>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border>
|
||||
<Grid>
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center"
|
||||
Width="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).PosterWidth}"
|
||||
Height="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).PosterHeight}"
|
||||
Margin="5">
|
||||
<Grid>
|
||||
<Image Source="{Binding ThumbnailImage}"
|
||||
Width="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).PosterImageWidth}"
|
||||
Height="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).PosterImageHeight}">
|
||||
</Image>
|
||||
|
||||
<Grid>
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center"
|
||||
Width="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).PosterWidth}"
|
||||
Height="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).PosterHeight}"
|
||||
Margin="5">
|
||||
<Grid>
|
||||
<Image Source="{Binding ThumbnailImage}"
|
||||
Width="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).PosterImageWidth}"
|
||||
Height="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).PosterImageHeight}">
|
||||
</Image>
|
||||
<Border VerticalAlignment="Top" HorizontalAlignment="Right" CornerRadius="0 0 0 10"
|
||||
Background="#f78c25" Opacity="1"
|
||||
Margin="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).CornerMargin} "
|
||||
IsVisible="{Binding NewEpisodes, Converter={StaticResource UiIntToVisibilityConverter}}">
|
||||
<TextBlock VerticalAlignment="Center" TextAlignment="Center" Width="30"
|
||||
Height="30"
|
||||
Text="{Binding NewEpisodes}"
|
||||
Padding="0,5,0,0" />
|
||||
</Border>
|
||||
|
||||
<Border VerticalAlignment="Top" HorizontalAlignment="Right" CornerRadius="0 0 0 10"
|
||||
Background="#f78c25" Opacity="1"
|
||||
Margin="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).CornerMargin} "
|
||||
IsVisible="{Binding NewEpisodes, Converter={StaticResource UiIntToVisibilityConverter}}">
|
||||
<TextBlock VerticalAlignment="Center" TextAlignment="Center" Width="30"
|
||||
Height="30"
|
||||
Text="{Binding NewEpisodes}"
|
||||
Padding="0,5,0,0" />
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<TextBlock HorizontalAlignment="Center" TextAlignment="Center"
|
||||
Text="{Binding SeriesTitle}"
|
||||
TextWrapping="Wrap"
|
||||
Width="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).PosterImageWidth}"
|
||||
FontSize="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).PosterTextSize}"
|
||||
Height="35"
|
||||
Margin="4,0,4,0">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="{Binding SeriesTitle}" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock HorizontalAlignment="Center" TextAlignment="Center"
|
||||
Text="{Binding SonarrNextAirDate}"
|
||||
TextWrapping="Wrap"
|
||||
Width="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).PosterImageWidth}"
|
||||
FontSize="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).PosterTextSize}"
|
||||
MaxHeight="20"
|
||||
Margin="4,0,4,0">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="{Binding SonarrNextAirDate}" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="#90000000" IsVisible="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).EditMode}">
|
||||
|
||||
<Button
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
|
||||
FontStyle="Italic"
|
||||
Command="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).RemoveSeries}"
|
||||
CommandParameter="{Binding SeriesId}"
|
||||
IsEnabled="{Binding !$parent[UserControl].((vm:HistoryPageViewModel)DataContext).ProgramManager.FetchingData}">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="Remove Series" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Delete" FontSize="32" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<TextBlock HorizontalAlignment="Center" TextAlignment="Center"
|
||||
Text="{Binding SeriesTitle}"
|
||||
TextWrapping="Wrap"
|
||||
Width="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).PosterImageWidth}"
|
||||
FontSize="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).PosterTextSize}"
|
||||
Height="35"
|
||||
Margin="4,0,4,0">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="{Binding SeriesTitle}" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
</TextBlock>
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="#90000000" IsVisible="{Binding FetchingData}">
|
||||
<controls:ProgressRing Width="100" Height="100"></controls:ProgressRing>
|
||||
</Grid>
|
||||
|
||||
<TextBlock HorizontalAlignment="Center" TextAlignment="Center"
|
||||
Text="{Binding SonarrNextAirDate}"
|
||||
TextWrapping="Wrap"
|
||||
Width="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).PosterImageWidth}"
|
||||
FontSize="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).PosterTextSize}"
|
||||
MaxHeight="20"
|
||||
Margin="4,0,4,0">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="{Binding SonarrNextAirDate}" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="#90000000" IsVisible="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).EditMode}">
|
||||
|
||||
<Button
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
|
||||
FontStyle="Italic"
|
||||
Command="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).RemoveSeries}"
|
||||
CommandParameter="{Binding SeriesId}"
|
||||
IsEnabled="{Binding !$parent[UserControl].((vm:HistoryPageViewModel)DataContext).ProgramManager.FetchingData}">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="Remove Series" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Delete" FontSize="32" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="#90000000" IsVisible="{Binding FetchingData}">
|
||||
<controls:ProgressRing Width="100" Height="100"></controls:ProgressRing>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
|
|
@ -422,7 +424,7 @@
|
|||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding EditModeEnabled}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Speaker2" FontSize="18" />
|
||||
<controls:SymbolIcon Symbol="Settings" FontSize="18" />
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
|
||||
|
|
@ -435,11 +437,51 @@
|
|||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||
|
||||
<StackPanel>
|
||||
<controls:SettingsExpander Header="Language Override">
|
||||
<controls:SettingsExpander Header="Settings Override">
|
||||
|
||||
<controls:SettingsExpander.Footer>
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0 0 0 10">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0 0 0 10">
|
||||
<TextBlock Text="Video Quality" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0 0 10 0"></TextBlock>
|
||||
|
||||
<StackPanel>
|
||||
<ToggleButton x:Name="OverrideDropdownButtonQuality" Width="210" HorizontalContentAlignment="Stretch">
|
||||
<ToggleButton.Content>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock HorizontalAlignment="Center" Text="{Binding SelectedVideoQualityItem.stringValue, FallbackValue=''}"
|
||||
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=OverrideDropdownButtonQuality, Mode=TwoWay}"
|
||||
Placement="Bottom"
|
||||
PlacementTarget="{Binding ElementName=OverrideDropdownButtonQuality}">
|
||||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||
<ListBox x:Name="ListBoxQulitiesSelection" SelectionMode="Single,Toggle" Width="210"
|
||||
MaxHeight="400"
|
||||
ItemsSource="{Binding VideoQualityList , Mode=OneWay}"
|
||||
SelectedItem="{Binding SelectedVideoQualityItem}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type structs:StringItem}">
|
||||
<TextBlock Text="{Binding stringValue}"></TextBlock>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Border>
|
||||
</Popup>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0 0 0 10">
|
||||
<TextBlock Text="Dub" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0 0 10 0"></TextBlock>
|
||||
<StackPanel>
|
||||
<ToggleButton x:Name="DropdownButtonDub" Width="210" HorizontalContentAlignment="Stretch">
|
||||
|
|
@ -476,7 +518,7 @@
|
|||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Right">
|
||||
<TextBlock Text="Sub" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0 0 10 0"></TextBlock>
|
||||
<StackPanel>
|
||||
<ToggleButton x:Name="DropdownButtonSub" Width="210" HorizontalContentAlignment="Stretch">
|
||||
|
|
@ -724,7 +766,7 @@
|
|||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding $parent[ScrollViewer].((history:HistorySeries)DataContext).EditModeEnabled}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Speaker2" FontSize="18" />
|
||||
<controls:SymbolIcon Symbol="Settings" FontSize="18" />
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
|
||||
|
|
@ -737,11 +779,52 @@
|
|||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||
|
||||
<StackPanel>
|
||||
<controls:SettingsExpander Header="Language Override">
|
||||
<controls:SettingsExpander Header="Settings Override">
|
||||
|
||||
<controls:SettingsExpander.Footer>
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0 0 0 10">
|
||||
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0 0 0 10">
|
||||
<TextBlock Text="Video Quality" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0 0 10 0"></TextBlock>
|
||||
|
||||
<StackPanel>
|
||||
<ToggleButton x:Name="SeasonOverrideDropdownButtonQuality" Width="210" HorizontalContentAlignment="Stretch">
|
||||
<ToggleButton.Content>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock HorizontalAlignment="Center" Text="{Binding SelectedVideoQualityItem.stringValue, FallbackValue=''}"
|
||||
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=SeasonOverrideDropdownButtonQuality, Mode=TwoWay}"
|
||||
Placement="Bottom"
|
||||
PlacementTarget="{Binding ElementName=SeasonOverrideDropdownButtonQuality}">
|
||||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||
<ListBox x:Name="ListBoxQulitiesSelection" SelectionMode="Single,Toggle" Width="210"
|
||||
MaxHeight="400"
|
||||
ItemsSource="{Binding VideoQualityList , Mode=OneWay}"
|
||||
SelectedItem="{Binding SelectedVideoQualityItem}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type structs:StringItem}">
|
||||
<TextBlock Text="{Binding stringValue}"></TextBlock>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Border>
|
||||
</Popup>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0 0 0 10">
|
||||
<TextBlock Text="Dub" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0 0 10 0"></TextBlock>
|
||||
<StackPanel>
|
||||
<ToggleButton x:Name="SeasonOverrideDropdownButtonDub" Width="210" HorizontalContentAlignment="Stretch">
|
||||
|
|
@ -778,7 +861,7 @@
|
|||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Right">
|
||||
<TextBlock Text="Sub" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0 0 10 0"></TextBlock>
|
||||
<StackPanel>
|
||||
<ToggleButton x:Name="SeasonOverrideDropdownButtonSub" Width="210" HorizontalContentAlignment="Stretch">
|
||||
|
|
|
|||
|
|
@ -10,10 +10,11 @@
|
|||
x:DataType="vm:MainWindowViewModel"
|
||||
Icon="/Assets/app_icon.ico"
|
||||
Title="Crunchy-Downloader">
|
||||
|
||||
|
||||
<Design.DataContext>
|
||||
<vm:MainWindowViewModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<Grid Name="MainGrid">
|
||||
<ContentControl x:Name="MainContent">
|
||||
<Grid RowDefinitions="Auto, *">
|
||||
|
|
@ -60,6 +61,8 @@
|
|||
IconSource="Add" />
|
||||
<ui:NavigationViewItem Classes="SampleAppNav" Content="Calendar" Tag="Calendar"
|
||||
IconSource="Calendar" />
|
||||
<ui:NavigationViewItem Classes="SampleAppNav" Content="Seasons" Tag="Seasons"
|
||||
IconSource="Clock" />
|
||||
<ui:NavigationViewItem IsEnabled="{Binding ProgramManager.FinishedLoading}" Classes="SampleAppNav" Content="History" Tag="History"
|
||||
IconSource="Library" />
|
||||
</ui:NavigationView.MenuItems>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using System.Linq;
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Platform;
|
||||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.Files;
|
||||
|
|
@ -13,7 +13,6 @@ using CRD.Utils.Structs;
|
|||
using CRD.Utils.Updater;
|
||||
using CRD.ViewModels;
|
||||
using CRD.Views.Utils;
|
||||
using FluentAvalonia.Core;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using FluentAvalonia.UI.Windowing;
|
||||
using Newtonsoft.Json;
|
||||
|
|
@ -56,14 +55,15 @@ public partial class MainWindow : AppWindow{
|
|||
private Size _restoreSize;
|
||||
|
||||
public MainWindow(){
|
||||
ProgramManager.Instance.StorageProvider = StorageProvider;
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
InitializeComponent();
|
||||
|
||||
|
||||
ExtendClientAreaTitleBarHeightHint = TitleBarHeightAdjustment;
|
||||
TitleBar.Height = TitleBarHeightAdjustment;
|
||||
TitleBar.ExtendsContentIntoTitleBar = true;
|
||||
TitleBar.TitleBarHitTestType = TitleBarHitTestType.Complex;
|
||||
|
||||
|
||||
Opened += OnOpened;
|
||||
Closing += OnClosing;
|
||||
|
||||
|
|
@ -83,17 +83,13 @@ public partial class MainWindow : AppWindow{
|
|||
if (message.Refresh){
|
||||
navigationStack.Pop();
|
||||
var viewModel = Activator.CreateInstance(message.ViewModelType);
|
||||
if (viewModel is SeriesPageViewModel){
|
||||
((SeriesPageViewModel)viewModel).SetStorageProvider(StorageProvider);
|
||||
}
|
||||
|
||||
|
||||
navigationStack.Push(viewModel);
|
||||
nv.Content = viewModel;
|
||||
} else if (!message.Back && message.ViewModelType != null){
|
||||
var viewModel = Activator.CreateInstance(message.ViewModelType);
|
||||
if (viewModel is SeriesPageViewModel){
|
||||
((SeriesPageViewModel)viewModel).SetStorageProvider(StorageProvider);
|
||||
}
|
||||
|
||||
|
||||
navigationStack.Push(viewModel);
|
||||
nv.Content = viewModel;
|
||||
|
|
@ -143,21 +139,20 @@ public partial class MainWindow : AppWindow{
|
|||
break;
|
||||
case "History":
|
||||
navView.Content = Activator.CreateInstance(typeof(HistoryPageViewModel));
|
||||
if (navView.Content is HistoryPageViewModel){
|
||||
((HistoryPageViewModel)navView.Content).SetStorageProvider(StorageProvider);
|
||||
}
|
||||
|
||||
navigationStack.Clear();
|
||||
navigationStack.Push(navView.Content);
|
||||
selectedNavVieItem = selectedItem;
|
||||
break;
|
||||
case "Seasons":
|
||||
navView.Content = Activator.CreateInstance(typeof(UpcomingPageViewModel));
|
||||
selectedNavVieItem = selectedItem;
|
||||
break;
|
||||
case "Account":
|
||||
navView.Content = Activator.CreateInstance(typeof(AccountPageViewModel));
|
||||
selectedNavVieItem = selectedItem;
|
||||
break;
|
||||
case "Settings":
|
||||
var viewModel = (SettingsPageViewModel)Activator.CreateInstance(typeof(SettingsPageViewModel));
|
||||
viewModel.SetStorageProvider(StorageProvider);
|
||||
navView.Content = viewModel;
|
||||
selectedNavVieItem = selectedItem;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@
|
|||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" FontSize="50" Text="{Binding SelectedSeries.SeriesTitle}"></TextBlock>
|
||||
<TextBlock Grid.Row="0" FontSize="45" Text="{Binding SelectedSeries.SeriesTitle}" TextTrimming="CharacterEllipsis"></TextBlock>
|
||||
<TextBlock Grid.Row="1" FontSize="20" TextWrapping="Wrap" Text="{Binding SelectedSeries.SeriesDescription}"></TextBlock>
|
||||
<TextBlock Grid.Row="3" FontSize="15" Opacity="0.8" TextWrapping="Wrap" Text="{Binding AvailableDubs}"></TextBlock>
|
||||
<TextBlock Grid.Row="4" FontSize="15" Opacity="0.8" TextWrapping="Wrap" Text="{Binding AvailableSubs}"></TextBlock>
|
||||
|
|
@ -121,7 +121,7 @@
|
|||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding EditMode}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Speaker2" FontSize="18" />
|
||||
<controls:SymbolIcon Symbol="Settings" FontSize="18" />
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
|
||||
|
|
@ -134,11 +134,56 @@
|
|||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||
|
||||
<StackPanel>
|
||||
<controls:SettingsExpander Header="Language Override">
|
||||
<controls:SettingsExpander Header="Settings Override">
|
||||
|
||||
<controls:SettingsExpander.Footer>
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0 0 0 10">
|
||||
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0 0 0 10">
|
||||
<TextBlock Text="Video Quality" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0 0 10 0"></TextBlock>
|
||||
<!-- <ComboBox HorizontalContentAlignment="Center" MinWidth="210" MaxDropDownHeight="400" -->
|
||||
<!-- ItemsSource="{Binding CrunchyrollManager.VideoQualityList}" -->
|
||||
<!-- SelectedItem="{Binding SelectedSeries.SelectedVideoQuality}"> -->
|
||||
<!-- </ComboBox> -->
|
||||
|
||||
<StackPanel>
|
||||
<ToggleButton x:Name="OverrideDropdownButtonQuality" Width="210" HorizontalContentAlignment="Stretch">
|
||||
<ToggleButton.Content>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock HorizontalAlignment="Center" Text="{Binding SelectedSeries.SelectedVideoQualityItem.stringValue, FallbackValue=''}"
|
||||
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=OverrideDropdownButtonQuality, Mode=TwoWay}"
|
||||
Placement="Bottom"
|
||||
PlacementTarget="{Binding ElementName=OverrideDropdownButtonQuality}">
|
||||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||
<ListBox x:Name="ListBoxQulitiesSelection" SelectionMode="Single,Toggle" Width="210"
|
||||
MaxHeight="400"
|
||||
ItemsSource="{Binding SelectedSeries.VideoQualityList , Mode=OneWay}"
|
||||
SelectedItem="{Binding SelectedSeries.SelectedVideoQualityItem}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type structs:StringItem}">
|
||||
<TextBlock Text="{Binding stringValue}"></TextBlock>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Border>
|
||||
</Popup>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0 0 0 10">
|
||||
<TextBlock Text="Dub" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0 0 10 0"></TextBlock>
|
||||
<StackPanel>
|
||||
<ToggleButton x:Name="DropdownButtonDub" Width="210" HorizontalContentAlignment="Stretch">
|
||||
|
|
@ -175,7 +220,7 @@
|
|||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Right">
|
||||
<TextBlock Text="Sub" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0 0 10 0"></TextBlock>
|
||||
<StackPanel>
|
||||
<ToggleButton x:Name="DropdownButtonSub" Width="210" HorizontalContentAlignment="Stretch">
|
||||
|
|
@ -426,7 +471,7 @@
|
|||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding $parent[UserControl].((vm:SeriesPageViewModel)DataContext).EditMode}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Speaker2" FontSize="18" />
|
||||
<controls:SymbolIcon Symbol="Settings" FontSize="18" />
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
|
||||
|
|
@ -439,11 +484,52 @@
|
|||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||
|
||||
<StackPanel>
|
||||
<controls:SettingsExpander Header="Language Override">
|
||||
<controls:SettingsExpander Header="Settings Override">
|
||||
|
||||
<controls:SettingsExpander.Footer>
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0 0 0 10">
|
||||
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0 0 0 10">
|
||||
<TextBlock Text="Video Quality" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0 0 10 0"></TextBlock>
|
||||
|
||||
<StackPanel>
|
||||
<ToggleButton x:Name="SeasonOverrideDropdownButtonQuality" Width="210" HorizontalContentAlignment="Stretch">
|
||||
<ToggleButton.Content>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock HorizontalAlignment="Center" Text="{Binding SelectedVideoQualityItem.stringValue, FallbackValue=''}"
|
||||
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=SeasonOverrideDropdownButtonQuality, Mode=TwoWay}"
|
||||
Placement="Bottom"
|
||||
PlacementTarget="{Binding ElementName=SeasonOverrideDropdownButtonQuality}">
|
||||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||
<ListBox x:Name="ListBoxQulitiesSelection" SelectionMode="Single,Toggle" Width="210"
|
||||
MaxHeight="400"
|
||||
ItemsSource="{Binding VideoQualityList , Mode=OneWay}"
|
||||
SelectedItem="{Binding SelectedVideoQualityItem}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type structs:StringItem}">
|
||||
<TextBlock Text="{Binding stringValue}"></TextBlock>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Border>
|
||||
</Popup>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0 0 0 10">
|
||||
<TextBlock Text="Dub" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0 0 10 0"></TextBlock>
|
||||
<StackPanel>
|
||||
<ToggleButton x:Name="SeasonOverrideDropdownButtonDub" Width="210" HorizontalContentAlignment="Stretch">
|
||||
|
|
@ -480,7 +566,7 @@
|
|||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Right">
|
||||
<TextBlock Text="Sub" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0 0 10 0"></TextBlock>
|
||||
<StackPanel>
|
||||
<ToggleButton x:Name="SeasonOverrideDropdownButtonSub" Width="210" HorizontalContentAlignment="Stretch">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace CRD.Views;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
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"
|
||||
|
|
@ -14,894 +13,10 @@
|
|||
<vm:SettingsPageViewModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<ScrollViewer Padding="20 20 20 0">
|
||||
<StackPanel Spacing="8">
|
||||
|
||||
|
||||
<controls:SettingsExpander Header="Dub language"
|
||||
IconSource="Speaker2"
|
||||
Description="Change the selected dub language (with multiple dubs some can be out of sync)">
|
||||
<controls:SettingsExpander.Footer>
|
||||
<!-- <ComboBox HorizontalContentAlignment="Center" MinWidth="210" MaxDropDownHeight="400" -->
|
||||
<!-- -->
|
||||
<!-- ItemsSource="{Binding DubLangList}" -->
|
||||
<!-- SelectedItem="{Binding SelectedDubLang}"> -->
|
||||
<!-- </ComboBox> -->
|
||||
|
||||
|
||||
<StackPanel>
|
||||
<ToggleButton x:Name="DropdownButtonDub" Width="210" HorizontalContentAlignment="Stretch">
|
||||
<ToggleButton.Content>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock HorizontalAlignment="Center" Text="{Binding SelectedDubs}"
|
||||
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=DropdownButtonDub, Mode=TwoWay}"
|
||||
Placement="Bottom"
|
||||
PlacementTarget="{Binding ElementName=DropdownButtonDub}">
|
||||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||
<ListBox x:Name="ListBoxDubsSelection" SelectionMode="Multiple,Toggle" Width="210"
|
||||
MaxHeight="400"
|
||||
ItemsSource="{Binding DubLangList}"
|
||||
SelectedItems="{Binding SelectedDubLang}"
|
||||
PointerWheelChanged="ListBox_PointerWheelChanged">
|
||||
</ListBox>
|
||||
</Border>
|
||||
</Popup>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
</controls:SettingsExpander.Footer>
|
||||
|
||||
</controls:SettingsExpander>
|
||||
|
||||
|
||||
<controls:SettingsExpander Header="Hardsubs language"
|
||||
IconSource="FontColorFilled"
|
||||
Description="Change the selected hardsub language">
|
||||
<controls:SettingsExpander.Footer>
|
||||
<ComboBox HorizontalContentAlignment="Center" MinWidth="210" MaxDropDownHeight="400"
|
||||
ItemsSource="{Binding HardSubLangList}"
|
||||
SelectedItem="{Binding SelectedHSLang}">
|
||||
</ComboBox>
|
||||
</controls:SettingsExpander.Footer>
|
||||
|
||||
</controls:SettingsExpander>
|
||||
|
||||
|
||||
<controls:SettingsExpander Header="Softsubs language"
|
||||
IconSource="FontColor"
|
||||
Description="Change the selected softsubs language">
|
||||
<controls:SettingsExpander.Footer>
|
||||
<StackPanel>
|
||||
<ToggleButton x:Name="dropdownButton" Width="210" HorizontalContentAlignment="Stretch">
|
||||
<ToggleButton.Content>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock HorizontalAlignment="Center" Text="{Binding SelectedSubs}"
|
||||
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=dropdownButton, Mode=TwoWay}" Placement="Bottom"
|
||||
PlacementTarget="{Binding ElementName=dropdownButton}">
|
||||
<Border BorderThickness="1" Background="{DynamicResource ComboBoxDropDownBackground}">
|
||||
<ListBox x:Name="listBoxSubsSelection" SelectionMode="Multiple,Toggle" Width="210"
|
||||
MaxHeight="400"
|
||||
ItemsSource="{Binding SubLangList}" SelectedItems="{Binding SelectedSubLang}"
|
||||
PointerWheelChanged="ListBox_PointerWheelChanged">
|
||||
</ListBox>
|
||||
</Border>
|
||||
</Popup>
|
||||
</StackPanel>
|
||||
</controls:SettingsExpander.Footer>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Add ScaledBorderAndShadow ">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ComboBox HorizontalContentAlignment="Center" IsVisible="{Binding AddScaledBorderAndShadow}" Margin="5 0" MinWidth="210" MaxDropDownHeight="400"
|
||||
ItemsSource="{Binding ScaledBorderAndShadow}"
|
||||
SelectedItem="{Binding SelectedScaledBorderAndShadow}">
|
||||
</ComboBox>
|
||||
<CheckBox IsChecked="{Binding AddScaledBorderAndShadow}"> </CheckBox>
|
||||
</StackPanel>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Signs Subtitles " Description="Download Signs (Forced) Subtitles">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
|
||||
<CheckBox HorizontalAlignment="Right" IsChecked="{Binding IncludeSignSubs}"> </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 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 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>
|
||||
|
||||
<controls:SettingsExpander Header="History"
|
||||
IconSource="Clock"
|
||||
Description="Change if the download history is recorded">
|
||||
<controls:SettingsExpander.Footer>
|
||||
<CheckBox IsChecked="{Binding History}"> </CheckBox>
|
||||
</controls:SettingsExpander.Footer>
|
||||
|
||||
<controls:SettingsExpanderItem Content="History Language" Description="Use the same language as Sonarr if you plan to connect it to this downloader">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<ComboBox HorizontalContentAlignment="Center" MinWidth="210" MaxDropDownHeight="400"
|
||||
ItemsSource="{Binding HistoryLangList}"
|
||||
SelectedItem="{Binding SelectedHistoryLang}">
|
||||
</ComboBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="History Add Specials" Description="Add specials to the queue if they weren't downloaded before">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding HistoryAddSpecials}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="History Missing/New Count from Sonarr" Description="The missing count (number in the orange corner) will count the episodes missing from sonarr">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding HistoryCountSonarr}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
</controls:SettingsExpander>
|
||||
|
||||
<controls:SettingsExpander Header="Download Settings"
|
||||
IconSource="Download"
|
||||
Description="Adjust download settings"
|
||||
IsExpanded="False">
|
||||
|
||||
<controls:SettingsExpanderItem Content="Max Download Speed"
|
||||
Description="Download in Kb/s - 0 is full speed">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<controls:NumberBox Minimum="0" Maximum="1000000000"
|
||||
Value="{Binding DownloadSpeed}"
|
||||
SpinButtonPlacementMode="Hidden"
|
||||
HorizontalAlignment="Stretch" />
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
|
||||
<controls:SettingsExpanderItem Content="Stream Endpoint ">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<ComboBox HorizontalContentAlignment="Center" MinWidth="210" MaxDropDownHeight="400"
|
||||
ItemsSource="{Binding StreamEndpoints}"
|
||||
SelectedItem="{Binding SelectedStreamEndpoint}">
|
||||
</ComboBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Use Temp Download Folder">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock IsVisible="{Binding DownloadToTempFolder}" FontSize="15" Opacity="0.8" TextWrapping="NoWrap" Text="{Binding TempDownloadDirPath, Mode=OneWay}" TextAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center" />
|
||||
<Button IsVisible="{Binding DownloadToTempFolder}" Margin="10 0 10 0" FontStyle="Italic"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding OpenFolderDialogTempFolderAsync}">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="Set Download Directory" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Folder" FontSize="18" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<CheckBox IsChecked="{Binding DownloadToTempFolder}"> </CheckBox>
|
||||
</StackPanel>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Download Folder">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock FontSize="15" Opacity="0.8" TextWrapping="NoWrap" Text="{Binding DownloadDirPath, Mode=OneWay}" TextAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center" />
|
||||
<Button Margin="10 0 0 0" FontStyle="Italic"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding OpenFolderDialogAsync}">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="Set Download Directory" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Folder" FontSize="18" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Simultaneous Downloads">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<controls:NumberBox Minimum="0" Maximum="10"
|
||||
Value="{Binding SimultaneousDownloads}"
|
||||
SpinButtonPlacementMode="Inline"
|
||||
HorizontalAlignment="Stretch" />
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Video">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding DownloadVideo}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Download Video for every dub">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<StackPanel>
|
||||
<CheckBox IsChecked="{Binding DownloadVideoForEveryDub}"> </CheckBox>
|
||||
<CheckBox IsVisible="{Binding DownloadVideoForEveryDub}" Content="Keep files separate" IsChecked="{Binding KeepDubsSeparate}"> </CheckBox>
|
||||
</StackPanel>
|
||||
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Video Quality">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<ComboBox HorizontalContentAlignment="Center" MinWidth="210" MaxDropDownHeight="400"
|
||||
ItemsSource="{Binding VideoQualityList}"
|
||||
SelectedItem="{Binding SelectedVideoQuality}">
|
||||
</ComboBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Audio">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding DownloadAudio}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Audio Quality">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<ComboBox HorizontalContentAlignment="Center" MinWidth="210" MaxDropDownHeight="400"
|
||||
ItemsSource="{Binding AudioQualityList}"
|
||||
SelectedItem="{Binding SelectedAudioQuality}">
|
||||
</ComboBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Chapters">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding DownloadChapters}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpander.Footer>
|
||||
</controls:SettingsExpander.Footer>
|
||||
</controls:SettingsExpander>
|
||||
|
||||
|
||||
<controls:SettingsExpander Header="Filename Settings"
|
||||
IconSource="Edit"
|
||||
Description="Change how the files are named"
|
||||
IsExpanded="False">
|
||||
|
||||
<controls:SettingsExpanderItem Content="Leading 0 for seasons and episodes">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<controls:NumberBox Minimum="0" Maximum="5"
|
||||
Value="{Binding LeadingNumbers}"
|
||||
SpinButtonPlacementMode="Inline"
|
||||
HorizontalAlignment="Stretch" />
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Filename"
|
||||
Description="${seriesTitle} ${seasonTitle} ${title} ${season} ${episode} ${height} ${width} ${dubs} - Folder with \\">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<TextBox Name="FileNameTextBox" HorizontalAlignment="Left" MinWidth="250"
|
||||
Text="{Binding FileName}" />
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpander.Footer>
|
||||
|
||||
</controls:SettingsExpander.Footer>
|
||||
</controls:SettingsExpander>
|
||||
|
||||
|
||||
<controls:SettingsExpander Header="Muxing Settings"
|
||||
IconSource="Repair"
|
||||
Description="MKVMerge and FFMpeg Settings"
|
||||
IsExpanded="False">
|
||||
|
||||
<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 IsVisible="{Binding !SkipMuxing}" Content="Keep Subtitles separate">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding SkipSubMux}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem IsVisible="{Binding !SkipMuxing}" Content="Default Audio ">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<ComboBox HorizontalContentAlignment="Center" MinWidth="210" MaxDropDownHeight="400"
|
||||
ItemsSource="{Binding DefaultDubLangList}"
|
||||
SelectedItem="{Binding SelectedDefaultDubLang}">
|
||||
</ComboBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
|
||||
<controls:SettingsExpanderItem IsVisible="{Binding !SkipMuxing}" Content="Default Subtitle ">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<ComboBox HorizontalContentAlignment="Center" MinWidth="210" MaxDropDownHeight="400"
|
||||
ItemsSource="{Binding DefaultSubLangList}"
|
||||
SelectedItem="{Binding SelectedDefaultSubLang}">
|
||||
</ComboBox>
|
||||
<CheckBox Content="Forced Display" IsChecked="{Binding DefaultSubForcedDisplay}"> </CheckBox>
|
||||
</StackPanel>
|
||||
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<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 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 IsVisible="{Binding !SkipMuxing}" Content="Include Episode description">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding IncludeEpisodeDescription}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem IsVisible="{Binding !SkipMuxing}" Content="Episode description Language">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<ComboBox HorizontalContentAlignment="Center" MinWidth="210" MaxDropDownHeight="400"
|
||||
ItemsSource="{Binding DescriptionLangList}"
|
||||
SelectedItem="{Binding SelectedDescriptionLang}">
|
||||
</ComboBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<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 IsVisible="{Binding !SkipMuxing}" Content="Additional MKVMerge Options">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Name="TargetTextBox2" HorizontalAlignment="Left" MinWidth="250"
|
||||
Text="{Binding MkvMergeOption }">
|
||||
</TextBox>
|
||||
<Button HorizontalAlignment="Center" Margin="5 0" Command="{Binding AddMkvMergeParam}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Add" FontSize="18" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<ItemsControl ItemsSource="{Binding MkvMergeOptions}" Margin="0,5" 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].((vm:SettingsPageViewModel)DataContext).RemoveMkvMergeParam}"
|
||||
CommandParameter="{Binding .}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem IsVisible="{Binding !SkipMuxing}" Content="Additional FFMpeg Options">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox HorizontalAlignment="Left" MinWidth="250"
|
||||
Text="{Binding FfmpegOption }">
|
||||
</TextBox>
|
||||
<Button HorizontalAlignment="Center" Margin="5 0" Command="{Binding AddFfmpegParam}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Add" FontSize="18" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<ItemsControl ItemsSource="{Binding FfmpegOptions}" Margin="0,5" 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].((vm:SettingsPageViewModel)DataContext).RemoveFfmpegParam}"
|
||||
CommandParameter="{Binding .}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</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>
|
||||
</controls:SettingsExpander>
|
||||
|
||||
<controls:SettingsExpander Header="Sonarr Settings"
|
||||
IconSource="Globe"
|
||||
Description="Adjust sonarr settings"
|
||||
IsEnabled="{Binding History}"
|
||||
IsExpanded="False">
|
||||
|
||||
<controls:SettingsExpanderItem Content="Host">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<TextBox HorizontalAlignment="Left" MinWidth="250"
|
||||
Text="{Binding SonarrHost}" />
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Port">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<TextBox HorizontalAlignment="Left" MinWidth="250"
|
||||
Text="{Binding SonarrPort}" />
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="API Key">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<TextBox HorizontalAlignment="Left" MinWidth="250"
|
||||
Text="{Binding SonarrApiKey}" />
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Use SSL">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding SonarrUseSsl}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Use Sonarr Numbering"
|
||||
Description="Potentially wrong if it couldn't be matched">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding SonarrUseSonarrNumbering}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
</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">
|
||||
|
||||
<controls:SettingsExpander.Footer>
|
||||
<ComboBox SelectedItem="{Binding CurrentAppTheme}"
|
||||
ItemsSource="{Binding AppThemes}"
|
||||
MinWidth="150" />
|
||||
</controls:SettingsExpander.Footer>
|
||||
|
||||
</controls:SettingsExpander>
|
||||
|
||||
<controls:SettingsExpander Header="App Accent Color"
|
||||
IconSource="ColorLine"
|
||||
Description="Set a custom accent color for the App"
|
||||
IsExpanded="False">
|
||||
|
||||
<controls:SettingsExpanderItem Content="Preview">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<Grid RowDefinitions="*,*,*,*"
|
||||
ColumnDefinitions="*,*"
|
||||
HorizontalAlignment="Right"
|
||||
Grid.Column="1">
|
||||
<Border Background="{DynamicResource SystemAccentColor}"
|
||||
Height="40" Grid.ColumnSpan="2">
|
||||
<TextBlock Text="SystemAccentColor"
|
||||
Foreground="{DynamicResource TextOnAccentFillColorPrimaryBrush}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
|
||||
<Border Background="{DynamicResource SystemAccentColorLight1}"
|
||||
Height="40" Width="90" Grid.Column="0" Grid.Row="1">
|
||||
<TextBlock Text="Light1"
|
||||
Foreground="{DynamicResource TextOnAccentFillColorPrimaryBrush}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<Border Background="{DynamicResource SystemAccentColorLight2}"
|
||||
Height="40" Width="90" Grid.Column="0" Grid.Row="2">
|
||||
<TextBlock Text="Light2"
|
||||
Foreground="{DynamicResource TextOnAccentFillColorPrimaryBrush}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<Border Background="{DynamicResource SystemAccentColorLight3}"
|
||||
Height="40" Width="90" Grid.Column="0" Grid.Row="3">
|
||||
<TextBlock Text="Light3"
|
||||
Foreground="{DynamicResource TextOnAccentFillColorPrimaryBrush}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
|
||||
<Border Background="{DynamicResource SystemAccentColorDark1}"
|
||||
Height="40" Width="90" Grid.Column="1" Grid.Row="1">
|
||||
<TextBlock Text="Dark1"
|
||||
Foreground="{DynamicResource TextOnAccentFillColorPrimaryBrush}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<Border Background="{DynamicResource SystemAccentColorDark2}"
|
||||
Height="40" Width="90" Grid.Column="1" Grid.Row="2">
|
||||
<TextBlock Text="Dark2"
|
||||
Foreground="{DynamicResource TextOnAccentFillColorPrimaryBrush}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<Border Background="{DynamicResource SystemAccentColorDark3}"
|
||||
Height="40" Width="90" Grid.Column="1" Grid.Row="3">
|
||||
<TextBlock Text="Dark3"
|
||||
Foreground="{DynamicResource TextOnAccentFillColorPrimaryBrush}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
</Grid>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem>
|
||||
<CheckBox Content="Use Custom Accent Color?"
|
||||
IsChecked="{Binding UseCustomAccent}"
|
||||
HorizontalAlignment="Right" />
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<StackPanel>
|
||||
<TextBlock Text="Pre-set Colors"
|
||||
Margin="24 24 0 0"
|
||||
IsVisible="{Binding UseCustomAccent}" />
|
||||
|
||||
<ListBox ItemsSource="{Binding PredefinedColors}"
|
||||
SelectedItem="{Binding ListBoxColor}"
|
||||
MaxWidth="441"
|
||||
AutoScrollToSelectedItem="False"
|
||||
Margin="24 0 24 12"
|
||||
HorizontalAlignment="Left"
|
||||
IsVisible="{Binding UseCustomAccent}">
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel />
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="Width" Value="48" />
|
||||
<Setter Property="Height" Value="48" />
|
||||
<Setter Property="MinWidth" Value="0" />
|
||||
<Setter Property="Margin" Value="1 1 0 0" />
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<Panel>
|
||||
<Border CornerRadius="{StaticResource ControlCornerRadius}"
|
||||
BorderThickness="2"
|
||||
Name="Root">
|
||||
<Border.Background>
|
||||
<SolidColorBrush Color="{Binding}" />
|
||||
</Border.Background>
|
||||
</Border>
|
||||
|
||||
<Border Name="Check"
|
||||
Background="{DynamicResource FocusStrokeColorOuter}"
|
||||
Width="20" Height="20"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Margin="0 2 2 0">
|
||||
<controls:SymbolIcon Symbol="Checkmark"
|
||||
Foreground="{DynamicResource SystemAccentColor}"
|
||||
FontSize="18" />
|
||||
</Border>
|
||||
</Panel>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem /template/ Border#Check">
|
||||
<Setter Property="IsVisible" Value="False" />
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem:pointerover /template/ Border#Root">
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource FocusStrokeColorOuter}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ListBoxItem:selected /template/ Border#Root">
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource FocusStrokeColorOuter}" />
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem:selected /template/ Border#Check">
|
||||
<Setter Property="IsVisible" Value="True" />
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
|
||||
</ListBox>
|
||||
|
||||
<Rectangle Fill="{DynamicResource ApplicationPageBackgroundThemeBrush}"
|
||||
Height="1"
|
||||
IsVisible="{Binding UseCustomAccent}" />
|
||||
|
||||
<DockPanel LastChildFill="False" Margin="24 6 0 0"
|
||||
IsVisible="{Binding UseCustomAccent}">
|
||||
<TextBlock Text="Custom Color"
|
||||
VerticalAlignment="Center"
|
||||
DockPanel.Dock="Left" />
|
||||
|
||||
<controls:ColorPickerButton Color="{Binding CustomAccentColor}"
|
||||
IsMoreButtonVisible="True"
|
||||
UseSpectrum="True"
|
||||
UseColorWheel="False"
|
||||
UseColorTriangle="False"
|
||||
UseColorPalette="False"
|
||||
IsCompact="True" ShowAcceptDismissButtons="True"
|
||||
DockPanel.Dock="Right" />
|
||||
</DockPanel>
|
||||
</StackPanel>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
</controls:SettingsExpander>
|
||||
|
||||
<controls:SettingsExpander Header="Log Mode"
|
||||
IconSource="Help"
|
||||
Description="Should only be enabled if something isn't working">
|
||||
<controls:SettingsExpander.Footer>
|
||||
<CheckBox IsChecked="{Binding LogMode}"> </CheckBox>
|
||||
</controls:SettingsExpander.Footer>
|
||||
|
||||
</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">
|
||||
|
||||
<DockPanel HorizontalAlignment="Center">
|
||||
<Image Source="/Assets/app_icon.ico"
|
||||
DockPanel.Dock="Left"
|
||||
Height="78"
|
||||
RenderOptions.BitmapInterpolationMode="HighQuality" />
|
||||
|
||||
<StackPanel Spacing="0" Margin="12 0">
|
||||
<TextBlock Text="Crunchy-Downloader"
|
||||
Theme="{StaticResource TitleTextBlockStyle}" />
|
||||
|
||||
<TextBlock Text="{Binding CurrentVersion}"
|
||||
Theme="{StaticResource BodyTextBlockStyle}" />
|
||||
|
||||
<TextBlock Theme="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="https://github.com/Crunchy-DL/Crunchy-Downloader"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
|
||||
</Grid>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<controls:TabView TabItems="{Binding Tabs}"
|
||||
AllowDropTabs="False" IsAddTabButtonVisible="False"
|
||||
Background="Transparent" CanDragTabs="False" CanReorderTabs="False"
|
||||
VerticalAlignment="Stretch">
|
||||
</controls:TabView>
|
||||
|
||||
</UserControl>
|
||||
|
|
@ -1,12 +1,7 @@
|
|||
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;
|
||||
using CRD.ViewModels;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Threading;
|
||||
|
|
|
|||
195
CRD/Views/UpcomingSeasonsPageView.axaml
Normal file
195
CRD/Views/UpcomingSeasonsPageView.axaml
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
<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:vm="clr-namespace:CRD.ViewModels"
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:structs="clr-namespace:CRD.Utils.Structs"
|
||||
x:DataType="vm:UpcomingPageViewModel"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="CRD.Views.UpcomingPageView">
|
||||
|
||||
<Grid>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="10">
|
||||
<ItemsControl ItemsSource="{Binding Seasons}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type structs:SeasonViewModel}">
|
||||
<Button
|
||||
Width="120"
|
||||
Height="50"
|
||||
HorizontalContentAlignment="Center"
|
||||
Command="{Binding $parent[UserControl].((vm:UpcomingPageViewModel)DataContext).SelectSeasonCommand}"
|
||||
CommandParameter="{Binding}"
|
||||
Margin="5">
|
||||
|
||||
<Button.Content>
|
||||
<StackPanel Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<TextBlock IsVisible="{Binding IsSelected}" Text="{Binding Season}" FontSize="16" FontWeight="Bold" HorizontalAlignment="Center" />
|
||||
<TextBlock IsVisible="{Binding !IsSelected}" Foreground="Gray" Text="{Binding Season}" FontSize="16" FontWeight="Bold" HorizontalAlignment="Center" />
|
||||
<TextBlock Text="{Binding Year}" FontSize="12" HorizontalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Button.Content>
|
||||
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
<ListBox Grid.Row="1" IsVisible="{Binding !IsLoading}" ItemsSource="{Binding SelectedSeason}"
|
||||
SelectedItem="{Binding SelectedSeries, Mode=TwoWay}" SelectedIndex="{Binding SelectedIndex}" SelectionChanged="SelectionChanged"
|
||||
Margin="5">
|
||||
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel Orientation="Horizontal"></WrapPanel>
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
|
||||
<!-- <ListBox.Styles> -->
|
||||
<!-- <Style Selector="ListBoxItem:selected /template/ ContentPresenter"> -->
|
||||
<!-- <Setter Property="Background" Value="Transparent"/> -->
|
||||
<!-- </Style> -->
|
||||
<!-- <Style Selector="ListBoxItem:selected /template/ Rectangle"> -->
|
||||
<!-- <Setter Property="IsVisible" Value="False"/> -->
|
||||
<!-- </Style> -->
|
||||
<!-- </ListBox.Styles> -->
|
||||
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
|
||||
<Grid>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="185" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Grid.Column="0" Orientation="Vertical" HorizontalAlignment="Center"
|
||||
Width="185"
|
||||
Height="315"
|
||||
Margin="5">
|
||||
<Grid>
|
||||
<Image Source="{Binding ThumbnailImage}"
|
||||
Width="185"
|
||||
Height="265">
|
||||
</Image>
|
||||
|
||||
<StackPanel VerticalAlignment="Top" HorizontalAlignment="Right"
|
||||
IsVisible="{Binding IsInHistory}" Margin="0 5 5 5" >
|
||||
<Border Background="DarkGray" CornerRadius="50">
|
||||
<controls:SymbolIcon Symbol="Library" Foreground="Black" FontSize="22" Margin="2" />
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="Series is in History" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
|
||||
<TextBlock HorizontalAlignment="Center" TextAlignment="Center"
|
||||
Text="{Binding Title.English}"
|
||||
TextWrapping="Wrap"
|
||||
Width="185"
|
||||
FontSize="12"
|
||||
Height="35"
|
||||
Margin="4,0,4,0">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="{Binding Title.English}" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock HorizontalAlignment="Center" TextAlignment="Center"
|
||||
Text="{Binding StartDateForm}"
|
||||
TextWrapping="Wrap"
|
||||
Width="185"
|
||||
FontSize="12"
|
||||
MaxHeight="20"
|
||||
Margin="4,0,4,0">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="{Binding StartDateForm}" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
</TextBlock>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<Expander Grid.Column="0" Grid.ColumnSpan="2" ExpandDirection="Right" >
|
||||
<Expander.Styles>
|
||||
<Style Selector="Expander:not(:expanded) /template/ ToggleButton#ExpanderHeader">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
</Style>
|
||||
<Style Selector="Expander:expanded /template/ ToggleButton#ExpanderHeader">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
</Style>
|
||||
<Style Selector="ToggleButton:pointerover /template/ Border#ExpandCollapseChevronBorder">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
<Style Selector="ToggleButton:not(:checked) /template/ TextBlock#ExpandCollapseChevron">
|
||||
<Setter Property="Foreground" Value="Transparent" />
|
||||
</Style>
|
||||
<Style Selector="ToggleButton:checked /template/ TextBlock#ExpandCollapseChevron">
|
||||
<Setter Property="Foreground" Value="Transparent" />
|
||||
</Style>
|
||||
</Expander.Styles>
|
||||
<Expander.Header>
|
||||
<Border Width="117" Height="315" />
|
||||
</Expander.Header>
|
||||
<Expander.Content>
|
||||
|
||||
<StackPanel>
|
||||
|
||||
<ScrollViewer MaxHeight="265" MinHeight="265" PointerWheelChanged="ScrollViewer_PointerWheelChanged" Margin="0 0 0 5">
|
||||
<TextBlock HorizontalAlignment="Center" TextAlignment="Center"
|
||||
Text="{Binding Description}"
|
||||
TextWrapping="Wrap"
|
||||
Width="185"
|
||||
FontSize="16"
|
||||
Margin="5">
|
||||
</TextBlock>
|
||||
</ScrollViewer>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Bottom">
|
||||
<Button HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="Trailer" Margin=" 0 0 5 0"
|
||||
Command="{Binding $parent[UserControl].((vm:UpcomingPageViewModel)DataContext).OpenTrailer}"
|
||||
CommandParameter="{Binding}"></Button>
|
||||
<StackPanel IsVisible="{Binding HasCrID}">
|
||||
<Button HorizontalAlignment="Right" VerticalAlignment="Bottom"
|
||||
IsVisible="{Binding !IsInHistory}"
|
||||
Command="{Binding $parent[UserControl].((vm:UpcomingPageViewModel)DataContext).AddToHistory}"
|
||||
CommandParameter="{Binding}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Library" FontSize="20" />
|
||||
<controls:SymbolIcon Symbol="Add" FontSize="20" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Expander.Content>
|
||||
</Expander>
|
||||
|
||||
</Grid>
|
||||
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<controls:ProgressRing Grid.Row="1" IsVisible="{Binding IsLoading}" VerticalAlignment="Center" HorizontalAlignment="Center" Width="100" Height="100"></controls:ProgressRing>
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
||||
34
CRD/Views/UpcomingSeasonsPageView.axaml.cs
Normal file
34
CRD/Views/UpcomingSeasonsPageView.axaml.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
using System;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using CRD.Utils.Sonarr;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.ViewModels;
|
||||
|
||||
namespace CRD.Views;
|
||||
|
||||
public partial class UpcomingPageView : UserControl{
|
||||
public UpcomingPageView(){
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void SelectionChanged(object? sender, SelectionChangedEventArgs e){
|
||||
if (DataContext is UpcomingPageViewModel viewModel && sender is ListBox listBox){
|
||||
viewModel.SelectionChangedOfSeries((AnilistSeries?)listBox.SelectedItem);
|
||||
}
|
||||
}
|
||||
|
||||
private void ScrollViewer_PointerWheelChanged(object sender, Avalonia.Input.PointerWheelEventArgs e){
|
||||
if (sender is ScrollViewer scrollViewer){
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
41
CRD/Views/Utils/ContentDialogDropdownSelectView.axaml
Normal file
41
CRD/Views/Utils/ContentDialogDropdownSelectView.axaml
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<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"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
xmlns:utils="clr-namespace:CRD.ViewModels.Utils"
|
||||
xmlns:structs="clr-namespace:CRD.Utils.Structs"
|
||||
x:DataType="utils:ContentDialogDropdownSelectViewModel"
|
||||
x:Class="CRD.Views.Utils.ContentDialogDropdownSelectView">
|
||||
|
||||
|
||||
<StackPanel Spacing="10" MinWidth="400">
|
||||
<TextBlock Text="{Binding EpisodeInfo}"></TextBlock>
|
||||
|
||||
<ComboBox HorizontalAlignment="Center" HorizontalContentAlignment="Center" MinWidth="210" MaxDropDownHeight="400"
|
||||
ItemsSource="{Binding DropDownItemList}"
|
||||
SelectedItem="{Binding SelectedDropdownItem}">
|
||||
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type structs:StringItem}">
|
||||
<TextBlock Text="{Binding stringValue}"></TextBlock>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
|
||||
</ComboBox>
|
||||
|
||||
|
||||
<!-- <ListBox SelectionMode="Single" Width="210" -->
|
||||
<!-- MaxHeight="400" -->
|
||||
<!-- ItemsSource="{Binding DropDownItemList , Mode=OneWay}" -->
|
||||
<!-- SelectedItems="{Binding SelectedDropdownItem}"> -->
|
||||
<!-- <ListBox.ItemTemplate> -->
|
||||
<!-- <DataTemplate DataType="{x:Type structs:StringItem}"> -->
|
||||
<!-- <TextBlock Text="{Binding stringValue}"></TextBlock> -->
|
||||
<!-- </DataTemplate> -->
|
||||
<!-- </ListBox.ItemTemplate> -->
|
||||
<!-- </ListBox> -->
|
||||
|
||||
</StackPanel>
|
||||
|
||||
</UserControl>
|
||||
9
CRD/Views/Utils/ContentDialogDropdownSelectView.axaml.cs
Normal file
9
CRD/Views/Utils/ContentDialogDropdownSelectView.axaml.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
using Avalonia.Controls;
|
||||
|
||||
namespace CRD.Views.Utils;
|
||||
|
||||
public partial class ContentDialogDropdownSelectView : UserControl{
|
||||
public ContentDialogDropdownSelectView(){
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,4 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace CRD.Views.Utils;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace CRD.Views.Utils;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace CRD.Views.Utils;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace CRD.Views.Utils;
|
||||
|
||||
|
|
|
|||
516
CRD/Views/Utils/GeneralSettingsView.axaml
Normal file
516
CRD/Views/Utils/GeneralSettingsView.axaml
Normal file
|
|
@ -0,0 +1,516 @@
|
|||
<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:vm="clr-namespace:CRD.ViewModels.Utils"
|
||||
x:DataType="vm:GeneralSettingsViewModel"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="CRD.Views.Utils.GeneralSettingsView">
|
||||
|
||||
|
||||
<Design.DataContext>
|
||||
<vm:GeneralSettingsViewModel />
|
||||
</Design.DataContext>
|
||||
|
||||
|
||||
<ScrollViewer Padding="20 20 20 0">
|
||||
<StackPanel Spacing="8">
|
||||
|
||||
<controls:SettingsExpander Header="History"
|
||||
IconSource="Clock"
|
||||
Description="Change if the download history is recorded">
|
||||
<controls:SettingsExpander.Footer>
|
||||
<CheckBox IsChecked="{Binding History}"> </CheckBox>
|
||||
</controls:SettingsExpander.Footer>
|
||||
|
||||
<controls:SettingsExpanderItem Content="History Language" Description="Use the same language as Sonarr if you plan to connect it to this downloader">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<ComboBox HorizontalContentAlignment="Center" MinWidth="210" MaxDropDownHeight="400"
|
||||
ItemsSource="{Binding HistoryLangList}"
|
||||
SelectedItem="{Binding SelectedHistoryLang}">
|
||||
</ComboBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="History Add Specials" Description="Add specials to the queue if they weren't downloaded before">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding HistoryAddSpecials}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="History Missing/New Count from Sonarr" Description="The missing count (number in the orange corner) will count the episodes missing from sonarr">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding HistoryCountSonarr}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
</controls:SettingsExpander>
|
||||
|
||||
<controls:SettingsExpander Header="Download Settings"
|
||||
IconSource="Download"
|
||||
Description="Adjust download settings"
|
||||
IsExpanded="False">
|
||||
|
||||
<controls:SettingsExpanderItem Content="Max Download Speed"
|
||||
Description="Download in Kb/s - 0 is full speed">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<controls:NumberBox Minimum="0" Maximum="1000000000"
|
||||
Value="{Binding DownloadSpeed}"
|
||||
SpinButtonPlacementMode="Hidden"
|
||||
HorizontalAlignment="Stretch" />
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Use Temp Download Folder">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock IsVisible="{Binding DownloadToTempFolder}" FontSize="15" Opacity="0.8" TextWrapping="NoWrap" Text="{Binding TempDownloadDirPath, Mode=OneWay}" TextAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center" />
|
||||
<Button IsVisible="{Binding DownloadToTempFolder}" Margin="10 0 10 0" FontStyle="Italic"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding OpenFolderDialogTempFolderAsync}">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="Set Temp Download Directory" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Folder" FontSize="18" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<Button IsVisible="{Binding DownloadToTempFolder}" Margin="0 0 10 0" FontStyle="Italic"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding ClearDownloadTempDirPath}">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="Reset to default" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Clear" FontSize="18" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<CheckBox IsChecked="{Binding DownloadToTempFolder}"> </CheckBox>
|
||||
</StackPanel>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Download Folder">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock FontSize="15" Opacity="0.8" TextWrapping="NoWrap" Text="{Binding DownloadDirPath, Mode=OneWay}" TextAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center" />
|
||||
<Button Margin="10 0 0 0" FontStyle="Italic"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding OpenFolderDialogAsync}">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="Set Download Directory" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Folder" FontSize="18" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<Button Margin="10 0 0 0" FontStyle="Italic"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding ClearDownloadDirPath}">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="Reset to default" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SymbolIcon Symbol="Clear" FontSize="18" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Simultaneous Downloads">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<controls:NumberBox Minimum="0" Maximum="10"
|
||||
Value="{Binding SimultaneousDownloads}"
|
||||
SpinButtonPlacementMode="Inline"
|
||||
HorizontalAlignment="Stretch" />
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpander.Footer>
|
||||
</controls:SettingsExpander.Footer>
|
||||
</controls:SettingsExpander>
|
||||
|
||||
<controls:SettingsExpander Header="Sonarr Settings"
|
||||
IconSource="Globe"
|
||||
Description="Adjust Sonarr settings"
|
||||
IsEnabled="{Binding History}"
|
||||
IsExpanded="False">
|
||||
|
||||
<controls:SettingsExpanderItem Content="Host">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<TextBox HorizontalAlignment="Left" MinWidth="250"
|
||||
Text="{Binding SonarrHost}" />
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Port">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<TextBox HorizontalAlignment="Left" MinWidth="250"
|
||||
Text="{Binding SonarrPort}" />
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="API Key">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<TextBox HorizontalAlignment="Left" MinWidth="250"
|
||||
Text="{Binding SonarrApiKey}" />
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Use SSL">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding SonarrUseSsl}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Use Sonarr Numbering"
|
||||
Description="May be incorrect if unable to match episodes">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding SonarrUseSonarrNumbering}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
</controls:SettingsExpander>
|
||||
|
||||
<controls:SettingsExpander Header="Proxy Settings"
|
||||
IconSource="Wifi3"
|
||||
Description="Changes will take effect after a restart"
|
||||
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 Appearance"
|
||||
IconSource="DarkTheme"
|
||||
Description="Customize the look and feel of the application"
|
||||
IsExpanded="False">
|
||||
|
||||
<controls:SettingsExpanderItem Content="App Theme" Description="Select the theme for the application (Light, Dark, System)">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<ComboBox SelectedItem="{Binding CurrentAppTheme}"
|
||||
ItemsSource="{Binding AppThemes}"
|
||||
MinWidth="150" />
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Background image">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<StackPanel Spacing="10">
|
||||
<StackPanel Orientation="Horizontal" Spacing="10" HorizontalAlignment="Right">
|
||||
<TextBlock Text="{Binding BackgroundImagePath, Mode=OneWay}"
|
||||
FontSize="15"
|
||||
Opacity="0.8"
|
||||
TextWrapping="NoWrap"
|
||||
TextAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
|
||||
<Button Command="{Binding OpenImageFileDialogAsyncInternalBackgroundImage}"
|
||||
VerticalAlignment="Center"
|
||||
FontStyle="Italic">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="Select Background Image" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
<StackPanel Orientation="Horizontal" Spacing="5">
|
||||
<controls:SymbolIcon Symbol="Folder" FontSize="18" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<Button Command="{Binding ClearBackgroundImagePath}"
|
||||
VerticalAlignment="Center"
|
||||
FontStyle="Italic">
|
||||
<ToolTip.Tip>
|
||||
<TextBlock Text="Remove Background Image Path" FontSize="15" />
|
||||
</ToolTip.Tip>
|
||||
<StackPanel Orientation="Horizontal" Spacing="5">
|
||||
<controls:SymbolIcon Symbol="Clear" FontSize="18" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
<Grid HorizontalAlignment="Right" Margin="0 5 0 0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="150" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Text="Opacity"
|
||||
FontSize="15"
|
||||
Opacity="0.8"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0 0 5 10"
|
||||
Grid.Row="0" Grid.Column="0" />
|
||||
<controls:NumberBox Minimum="0" Maximum="1"
|
||||
SmallChange="0.05"
|
||||
LargeChange="0.1"
|
||||
SimpleNumberFormat="F2"
|
||||
Value="{Binding BackgroundImageOpacity}"
|
||||
SpinButtonPlacementMode="Inline"
|
||||
HorizontalAlignment="Stretch"
|
||||
Margin="0 0 0 10"
|
||||
Grid.Row="0" Grid.Column="1" />
|
||||
|
||||
<TextBlock Text="Blur Radius"
|
||||
FontSize="15"
|
||||
Opacity="0.8"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0 0 5 0"
|
||||
Grid.Row="1" Grid.Column="0" />
|
||||
<controls:NumberBox Minimum="0" Maximum="40"
|
||||
SmallChange="1"
|
||||
LargeChange="5"
|
||||
SimpleNumberFormat="F0"
|
||||
Value="{Binding BackgroundImageBlurRadius}"
|
||||
SpinButtonPlacementMode="Inline"
|
||||
HorizontalAlignment="Stretch"
|
||||
Grid.Row="1" Grid.Column="1" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="App accent color preview" Description="Choose a custom accent color for the application">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<Grid RowDefinitions="*,*,*,*"
|
||||
ColumnDefinitions="*,*"
|
||||
HorizontalAlignment="Right"
|
||||
Grid.Column="1">
|
||||
<Border Background="{DynamicResource SystemAccentColor}"
|
||||
Height="40" Grid.ColumnSpan="2">
|
||||
<TextBlock Text="SystemAccentColor"
|
||||
Foreground="{DynamicResource TextOnAccentFillColorPrimaryBrush}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
|
||||
<Border Background="{DynamicResource SystemAccentColorLight1}"
|
||||
Height="40" Width="90" Grid.Column="0" Grid.Row="1">
|
||||
<TextBlock Text="Light1"
|
||||
Foreground="{DynamicResource TextOnAccentFillColorPrimaryBrush}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<Border Background="{DynamicResource SystemAccentColorLight2}"
|
||||
Height="40" Width="90" Grid.Column="0" Grid.Row="2">
|
||||
<TextBlock Text="Light2"
|
||||
Foreground="{DynamicResource TextOnAccentFillColorPrimaryBrush}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<Border Background="{DynamicResource SystemAccentColorLight3}"
|
||||
Height="40" Width="90" Grid.Column="0" Grid.Row="3">
|
||||
<TextBlock Text="Light3"
|
||||
Foreground="{DynamicResource TextOnAccentFillColorPrimaryBrush}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
|
||||
<Border Background="{DynamicResource SystemAccentColorDark1}"
|
||||
Height="40" Width="90" Grid.Column="1" Grid.Row="1">
|
||||
<TextBlock Text="Dark1"
|
||||
Foreground="{DynamicResource TextOnAccentFillColorPrimaryBrush}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<Border Background="{DynamicResource SystemAccentColorDark2}"
|
||||
Height="40" Width="90" Grid.Column="1" Grid.Row="2">
|
||||
<TextBlock Text="Dark2"
|
||||
Foreground="{DynamicResource TextOnAccentFillColorPrimaryBrush}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<Border Background="{DynamicResource SystemAccentColorDark3}"
|
||||
Height="40" Width="90" Grid.Column="1" Grid.Row="3">
|
||||
<TextBlock Text="Dark3"
|
||||
Foreground="{DynamicResource TextOnAccentFillColorPrimaryBrush}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
</Grid>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem>
|
||||
<CheckBox Content="Use Custom Accent Color?"
|
||||
IsChecked="{Binding UseCustomAccent}"
|
||||
HorizontalAlignment="Right" />
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<StackPanel>
|
||||
<TextBlock Text="Pre-set Colors"
|
||||
Margin="24 24 0 0"
|
||||
IsVisible="{Binding UseCustomAccent}" />
|
||||
|
||||
<ListBox ItemsSource="{Binding PredefinedColors}"
|
||||
SelectedItem="{Binding ListBoxColor}"
|
||||
MaxWidth="441"
|
||||
AutoScrollToSelectedItem="False"
|
||||
Margin="24 0 24 12"
|
||||
HorizontalAlignment="Left"
|
||||
IsVisible="{Binding UseCustomAccent}">
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel />
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="Width" Value="48" />
|
||||
<Setter Property="Height" Value="48" />
|
||||
<Setter Property="MinWidth" Value="0" />
|
||||
<Setter Property="Margin" Value="1 1 0 0" />
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<Panel>
|
||||
<Border CornerRadius="{StaticResource ControlCornerRadius}"
|
||||
BorderThickness="2"
|
||||
Name="Root">
|
||||
<Border.Background>
|
||||
<SolidColorBrush Color="{Binding}" />
|
||||
</Border.Background>
|
||||
</Border>
|
||||
|
||||
<Border Name="Check"
|
||||
Background="{DynamicResource FocusStrokeColorOuter}"
|
||||
Width="20" Height="20"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Margin="0 2 2 0">
|
||||
<controls:SymbolIcon Symbol="Checkmark"
|
||||
Foreground="{DynamicResource SystemAccentColor}"
|
||||
FontSize="18" />
|
||||
</Border>
|
||||
</Panel>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem /template/ Border#Check">
|
||||
<Setter Property="IsVisible" Value="False" />
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem:pointerover /template/ Border#Root">
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource FocusStrokeColorOuter}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ListBoxItem:selected /template/ Border#Root">
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource FocusStrokeColorOuter}" />
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem:selected /template/ Border#Check">
|
||||
<Setter Property="IsVisible" Value="True" />
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
|
||||
</ListBox>
|
||||
|
||||
<Rectangle Fill="{DynamicResource ApplicationPageBackgroundThemeBrush}"
|
||||
Height="1"
|
||||
IsVisible="{Binding UseCustomAccent}" />
|
||||
|
||||
<DockPanel LastChildFill="False" Margin="24 6 0 0"
|
||||
IsVisible="{Binding UseCustomAccent}">
|
||||
<TextBlock Text="Custom Color"
|
||||
VerticalAlignment="Center"
|
||||
DockPanel.Dock="Left" />
|
||||
|
||||
<controls:ColorPickerButton Color="{Binding CustomAccentColor}"
|
||||
IsMoreButtonVisible="True"
|
||||
UseSpectrum="True"
|
||||
UseColorWheel="False"
|
||||
UseColorTriangle="False"
|
||||
UseColorPalette="False"
|
||||
IsCompact="True" ShowAcceptDismissButtons="True"
|
||||
DockPanel.Dock="Right" />
|
||||
</DockPanel>
|
||||
</StackPanel>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
</controls:SettingsExpander>
|
||||
|
||||
<controls:SettingsExpander Header="Debug"
|
||||
IconSource="Help"
|
||||
Description="Tools and options for debugging and troubleshooting issues">
|
||||
|
||||
<controls:SettingsExpanderItem Content="Log Mode" Description="Enable error logging. Recommended only for troubleshooting issues">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding LogMode}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="IP" Description="Check your current IP address to confirm if traffic is routed through a VPN">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<Grid VerticalAlignment="Center">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Border VerticalAlignment="Center" Height="30">
|
||||
<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:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
</controls:SettingsExpander>
|
||||
|
||||
<Grid Margin="0 0 0 10"
|
||||
ColumnDefinitions="*,Auto" RowDefinitions="*,Auto">
|
||||
|
||||
<DockPanel HorizontalAlignment="Center">
|
||||
<Image Source="/Assets/app_icon.ico"
|
||||
DockPanel.Dock="Left"
|
||||
Height="78"
|
||||
RenderOptions.BitmapInterpolationMode="HighQuality" />
|
||||
|
||||
<StackPanel Spacing="0" Margin="12 0">
|
||||
<TextBlock Text="Crunchy-Downloader"
|
||||
Theme="{StaticResource TitleTextBlockStyle}" />
|
||||
|
||||
<TextBlock Text="{Binding CurrentVersion}"
|
||||
Theme="{StaticResource BodyTextBlockStyle}" />
|
||||
|
||||
<TextBlock Theme="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="https://github.com/Crunchy-DL/Crunchy-Downloader"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
|
||||
</Grid>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
|
||||
</UserControl>
|
||||
9
CRD/Views/Utils/GeneralSettingsView.axaml.cs
Normal file
9
CRD/Views/Utils/GeneralSettingsView.axaml.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
using Avalonia.Controls;
|
||||
|
||||
namespace CRD.Views.Utils;
|
||||
|
||||
public partial class GeneralSettingsView : UserControl{
|
||||
public GeneralSettingsView(){
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
|
@ -15,4 +15,11 @@
|
|||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
|
||||
<ws2:longPathAware>true</ws2:longPathAware>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
|
||||
</assembly>
|
||||
|
|
|
|||
Loading…
Reference in a new issue