Add - Added fields to add mkvmerge & ffmpeg parameters to settings
Add - Added a new way to check the premium status of an account Add - Added default audio and sub to muxing settings Add - Added option to keep subtitles as files and not merge them Add - Added option to download videos for all dubs selected Chg - FFmpeg now also adds chapters to the files Chg - Shows error if decryption files are missing
This commit is contained in:
parent
8fc0812448
commit
272d59a03b
@ -35,7 +35,7 @@ public class CrAuth{
|
||||
if (response.IsOk){
|
||||
JsonTokenToFileAndVariable(response.ResponseContent);
|
||||
} else{
|
||||
Console.WriteLine("Anonymous login failed");
|
||||
Console.Error.WriteLine("Anonymous login failed");
|
||||
}
|
||||
|
||||
crunInstance.Profile = new CrProfile{
|
||||
@ -104,13 +104,27 @@ 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 responseSubs = await HttpClientReq.Instance.SendHttpRequest(requestSubs);
|
||||
|
||||
if (responseSubs.IsOk){
|
||||
var subsc = Helpers.Deserialize<Subscription>(responseSubs.ResponseContent, crunInstance.SettingsJsonSerializerSettings);
|
||||
crunInstance.Profile.Subscription = subsc;
|
||||
crunInstance.Profile.HasPremium = subsc.IsActive;
|
||||
} else{
|
||||
crunInstance.Profile.HasPremium = false;
|
||||
Console.Error.WriteLine("Failed to check premium subscription status");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async void LoginWithToken(){
|
||||
if (crunInstance.Token?.refresh_token == null){
|
||||
Console.WriteLine("Missing Refresh Token");
|
||||
Console.Error.WriteLine("Missing Refresh Token");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -133,7 +147,7 @@ public class CrAuth{
|
||||
if (response.IsOk){
|
||||
JsonTokenToFileAndVariable(response.ResponseContent);
|
||||
} else{
|
||||
Console.WriteLine("Token Auth Failed");
|
||||
Console.Error.WriteLine("Token Auth Failed");
|
||||
}
|
||||
|
||||
if (crunInstance.Token?.refresh_token != null){
|
||||
@ -176,7 +190,7 @@ public class CrAuth{
|
||||
if (response.IsOk){
|
||||
JsonTokenToFileAndVariable(response.ResponseContent);
|
||||
} else{
|
||||
Console.WriteLine("Refresh Token Auth Failed");
|
||||
Console.Error.WriteLine("Refresh Token Auth Failed");
|
||||
}
|
||||
|
||||
await GetCmsToken();
|
||||
@ -185,7 +199,7 @@ public class CrAuth{
|
||||
|
||||
public async Task GetCmsToken(){
|
||||
if (crunInstance.Token?.access_token == null){
|
||||
Console.WriteLine($"Missing Access Token: {crunInstance.Token?.access_token}");
|
||||
Console.Error.WriteLine($"Missing Access Token: {crunInstance.Token?.access_token}");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -196,13 +210,13 @@ public class CrAuth{
|
||||
if (response.IsOk){
|
||||
crunInstance.CmsToken = JsonConvert.DeserializeObject<CrCmsToken>(response.ResponseContent, crunInstance.SettingsJsonSerializerSettings);
|
||||
} else{
|
||||
Console.WriteLine("CMS Token Auth Failed");
|
||||
Console.Error.WriteLine("CMS Token Auth Failed");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task GetCmsData(){
|
||||
if (crunInstance.CmsToken?.Cms == null){
|
||||
Console.WriteLine("Missing CMS Token");
|
||||
Console.Error.WriteLine("Missing CMS Token");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -224,7 +238,7 @@ public class CrAuth{
|
||||
if (response.IsOk){
|
||||
Console.WriteLine(response.ResponseContent);
|
||||
} else{
|
||||
Console.WriteLine("Failed to Get CMS Index");
|
||||
Console.Error.WriteLine("Failed to Get CMS Index");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
@ -19,7 +19,7 @@ public class CrEpisode(){
|
||||
|
||||
public async Task<CrunchyEpisodeList?> ParseEpisodeById(string id,string locale){
|
||||
if (crunInstance.CmsToken?.Cms == null){
|
||||
Console.WriteLine("Missing CMS Access Token");
|
||||
Console.Error.WriteLine("Missing CMS Access Token");
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ public class CrEpisode(){
|
||||
var response = await HttpClientReq.Instance.SendHttpRequest(request);
|
||||
|
||||
if (!response.IsOk){
|
||||
Console.WriteLine("Series Request Failed");
|
||||
Console.Error.WriteLine("Series Request Failed");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,9 @@ using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using CRD.Utils;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Views;
|
||||
using Newtonsoft.Json;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace CRD.Downloader;
|
||||
|
||||
@ -45,10 +47,15 @@ public class CrSeries(){
|
||||
foreach (var kvp in eps){
|
||||
var key = kvp.Key;
|
||||
var episode = kvp.Value;
|
||||
|
||||
|
||||
for (int index = 0; index < episode.Items.Count; index++){
|
||||
var item = episode.Items[index];
|
||||
|
||||
if (item.IsPremiumOnly && !crunInstance.Profile.HasPremium){
|
||||
MessageBus.Current.SendMessage(new ToastMessage($"Episode is a premium episode – make sure that you are signed in with an account that has an active premium subscription", ToastType.Error, 3));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!dubLang.Contains(episode.Langs[index].CrLocale))
|
||||
continue;
|
||||
|
||||
@ -133,7 +140,7 @@ public class CrSeries(){
|
||||
CrSeriesSearch? parsedSeries = await ParseSeriesById(id,Locale); // one piece - GRMG8ZQZR
|
||||
|
||||
if (parsedSeries == null){
|
||||
Console.WriteLine("Parse Data Invalid");
|
||||
Console.Error.WriteLine("Parse Data Invalid");
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -281,7 +288,7 @@ public class CrSeries(){
|
||||
CrunchyEpisodeList episodeList = new CrunchyEpisodeList(){ Data = new List<CrunchyEpisode>(), Total = 0, Meta = new Meta() };
|
||||
|
||||
if (crunInstance.CmsToken?.Cms == null){
|
||||
Console.WriteLine("Missing CMS Token");
|
||||
Console.Error.WriteLine("Missing CMS Token");
|
||||
return episodeList;
|
||||
}
|
||||
|
||||
@ -291,14 +298,12 @@ public class CrSeries(){
|
||||
var response = await HttpClientReq.Instance.SendHttpRequest(showRequest);
|
||||
|
||||
if (!response.IsOk){
|
||||
Console.WriteLine("Show Request FAILED!");
|
||||
Console.Error.WriteLine("Show Request FAILED!");
|
||||
} else{
|
||||
Console.WriteLine(response.ResponseContent);
|
||||
}
|
||||
}
|
||||
|
||||
//TODO
|
||||
|
||||
var episodeRequest = new HttpRequestMessage(HttpMethod.Get, $"{Api.Cms}/seasons/{seasonID}/episodes?preferred_audio_language=ja-JP");
|
||||
|
||||
episodeRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", crunInstance.Token?.access_token);
|
||||
@ -306,13 +311,13 @@ public class CrSeries(){
|
||||
var episodeRequestResponse = await HttpClientReq.Instance.SendHttpRequest(episodeRequest);
|
||||
|
||||
if (!episodeRequestResponse.IsOk){
|
||||
Console.WriteLine($"Episode List Request FAILED! uri: {episodeRequest.RequestUri}");
|
||||
Console.Error.WriteLine($"Episode List Request FAILED! uri: {episodeRequest.RequestUri}");
|
||||
} else{
|
||||
episodeList = Helpers.Deserialize<CrunchyEpisodeList>(episodeRequestResponse.ResponseContent, crunInstance.SettingsJsonSerializerSettings);
|
||||
}
|
||||
|
||||
if (episodeList.Total < 1){
|
||||
Console.WriteLine("Season is empty!");
|
||||
Console.Error.WriteLine("Season is empty!");
|
||||
}
|
||||
|
||||
return episodeList;
|
||||
@ -352,7 +357,7 @@ public class CrSeries(){
|
||||
|
||||
public async Task<CrSeriesSearch?> ParseSeriesById(string id,string? locale){
|
||||
if (crunInstance.CmsToken?.Cms == null){
|
||||
Console.WriteLine("Missing CMS Access Token");
|
||||
Console.Error.WriteLine("Missing CMS Access Token");
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -369,7 +374,7 @@ public class CrSeries(){
|
||||
var response = await HttpClientReq.Instance.SendHttpRequest(request);
|
||||
|
||||
if (!response.IsOk){
|
||||
Console.WriteLine("Series Request Failed");
|
||||
Console.Error.WriteLine("Series Request Failed");
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -385,7 +390,7 @@ public class CrSeries(){
|
||||
|
||||
public async Task<CrSeriesBase?> SeriesById(string id){
|
||||
if (crunInstance.CmsToken?.Cms == null){
|
||||
Console.WriteLine("Missing CMS Access Token");
|
||||
Console.Error.WriteLine("Missing CMS Access Token");
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -398,7 +403,7 @@ public class CrSeries(){
|
||||
var response = await HttpClientReq.Instance.SendHttpRequest(request);
|
||||
|
||||
if (!response.IsOk){
|
||||
Console.WriteLine("Series Request Failed");
|
||||
Console.Error.WriteLine("Series Request Failed");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -114,7 +114,8 @@ public class Crunchyroll{
|
||||
Username = "???",
|
||||
Avatar = "003-cr-hime-excited.png",
|
||||
PreferredContentAudioLanguage = "ja-JP",
|
||||
PreferredContentSubtitleLanguage = "de-DE"
|
||||
PreferredContentSubtitleLanguage = "de-DE",
|
||||
HasPremium = false,
|
||||
};
|
||||
|
||||
|
||||
@ -132,14 +133,14 @@ public class Crunchyroll{
|
||||
CrunOptions.Chapters = true;
|
||||
CrunOptions.Hslang = "none";
|
||||
CrunOptions.Force = "Y";
|
||||
CrunOptions.FileName = "${showTitle} - S${season}E${episode} [${height}p]";
|
||||
CrunOptions.FileName = "${seriesTitle} - S${season}E${episode} [${height}p]";
|
||||
CrunOptions.Partsize = 10;
|
||||
CrunOptions.NoSubs = false;
|
||||
CrunOptions.DlSubs = new List<string>{ "de-DE" };
|
||||
CrunOptions.Skipmux = false;
|
||||
CrunOptions.MkvmergeOptions = new List<string>{ "--no-date", "--disable-track-statistics-tags", "--engage no_variable_data" };
|
||||
CrunOptions.DefaultAudio = Languages.FindLang("ja-JP");
|
||||
CrunOptions.DefaultSub = Languages.FindLang("de-DE");
|
||||
CrunOptions.FfmpegOptions = new();
|
||||
CrunOptions.DefaultAudio = "ja-JP";
|
||||
CrunOptions.DefaultSub = "de-DE";
|
||||
CrunOptions.CcTag = "cc";
|
||||
CrunOptions.FsRetryTime = 5;
|
||||
CrunOptions.Numbers = 2;
|
||||
@ -163,7 +164,7 @@ public class Crunchyroll{
|
||||
|
||||
RefreshSonarr();
|
||||
}
|
||||
|
||||
|
||||
if (CrunOptions.LogMode){
|
||||
CfgManager.EnableLogMode();
|
||||
} else{
|
||||
@ -269,7 +270,7 @@ public class Crunchyroll{
|
||||
week.CalendarDays.Add(calDay);
|
||||
}
|
||||
} else{
|
||||
Console.WriteLine("No days found in the HTML document.");
|
||||
Console.Error.WriteLine("No days found in the HTML document.");
|
||||
}
|
||||
|
||||
calendar[weeksMondayDate] = week;
|
||||
@ -285,7 +286,7 @@ public class Crunchyroll{
|
||||
|
||||
|
||||
if (episodeL != null){
|
||||
if (episodeL.Value.Data != null && episodeL.Value.Data.First().IsPremiumOnly && (episodeL.Value.Data.First().StreamsLink == null || Profile.Username == "???")){
|
||||
if (episodeL.Value.Data != null && episodeL.Value.Data.First().IsPremiumOnly && !Profile.HasPremium){
|
||||
MessageBus.Current.SendMessage(new ToastMessage($"Episode is a premium episode – make sure that you are signed in with an account that has an active premium subscription", ToastType.Error, 3));
|
||||
return;
|
||||
}
|
||||
@ -298,8 +299,13 @@ public class Crunchyroll{
|
||||
if (CrunOptions.SonarrProperties is{ SonarrEnabled: true, UseSonarrNumbering: true }){
|
||||
var historyEpisode = CrHistory.GetHistoryEpisode(crunchyEpMeta.ShowId, crunchyEpMeta.SeasonId, crunchyEpMeta.Data.First().MediaId);
|
||||
if (historyEpisode != null){
|
||||
crunchyEpMeta.EpisodeNumber = historyEpisode.SonarrEpisodeNumber;
|
||||
crunchyEpMeta.Season = historyEpisode.SonarrSeasonNumber;
|
||||
if (!string.IsNullOrEmpty(historyEpisode.SonarrEpisodeNumber)){
|
||||
crunchyEpMeta.EpisodeNumber = historyEpisode.SonarrEpisodeNumber;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(historyEpisode.SonarrSeasonNumber)){
|
||||
crunchyEpMeta.Season = historyEpisode.SonarrSeasonNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -321,8 +327,13 @@ public class Crunchyroll{
|
||||
if (CrunOptions.SonarrProperties is{ SonarrEnabled: true, UseSonarrNumbering: true }){
|
||||
var historyEpisode = CrHistory.GetHistoryEpisode(crunchyEpMeta.ShowId, crunchyEpMeta.SeasonId, crunchyEpMeta.Data.First().MediaId);
|
||||
if (historyEpisode != null){
|
||||
crunchyEpMeta.EpisodeNumber = historyEpisode.SonarrEpisodeNumber;
|
||||
crunchyEpMeta.Season = historyEpisode.SonarrSeasonNumber;
|
||||
if (!string.IsNullOrEmpty(historyEpisode.SonarrEpisodeNumber)){
|
||||
crunchyEpMeta.EpisodeNumber = historyEpisode.SonarrEpisodeNumber;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(historyEpisode.SonarrSeasonNumber)){
|
||||
crunchyEpMeta.Season = historyEpisode.SonarrSeasonNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -382,19 +393,19 @@ public class Crunchyroll{
|
||||
await MuxStreams(res.Data,
|
||||
new CrunchyMuxOptions{
|
||||
FfmpegOptions = options.FfmpegOptions,
|
||||
SkipSubMux = options.Skipmux,
|
||||
SkipSubMux = options.SkipSubsMux,
|
||||
Output = res.FileName,
|
||||
Mp4 = options.Mp4,
|
||||
VideoTitle = options.VideoTitle,
|
||||
Novids = options.Novids,
|
||||
NoCleanup = options.Nocleanup,
|
||||
DefaultAudio = options.DefaultAudio,
|
||||
DefaultSub = options.DefaultSub,
|
||||
DefaultAudio = Languages.FindLang(options.DefaultAudio),
|
||||
DefaultSub = Languages.FindLang(options.DefaultSub),
|
||||
MkvmergeOptions = options.MkvmergeOptions,
|
||||
ForceMuxer = options.Force,
|
||||
SyncTiming = options.SyncTiming,
|
||||
CcTag = options.CcTag,
|
||||
KeepAllVideos = false
|
||||
KeepAllVideos = true
|
||||
},
|
||||
res.FileName);
|
||||
|
||||
@ -423,8 +434,6 @@ public class Crunchyroll{
|
||||
}
|
||||
|
||||
private async Task MuxStreams(List<DownloadedMedia> data, CrunchyMuxOptions options, string filename){
|
||||
var hasAudioStreams = false;
|
||||
|
||||
var muxToMp3 = false;
|
||||
|
||||
if (options.Novids == true || data.FindAll(a => a.Type == DownloadMediaType.Video).Count == 0){
|
||||
@ -437,10 +446,6 @@ public class Crunchyroll{
|
||||
}
|
||||
}
|
||||
|
||||
if (data.Any(a => a.Type == DownloadMediaType.Audio)){
|
||||
hasAudioStreams = true;
|
||||
}
|
||||
|
||||
var subs = data.Where(a => a.Type == DownloadMediaType.Subtitle).ToList();
|
||||
var subsList = new List<SubtitleFonts>();
|
||||
|
||||
@ -465,15 +470,13 @@ public class Crunchyroll{
|
||||
|
||||
|
||||
var merger = new Merger(new MergerOptions{
|
||||
OnlyVid = hasAudioStreams ? data.Where(a => a.Type == DownloadMediaType.Video).Select(a => new MergerInput{ Language = a.Lang, Path = a.Path ?? string.Empty }).ToList() : new List<MergerInput>(),
|
||||
OnlyVid = data.Where(a => a.Type == DownloadMediaType.Video).Select(a => new MergerInput{ Language = a.Lang, Path = a.Path ?? string.Empty }).ToList(),
|
||||
SkipSubMux = options.SkipSubMux,
|
||||
OnlyAudio = hasAudioStreams ? data.Where(a => a.Type == DownloadMediaType.Audio).Select(a => new MergerInput{ Language = a.Lang, Path = a.Path ?? string.Empty }).ToList() : new List<MergerInput>(),
|
||||
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 }).ToList(),
|
||||
Simul = false,
|
||||
KeepAllVideos = options.KeepAllVideos,
|
||||
Fonts = FontsManager.Instance.MakeFontsList(CfgManager.PathFONTS_DIR, subsList), // Assuming MakeFontsList is properly defined
|
||||
VideoAndAudio = hasAudioStreams ? new List<MergerInput>() : data.Where(a => a.Type == DownloadMediaType.Video).Select(a => new MergerInput{ Language = a.Lang, Path = a.Path ?? string.Empty }).ToList(),
|
||||
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(){
|
||||
@ -489,11 +492,11 @@ public class Crunchyroll{
|
||||
});
|
||||
|
||||
if (!File.Exists(CfgManager.PathFFMPEG)){
|
||||
Console.WriteLine("FFmpeg not found");
|
||||
Console.Error.WriteLine("FFmpeg not found");
|
||||
}
|
||||
|
||||
if (!File.Exists(CfgManager.PathMKVMERGE)){
|
||||
Console.WriteLine("MKVmerge not found");
|
||||
Console.Error.WriteLine("MKVmerge not found");
|
||||
}
|
||||
|
||||
bool isMuxed;
|
||||
@ -645,6 +648,20 @@ public class Crunchyroll{
|
||||
var fetchPlaybackData = await FetchPlaybackData(mediaId, mediaGuid, epMeta);
|
||||
|
||||
if (!fetchPlaybackData.IsOk){
|
||||
if (!fetchPlaybackData.IsOk && fetchPlaybackData.error != string.Empty){
|
||||
var s = fetchPlaybackData.error;
|
||||
var error = StreamError.FromJson(s);
|
||||
if (error != null && error.IsTooManyActiveStreamsError()){
|
||||
MainWindow.Instance.ShowError("Too many active streams that couldn't be stopped");
|
||||
return new DownloadResponse{
|
||||
Data = new List<DownloadedMedia>(),
|
||||
Error = true,
|
||||
FileName = "./unknown",
|
||||
ErrorText = "Too many active streams that couldn't be stopped"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
MainWindow.Instance.ShowError("Couldn't get Playback Data");
|
||||
return new DownloadResponse{
|
||||
Data = new List<DownloadedMedia>(),
|
||||
@ -1001,6 +1018,17 @@ public class Crunchyroll{
|
||||
|
||||
Console.WriteLine("Decryption Needed, attempting to decrypt");
|
||||
|
||||
if (!_widevine.canDecrypt){
|
||||
dlFailed = true;
|
||||
return new DownloadResponse{
|
||||
Data = files,
|
||||
Error = dlFailed,
|
||||
FileName = fileName.Length > 0 ? (Path.IsPathRooted(fileName) ? fileName : Path.Combine(CfgManager.PathVIDEOS_DIR, fileName)) : "./unknown",
|
||||
ErrorText = "Decryption Needed but couldn't find CDM files"
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
var reqBodyData = new{
|
||||
accounting_id = "crunchyroll",
|
||||
asset_id = assetId,
|
||||
@ -1169,9 +1197,6 @@ public class Crunchyroll{
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (!options.Novids){
|
||||
//TODO
|
||||
MainWindow.Instance.ShowError("Requested Video with the current settings not implemented");
|
||||
} else if (options.Novids){
|
||||
fileName = Path.Combine(FileNameManager.ParseFileName(options.FileName, variables, options.Numbers, options.Override).ToArray());
|
||||
Console.WriteLine("Downloading skipped!");
|
||||
@ -1234,11 +1259,6 @@ public class Crunchyroll{
|
||||
options.SkipSubs = true;
|
||||
}
|
||||
|
||||
if (options.NoSubs){
|
||||
Console.WriteLine("Subtitles downloading disabled from nosubs flag.");
|
||||
options.SkipSubs = true;
|
||||
}
|
||||
|
||||
if (!options.SkipSubs && options.DlSubs.IndexOf("none") == -1){
|
||||
await DownloadSubtitles(options, pbData, audDub, fileName, files);
|
||||
} else{
|
||||
@ -1486,12 +1506,25 @@ public class Crunchyroll{
|
||||
|
||||
var playbackRequestNonDrmResponse = await HttpClientReq.Instance.SendHttpRequest(playbackRequestNonDrm);
|
||||
|
||||
if (!playbackRequestNonDrmResponse.IsOk && playbackRequestNonDrmResponse.ResponseContent != string.Empty){
|
||||
var s = playbackRequestNonDrmResponse.ResponseContent;
|
||||
var error = StreamError.FromJson(s);
|
||||
if (error != null && error.IsTooManyActiveStreamsError()){
|
||||
foreach (var errorActiveStream in error.ActiveStreams){
|
||||
await HttpClientReq.DeAuthVideo(errorActiveStream.ContentId, errorActiveStream.Token);
|
||||
}
|
||||
|
||||
playbackRequestNonDrm = HttpClientReq.CreateRequestMessage($"https://cr-play-service.prd.crunchyrollsvc.com/v1/{currentMediaId}/console/switch/play", HttpMethod.Get, true, true, null);
|
||||
playbackRequestNonDrm.Headers.UserAgent.ParseAdd("Crunchyroll/1.8.0 Nintendo Switch/12.3.12.0 UE4/4.27");
|
||||
playbackRequestNonDrmResponse = await HttpClientReq.Instance.SendHttpRequest(playbackRequestNonDrm);
|
||||
}
|
||||
}
|
||||
|
||||
if (playbackRequestNonDrmResponse.IsOk && playbackRequestNonDrmResponse.ResponseContent != string.Empty){
|
||||
CrunchyStreamData? playStream = JsonConvert.DeserializeObject<CrunchyStreamData>(playbackRequestNonDrmResponse.ResponseContent, SettingsJsonSerializerSettings);
|
||||
CrunchyStreams derivedPlayCrunchyStreams = new CrunchyStreams();
|
||||
if (playStream != null){
|
||||
var deauthVideoToken = HttpClientReq.CreateRequestMessage($"https://cr-play-service.prd.crunchyrollsvc.com/v1/token/{currentMediaId}/{playStream.Token}/inactive", HttpMethod.Patch, true, false, null);
|
||||
var deauthVideoTokenResponse = await HttpClientReq.Instance.SendHttpRequest(deauthVideoToken);
|
||||
if (playStream.Token != null) await HttpClientReq.DeAuthVideo(currentMediaId, playStream.Token);
|
||||
|
||||
if (playStream.HardSubs != null)
|
||||
foreach (var hardsub in playStream.HardSubs){
|
||||
@ -1514,7 +1547,7 @@ public class Crunchyroll{
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<(bool IsOk, PlaybackData pbData)> FetchPlaybackData(string mediaId, string mediaGuidId, CrunchyEpMetaData epMeta){
|
||||
private async Task<(bool IsOk, PlaybackData pbData, string error)> FetchPlaybackData(string mediaId, string mediaGuidId, CrunchyEpMetaData epMeta){
|
||||
PlaybackData temppbData = new PlaybackData{ Total = 0, Data = new List<Dictionary<string, Dictionary<string, StreamDetails>>>() };
|
||||
bool ok = true;
|
||||
|
||||
@ -1569,64 +1602,86 @@ public class Crunchyroll{
|
||||
//
|
||||
// var playbackRequestResponse22 = await HttpClientReq.Instance.SendHttpRequest(playbackRequest22);
|
||||
|
||||
playbackRequest = HttpClientReq.CreateRequestMessage($"https://cr-play-service.prd.crunchyrollsvc.com/v1/{mediaGuidId}/web/firefox/play", HttpMethod.Get, true, false, null);
|
||||
playbackRequest = HttpClientReq.CreateRequestMessage($"{Api.Cms}/videos/{mediaId}/streams", HttpMethod.Get, true, false, null);
|
||||
|
||||
playbackRequestResponse = await HttpClientReq.Instance.SendHttpRequest(playbackRequest);
|
||||
|
||||
if (playbackRequestResponse.IsOk){
|
||||
// var temppbData2 = Helpers.Deserialize<PlaybackData>(playbackRequestResponse22.ResponseContent, SettingsJsonSerializerSettings) ??
|
||||
// new PlaybackData{ Total = 0, Data = new List<Dictionary<string, Dictionary<string, StreamDetails>>>() };
|
||||
|
||||
temppbData = new PlaybackData{ Total = 0, Data = new List<Dictionary<string, Dictionary<string, StreamDetails>>>() };
|
||||
temppbData.Data.Add(new Dictionary<string, Dictionary<string, StreamDetails>>());
|
||||
|
||||
CrunchyStreamData? playStream = JsonConvert.DeserializeObject<CrunchyStreamData>(playbackRequestResponse.ResponseContent, SettingsJsonSerializerSettings);
|
||||
CrunchyStreams derivedPlayCrunchyStreams = new CrunchyStreams();
|
||||
if (playStream != null){
|
||||
var deauthVideoToken = HttpClientReq.CreateRequestMessage($"https://cr-play-service.prd.crunchyrollsvc.com/v1/token/{mediaGuidId}/{playStream.Token}/inactive", HttpMethod.Patch, true, false, null);
|
||||
var deauthVideoTokenResponse = await HttpClientReq.Instance.SendHttpRequest(deauthVideoToken);
|
||||
|
||||
if (playStream.HardSubs != null)
|
||||
foreach (var hardsub in playStream.HardSubs){
|
||||
var stream = hardsub.Value;
|
||||
derivedPlayCrunchyStreams[hardsub.Key] = new StreamDetails{
|
||||
Url = stream.Url,
|
||||
HardsubLocale = stream.Hlang
|
||||
};
|
||||
}
|
||||
|
||||
derivedPlayCrunchyStreams[""] = new StreamDetails{
|
||||
Url = playStream.Url,
|
||||
HardsubLocale = Locale.DefaulT
|
||||
};
|
||||
|
||||
if (temppbData.Data != null) temppbData.Data[0]["drm_adaptive_switch_dash"] = derivedPlayCrunchyStreams;
|
||||
|
||||
temppbData.Meta = new PlaybackMeta(){ AudioLocale = playStream.AudioLocale, Versions = playStream.Versions, Bifs = new List<string>{ playStream.Bifs }, MediaId = mediaId };
|
||||
temppbData.Meta.Subtitles = new Subtitles();
|
||||
foreach (var playStreamSubtitle in playStream.Subtitles){
|
||||
Subtitle sub = playStreamSubtitle.Value;
|
||||
temppbData.Meta.Subtitles.Add(playStreamSubtitle.Key, new SubtitleInfo(){ Format = sub.Format, Locale = sub.Locale, Url = sub.Url });
|
||||
if (!playbackRequestResponse.IsOk && playbackRequestResponse.ResponseContent != string.Empty){
|
||||
var s = playbackRequestResponse.ResponseContent;
|
||||
var error = StreamError.FromJson(s);
|
||||
if (error != null && error.IsTooManyActiveStreamsError()){
|
||||
foreach (var errorActiveStream in error.ActiveStreams){
|
||||
await HttpClientReq.DeAuthVideo(errorActiveStream.ContentId, errorActiveStream.Token);
|
||||
}
|
||||
|
||||
playbackRequest = HttpClientReq.CreateRequestMessage($"{Api.Cms}/videos/{mediaId}/streams", HttpMethod.Get, true, false, null);
|
||||
playbackRequestResponse = await HttpClientReq.Instance.SendHttpRequest(playbackRequest);
|
||||
}
|
||||
}
|
||||
|
||||
if (!playbackRequestResponse.IsOk){
|
||||
temppbData = Helpers.Deserialize<PlaybackData>(playbackRequestResponse.ResponseContent, SettingsJsonSerializerSettings) ??
|
||||
new PlaybackData{ Total = 0, Data = new List<Dictionary<string, Dictionary<string, StreamDetails>>>() };
|
||||
} else{
|
||||
Console.WriteLine("Request Stream URLs FAILED! Attempting fallback");
|
||||
|
||||
playbackRequest = HttpClientReq.CreateRequestMessage($"{Api.ApiBeta}{epMeta.Playback}", HttpMethod.Get, true, true, null);
|
||||
|
||||
playbackRequest = HttpClientReq.CreateRequestMessage($"https://cr-play-service.prd.crunchyrollsvc.com/v1/{mediaGuidId}/web/firefox/play", HttpMethod.Get, true, false, null);
|
||||
|
||||
playbackRequestResponse = await HttpClientReq.Instance.SendHttpRequest(playbackRequest);
|
||||
|
||||
if (!playbackRequestResponse.IsOk && playbackRequestResponse.ResponseContent != string.Empty){
|
||||
var s = playbackRequestResponse.ResponseContent;
|
||||
var error = StreamError.FromJson(s);
|
||||
if (error != null && error.IsTooManyActiveStreamsError()){
|
||||
foreach (var errorActiveStream in error.ActiveStreams){
|
||||
await HttpClientReq.DeAuthVideo(errorActiveStream.ContentId, errorActiveStream.Token);
|
||||
}
|
||||
|
||||
playbackRequest = HttpClientReq.CreateRequestMessage($"https://cr-play-service.prd.crunchyrollsvc.com/v1/{mediaGuidId}/web/firefox/play", HttpMethod.Get, true, false, null);
|
||||
playbackRequestResponse = await HttpClientReq.Instance.SendHttpRequest(playbackRequest);
|
||||
}
|
||||
}
|
||||
|
||||
if (playbackRequestResponse.IsOk){
|
||||
temppbData = Helpers.Deserialize<PlaybackData>(playbackRequestResponse.ResponseContent, SettingsJsonSerializerSettings) ??
|
||||
new PlaybackData{ Total = 0, Data = new List<Dictionary<string, Dictionary<string, StreamDetails>>>() };
|
||||
temppbData = new PlaybackData{ Total = 0, Data = new List<Dictionary<string, Dictionary<string, StreamDetails>>>() };
|
||||
temppbData.Data.Add(new Dictionary<string, Dictionary<string, StreamDetails>>());
|
||||
|
||||
CrunchyStreamData? playStream = JsonConvert.DeserializeObject<CrunchyStreamData>(playbackRequestResponse.ResponseContent, SettingsJsonSerializerSettings);
|
||||
CrunchyStreams derivedPlayCrunchyStreams = new CrunchyStreams();
|
||||
if (playStream != null){
|
||||
if (playStream.Token != null) await HttpClientReq.DeAuthVideo(mediaGuidId, playStream.Token);
|
||||
|
||||
if (playStream.HardSubs != null)
|
||||
foreach (var hardsub in playStream.HardSubs){
|
||||
var stream = hardsub.Value;
|
||||
derivedPlayCrunchyStreams[hardsub.Key] = new StreamDetails{
|
||||
Url = stream.Url,
|
||||
HardsubLocale = stream.Hlang
|
||||
};
|
||||
}
|
||||
|
||||
derivedPlayCrunchyStreams[""] = new StreamDetails{
|
||||
Url = playStream.Url,
|
||||
HardsubLocale = Locale.DefaulT
|
||||
};
|
||||
|
||||
if (temppbData.Data != null) temppbData.Data[0]["drm_adaptive_switch_dash"] = derivedPlayCrunchyStreams;
|
||||
|
||||
temppbData.Meta = new PlaybackMeta(){ AudioLocale = playStream.AudioLocale, Versions = playStream.Versions, Bifs = new List<string>{ playStream.Bifs }, MediaId = mediaId };
|
||||
temppbData.Meta.Subtitles = new Subtitles();
|
||||
foreach (var playStreamSubtitle in playStream.Subtitles){
|
||||
Subtitle sub = playStreamSubtitle.Value;
|
||||
temppbData.Meta.Subtitles.Add(playStreamSubtitle.Key, new SubtitleInfo(){ Format = sub.Format, Locale = sub.Locale, Url = sub.Url });
|
||||
}
|
||||
}
|
||||
} else{
|
||||
Console.WriteLine("'Fallback Request Stream URLs FAILED!'");
|
||||
Console.Error.WriteLine("'Fallback Request Stream URLs FAILED!'");
|
||||
ok = playbackRequestResponse.IsOk;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (IsOk: ok, pbData: temppbData);
|
||||
return (IsOk: ok, pbData: temppbData, error: ok ? "" : playbackRequestResponse.ResponseContent);
|
||||
}
|
||||
|
||||
private async Task ParseChapters(string currentMediaId, List<string> compiledChapters){
|
||||
|
@ -25,7 +25,7 @@ public class History(){
|
||||
CrSeriesSearch? parsedSeries = await crunInstance.CrSeries.ParseSeriesById(seriesId, "ja");
|
||||
|
||||
if (parsedSeries == null){
|
||||
Console.WriteLine("Parse Data Invalid");
|
||||
Console.Error.WriteLine("Parse Data Invalid");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -420,7 +420,7 @@ public class History(){
|
||||
historyEpisode.SonarrSeasonNumber = episode2.SeasonNumber + "";
|
||||
episodes.Remove(episode2);
|
||||
} else{
|
||||
Console.WriteLine($"Could not match episode {historyEpisode.EpisodeTitle} to sonarr episode");
|
||||
Console.Error.WriteLine($"Could not match episode {historyEpisode.EpisodeTitle} to sonarr episode");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -568,7 +568,7 @@ public class HistorySeries : INotifyPropertyChanged{
|
||||
}
|
||||
} catch (Exception ex){
|
||||
// Handle exceptions
|
||||
Console.WriteLine("Failed to load image: " + ex.Message);
|
||||
Console.Error.WriteLine("Failed to load image: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,14 +60,14 @@ public class Widevine{
|
||||
if (privateKey.Length != 0 && identifierBlob.Length != 0){
|
||||
canDecrypt = true;
|
||||
} else if (privateKey.Length == 0){
|
||||
Console.WriteLine("Private key missing");
|
||||
Console.Error.WriteLine("Private key missing");
|
||||
canDecrypt = false;
|
||||
} else if (identifierBlob.Length == 0){
|
||||
Console.WriteLine("Identifier blob missing");
|
||||
Console.Error.WriteLine("Identifier blob missing");
|
||||
canDecrypt = false;
|
||||
}
|
||||
} catch (Exception e){
|
||||
Console.WriteLine("Widevine: " + e);
|
||||
Console.Error.WriteLine("Widevine: " + e);
|
||||
canDecrypt = false;
|
||||
}
|
||||
}
|
||||
@ -90,7 +90,7 @@ public class Widevine{
|
||||
var response = await HttpClientReq.Instance.SendHttpRequest(playbackRequest2);
|
||||
|
||||
if (!response.IsOk){
|
||||
Console.WriteLine("Fallback Request Stream URLs FAILED!");
|
||||
Console.Error.WriteLine("Failed to get Keys!");
|
||||
return new List<ContentKey>();
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using CRD.Downloader;
|
||||
using CRD.Utils.Structs;
|
||||
using Newtonsoft.Json;
|
||||
using YamlDotNet.Core;
|
||||
using YamlDotNet.Core.Events;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
|
||||
@ -23,9 +28,9 @@ public class CfgManager{
|
||||
|
||||
public static readonly string PathVIDEOS_DIR = WorkingDirectory + "/video/";
|
||||
public static readonly string PathFONTS_DIR = WorkingDirectory + "/video/";
|
||||
|
||||
|
||||
public static readonly string PathLogFile = WorkingDirectory + "/logfile.txt";
|
||||
|
||||
|
||||
private static StreamWriter logFile;
|
||||
private static bool isLogModeEnabled = false;
|
||||
|
||||
@ -33,23 +38,23 @@ public class CfgManager{
|
||||
if (!isLogModeEnabled){
|
||||
logFile = new StreamWriter(PathLogFile);
|
||||
logFile.AutoFlush = true;
|
||||
Console.SetOut(logFile);
|
||||
Console.SetError(logFile);
|
||||
isLogModeEnabled = true;
|
||||
Console.WriteLine("Log mode enabled.");
|
||||
Console.Error.WriteLine("Log mode enabled.");
|
||||
}
|
||||
}
|
||||
|
||||
public static void DisableLogMode(){
|
||||
if (isLogModeEnabled){
|
||||
logFile.Close();
|
||||
StreamWriter standardOutput = new StreamWriter(Console.OpenStandardOutput());
|
||||
standardOutput.AutoFlush = true;
|
||||
Console.SetOut(standardOutput);
|
||||
StreamWriter standardError = new StreamWriter(Console.OpenStandardError());
|
||||
standardError.AutoFlush = true;
|
||||
Console.SetError(standardError);
|
||||
isLogModeEnabled = false;
|
||||
Console.WriteLine("Log mode disabled.");
|
||||
Console.Error.WriteLine("Log mode disabled.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void WriteJsonResponseToYamlFile(string jsonResponse, string filePath){
|
||||
// Convert JSON to an object
|
||||
var deserializer = new DeserializerBuilder()
|
||||
@ -122,6 +127,7 @@ public class CfgManager{
|
||||
File.WriteAllText(PathCrDownloadOptions, yaml);
|
||||
}
|
||||
|
||||
|
||||
public static void UpdateSettingsFromFile(){
|
||||
string dirPath = Path.GetDirectoryName(PathCrDownloadOptions) ?? string.Empty;
|
||||
|
||||
@ -144,35 +150,46 @@ public class CfgManager{
|
||||
|
||||
var deserializer = new DeserializerBuilder()
|
||||
.WithNamingConvention(UnderscoredNamingConvention.Instance)
|
||||
.IgnoreUnmatchedProperties() // Important to ignore properties not present in YAML
|
||||
.IgnoreUnmatchedProperties()
|
||||
.Build();
|
||||
|
||||
var propertiesPresentInYaml = GetTopLevelPropertiesInYaml(input);
|
||||
var loadedOptions = deserializer.Deserialize<CrDownloadOptions>(new StringReader(input));
|
||||
var instanceOptions = Crunchyroll.Instance.CrunOptions;
|
||||
|
||||
Crunchyroll.Instance.CrunOptions.Hslang = loadedOptions.Hslang;
|
||||
Crunchyroll.Instance.CrunOptions.Novids = loadedOptions.Novids;
|
||||
Crunchyroll.Instance.CrunOptions.Noaudio = loadedOptions.Noaudio;
|
||||
Crunchyroll.Instance.CrunOptions.FileName = loadedOptions.FileName;
|
||||
Crunchyroll.Instance.CrunOptions.Numbers = loadedOptions.Numbers;
|
||||
Crunchyroll.Instance.CrunOptions.DlSubs = loadedOptions.DlSubs;
|
||||
Crunchyroll.Instance.CrunOptions.Mp4 = loadedOptions.Mp4;
|
||||
Crunchyroll.Instance.CrunOptions.FfmpegOptions = loadedOptions.FfmpegOptions;
|
||||
Crunchyroll.Instance.CrunOptions.MkvmergeOptions = loadedOptions.MkvmergeOptions;
|
||||
Crunchyroll.Instance.CrunOptions.Chapters = loadedOptions.Chapters;
|
||||
Crunchyroll.Instance.CrunOptions.SimultaneousDownloads = loadedOptions.SimultaneousDownloads;
|
||||
Crunchyroll.Instance.CrunOptions.QualityAudio = loadedOptions.QualityAudio;
|
||||
Crunchyroll.Instance.CrunOptions.QualityVideo = loadedOptions.QualityVideo;
|
||||
Crunchyroll.Instance.CrunOptions.DubLang = loadedOptions.DubLang;
|
||||
Crunchyroll.Instance.CrunOptions.Theme = loadedOptions.Theme;
|
||||
Crunchyroll.Instance.CrunOptions.AccentColor = loadedOptions.AccentColor;
|
||||
Crunchyroll.Instance.CrunOptions.History = loadedOptions.History;
|
||||
Crunchyroll.Instance.CrunOptions.UseNonDrmStreams = loadedOptions.UseNonDrmStreams;
|
||||
Crunchyroll.Instance.CrunOptions.SonarrProperties = loadedOptions.SonarrProperties;
|
||||
Crunchyroll.Instance.CrunOptions.LogMode = loadedOptions.LogMode;
|
||||
foreach (PropertyInfo property in typeof(CrDownloadOptions).GetProperties()){
|
||||
var yamlMemberAttribute = property.GetCustomAttribute<YamlMemberAttribute>();
|
||||
string yamlPropertyName = yamlMemberAttribute?.Alias ?? property.Name;
|
||||
|
||||
if (propertiesPresentInYaml.Contains(yamlPropertyName)){
|
||||
PropertyInfo instanceProperty = instanceOptions.GetType().GetProperty(property.Name);
|
||||
if (instanceProperty != null && instanceProperty.CanWrite){
|
||||
instanceProperty.SetValue(instanceOptions, property.GetValue(loadedOptions));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static HashSet<string> GetTopLevelPropertiesInYaml(string yamlContent){
|
||||
var reader = new StringReader(yamlContent);
|
||||
var yamlStream = new YamlStream();
|
||||
yamlStream.Load(reader);
|
||||
|
||||
var properties = new HashSet<string>();
|
||||
|
||||
if (yamlStream.Documents.Count > 0 && yamlStream.Documents[0].RootNode is YamlMappingNode rootNode){
|
||||
foreach (var entry in rootNode.Children){
|
||||
if (entry.Key is YamlScalarNode scalarKey){
|
||||
properties.Add(scalarKey.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
private static object fileLock = new object();
|
||||
|
||||
|
||||
public static void WriteJsonToFile(string pathToFile, object obj){
|
||||
try{
|
||||
// Check if the directory exists; if not, create it.
|
||||
@ -191,7 +208,7 @@ public class CfgManager{
|
||||
}
|
||||
}
|
||||
} catch (Exception ex){
|
||||
Console.WriteLine($"An error occurred: {ex.Message}");
|
||||
Console.Error.WriteLine($"An error occurred: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ public class FileNameManager{
|
||||
var variable = overriddenVars.FirstOrDefault(v => v.Name == varName);
|
||||
|
||||
if (variable == null){
|
||||
Console.WriteLine($"[ERROR] Found variable '{match}' in fileName but no values was internally found!");
|
||||
Console.Error.WriteLine($"[ERROR] Found variable '{match}' in fileName but no values was internally found!");
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -51,13 +51,13 @@ public class FileNameManager{
|
||||
foreach (var item in overrides){
|
||||
int index = item.IndexOf('=');
|
||||
if (index == -1){
|
||||
Console.WriteLine($"Error: Invalid override format '{item}'");
|
||||
Console.Error.WriteLine($"Error: Invalid override format '{item}'");
|
||||
continue;
|
||||
}
|
||||
|
||||
string[] parts ={ item.Substring(0, index), item.Substring(index + 1) };
|
||||
if (!(parts[1].StartsWith("'") && parts[1].EndsWith("'") && parts[1].Length >= 2)){
|
||||
Console.WriteLine($"Error: Invalid value format for '{item}'");
|
||||
Console.Error.WriteLine($"Error: Invalid value format for '{item}'");
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -67,7 +67,7 @@ public class FileNameManager{
|
||||
if (alreadyIndex > -1){
|
||||
if (variables[alreadyIndex].Type == "number"){
|
||||
if (!float.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out float numberValue)){
|
||||
Console.WriteLine($"Error: Wrong type for '{item}'");
|
||||
Console.Error.WriteLine($"Error: Wrong type for '{item}'");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -81,8 +81,8 @@ public class HlsDownloader{
|
||||
$"Current: {{ total: {_data.M3U8Json?.Segments.Count} }}");
|
||||
}
|
||||
} catch (Exception e){
|
||||
Console.WriteLine("Resume failed, downloading will not be resumed!");
|
||||
Console.WriteLine(e.Message);
|
||||
Console.Error.WriteLine("Resume failed, downloading will not be resumed!");
|
||||
Console.Error.WriteLine(e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,7 +169,7 @@ public class HlsDownloader{
|
||||
try{
|
||||
await Task.WhenAll(keyTasks.Values);
|
||||
} catch (Exception ex){
|
||||
Console.WriteLine($"Error downloading keys: {ex.Message}");
|
||||
Console.Error.WriteLine($"Error downloading keys: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
@ -21,7 +22,7 @@ public class Helpers{
|
||||
try{
|
||||
return JsonConvert.DeserializeObject<T>(json, serializerSettings);
|
||||
} catch (JsonException ex){
|
||||
Console.WriteLine($"Error deserializing JSON: {ex.Message}");
|
||||
Console.Error.WriteLine($"Error deserializing JSON: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@ -41,7 +42,7 @@ public class Helpers{
|
||||
if (string.IsNullOrEmpty(value)){
|
||||
return Locale.DefaulT;
|
||||
}
|
||||
|
||||
|
||||
return Locale.Unknown; // Return default if not found
|
||||
}
|
||||
|
||||
@ -58,6 +59,35 @@ public class Helpers{
|
||||
return milliseconds + highResTimestamp;
|
||||
}
|
||||
|
||||
public static void ConvertChapterFileForFFMPEG(string chapterFilePath)
|
||||
{
|
||||
var chapterLines = File.ReadAllLines(chapterFilePath);
|
||||
var ffmpegChapterLines = new List<string> { ";FFMETADATA1" };
|
||||
|
||||
for (int i = 0; i < chapterLines.Length; i += 2)
|
||||
{
|
||||
var timeLine = chapterLines[i];
|
||||
var nameLine = chapterLines[i + 1];
|
||||
|
||||
var timeParts = timeLine.Split('=');
|
||||
var nameParts = nameLine.Split('=');
|
||||
|
||||
if (timeParts.Length == 2 && nameParts.Length == 2)
|
||||
{
|
||||
var startTime = TimeSpan.Parse(timeParts[1]).TotalMilliseconds;
|
||||
var endTime = i + 2 < chapterLines.Length ? TimeSpan.Parse(chapterLines[i + 2].Split('=')[1]).TotalMilliseconds : startTime + 10000;
|
||||
|
||||
ffmpegChapterLines.Add("[CHAPTER]");
|
||||
ffmpegChapterLines.Add("TIMEBASE=1/1000");
|
||||
ffmpegChapterLines.Add($"START={startTime}");
|
||||
ffmpegChapterLines.Add($"END={endTime}");
|
||||
ffmpegChapterLines.Add($"title={nameParts[1]}");
|
||||
}
|
||||
}
|
||||
|
||||
File.WriteAllLines(chapterFilePath, ffmpegChapterLines);
|
||||
}
|
||||
|
||||
public static async Task<(bool IsOk, int ErrorCode)> ExecuteCommandAsync(string type, string bin, string command){
|
||||
using (var process = new Process()){
|
||||
process.StartInfo.FileName = bin;
|
||||
@ -75,7 +105,7 @@ public class Helpers{
|
||||
|
||||
process.ErrorDataReceived += (sender, e) => {
|
||||
if (!string.IsNullOrEmpty(e.Data)){
|
||||
Console.WriteLine($"ERROR: {e.Data}");
|
||||
Console.WriteLine($"{e.Data}");
|
||||
}
|
||||
};
|
||||
|
||||
@ -137,7 +167,7 @@ public class Helpers{
|
||||
return words;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static double CosineSimilarity(Dictionary<string, double> vector1, Dictionary<string, double> vector2){
|
||||
var intersection = vector1.Keys.Intersect(vector2.Keys);
|
||||
|
||||
|
@ -97,7 +97,7 @@ public class HttpClientReq{
|
||||
|
||||
return (IsOk: true, ResponseContent: content);
|
||||
} catch (Exception e){
|
||||
Console.WriteLine($"Error: {e} \n Response: {content}");
|
||||
Console.Error.WriteLine($"Error: {e} \n Response: {(content.Length < 500 ? content : "error to long")}");
|
||||
return (IsOk: false, ResponseContent: content);
|
||||
}
|
||||
}
|
||||
@ -124,6 +124,11 @@ public class HttpClientReq{
|
||||
return request;
|
||||
}
|
||||
|
||||
public static async Task DeAuthVideo(string currentMediaId, string token){
|
||||
var deauthVideoToken = HttpClientReq.CreateRequestMessage($"https://cr-play-service.prd.crunchyrollsvc.com/v1/token/{currentMediaId}/{token}/inactive", HttpMethod.Patch, true, false, null);
|
||||
var deauthVideoTokenResponse = await HttpClientReq.Instance.SendHttpRequest(deauthVideoToken);
|
||||
}
|
||||
|
||||
public HttpClient GetHttpClient(){
|
||||
return client;
|
||||
}
|
||||
@ -132,7 +137,7 @@ public class HttpClientReq{
|
||||
|
||||
public static class Api{
|
||||
public static readonly string ApiBeta = "https://beta-api.crunchyroll.com";
|
||||
public static readonly string ApiN = "https://crunchyroll.com";
|
||||
public static readonly string ApiN = "https://www.crunchyroll.com";
|
||||
|
||||
public static readonly string BetaAuth = ApiBeta + "/auth/v1/token";
|
||||
public static readonly string BetaProfile = ApiBeta + "/accounts/v1/me/profile";
|
||||
@ -143,7 +148,7 @@ public static class Api{
|
||||
public static readonly string BetaCms = ApiBeta + "/cms/v2";
|
||||
public static readonly string DRM = ApiBeta + "/drm/v1/auth";
|
||||
|
||||
|
||||
public static readonly string Subscription = ApiBeta + "/subs/v3/subscriptions/";
|
||||
public static readonly string CmsN = ApiN + "/content/v2/cms";
|
||||
|
||||
|
||||
|
@ -14,7 +14,7 @@ public class Merger{
|
||||
public Merger(MergerOptions options){
|
||||
this.options = options;
|
||||
if (this.options.SkipSubMux != null && this.options.SkipSubMux == true){
|
||||
this.options.Subtitles = new List<SubtitleInput>();
|
||||
this.options.Subtitles = new();
|
||||
}
|
||||
|
||||
if (this.options.VideoTitle != null && this.options.VideoTitle.Length > 0){
|
||||
@ -32,31 +32,11 @@ public class Merger{
|
||||
var hasVideo = false;
|
||||
|
||||
if (!options.mp3){
|
||||
foreach (var vid in options.VideoAndAudio){
|
||||
if (vid.Delay != null && hasVideo){
|
||||
args.Add($"-itsoffset -{Math.Ceiling((double)vid.Delay * 1000)}ms");
|
||||
}
|
||||
|
||||
args.Add($"-i \"{vid.Path}\"");
|
||||
if (!hasVideo || options.KeepAllVideos == true){
|
||||
metaData.Add($"-map {index}:a -map {index}:v");
|
||||
metaData.Add($"-metadata:s:a:{audioIndex} language={vid.Language.Code}");
|
||||
metaData.Add($"-metadata:s:v:{index} title=\"{options.VideoTitle}\"");
|
||||
hasVideo = true;
|
||||
} else{
|
||||
metaData.Add($"-map {index}:a");
|
||||
metaData.Add($"-metadata:s:a:{audioIndex} language={vid.Language.Code}");
|
||||
}
|
||||
|
||||
audioIndex++;
|
||||
index++;
|
||||
}
|
||||
|
||||
foreach (var vid in options.OnlyVid){
|
||||
if (!hasVideo || options.KeepAllVideos == true){
|
||||
args.Add($"-i \"{vid.Path}\"");
|
||||
metaData.Add($"-map {index} -map -{index}:a");
|
||||
metaData.Add($"-metadata:s:v:{index} title=\"{options.VideoTitle}\"");
|
||||
metaData.Add($"-map {index}:v");
|
||||
metaData.Add($"-metadata:s:v:{index} title=\"{(options.VideoTitle ?? vid.Language.Name)}\"");
|
||||
hasVideo = true;
|
||||
index++;
|
||||
}
|
||||
@ -64,12 +44,21 @@ public class Merger{
|
||||
|
||||
foreach (var aud in options.OnlyAudio){
|
||||
args.Add($"-i \"{aud.Path}\"");
|
||||
metaData.Add($"-map {index}");
|
||||
metaData.Add($"-map {index}:a");
|
||||
metaData.Add($"-metadata:s:a:{audioIndex} language={aud.Language.Code}");
|
||||
index++;
|
||||
audioIndex++;
|
||||
}
|
||||
|
||||
if (options.Chapters != null && options.Chapters.Count > 0){
|
||||
|
||||
Helpers.ConvertChapterFileForFFMPEG(options.Chapters[0].Path);
|
||||
|
||||
args.Add($"-i \"{options.Chapters[0].Path}\"");
|
||||
metaData.Add($"-map_metadata {index}");
|
||||
index++;
|
||||
}
|
||||
|
||||
foreach (var sub in options.Subtitles.Select((value, i) => new{ value, i })){
|
||||
if (sub.value.Delay != null){
|
||||
args.Add($"-itsoffset -{Math.Ceiling((double)sub.value.Delay * 1000)}ms");
|
||||
@ -77,7 +66,7 @@ public class Merger{
|
||||
|
||||
args.Add($"-i \"{sub.value.File}\"");
|
||||
}
|
||||
|
||||
|
||||
if (options.Output.EndsWith(".mkv", StringComparison.OrdinalIgnoreCase)){
|
||||
if (options.Fonts != null){
|
||||
int fontIndex = 0;
|
||||
@ -87,7 +76,7 @@ public class Merger{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
args.AddRange(metaData);
|
||||
args.AddRange(options.Subtitles.Select((sub, subIndex) => $"-map {subIndex + index}"));
|
||||
args.Add("-c:v copy");
|
||||
@ -95,6 +84,9 @@ public class Merger{
|
||||
args.Add(options.Output.EndsWith(".mp4", StringComparison.OrdinalIgnoreCase) ? "-c:s mov_text" : "-c:s ass");
|
||||
args.AddRange(options.Subtitles.Select((sub, subindex) =>
|
||||
$"-metadata:s:s:{subindex} title=\"{sub.Language.Language ?? sub.Language.Name}{(sub.ClosedCaption == true ? $" {options.CcTag}" : "")}{(sub.Signs == true ? " Signs" : "")}\" -metadata:s:s:{subindex} language={sub.Language.Code}"));
|
||||
|
||||
|
||||
|
||||
if (options.Options.ffmpeg?.Count > 0){
|
||||
args.AddRange(options.Options.ffmpeg);
|
||||
}
|
||||
@ -112,13 +104,15 @@ public class Merger{
|
||||
index++;
|
||||
audioIndex++;
|
||||
}
|
||||
|
||||
|
||||
args.Add("-acodec libmp3lame");
|
||||
args.Add("-ab 192k");
|
||||
args.Add($"\"{options.Output}\"");
|
||||
return string.Join(" ", args);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public string MkvMerge(){
|
||||
List<string> args = new List<string>();
|
||||
|
||||
@ -135,7 +129,7 @@ public class Merger{
|
||||
args.Add("--video-tracks 0");
|
||||
args.Add("--no-audio");
|
||||
|
||||
string trackName = $"{(options.VideoTitle ?? vid.Language.Name)}{(options.Simul == true ? " [Simulcast]" : " [Uncut]")}";
|
||||
string trackName = $"{(options.VideoTitle ?? vid.Language.Name)}";
|
||||
args.Add($"--track-name 0:\"{trackName}\"");
|
||||
args.Add($"--language 0:{vid.Language.Code}");
|
||||
|
||||
@ -144,47 +138,6 @@ public class Merger{
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var vid in options.VideoAndAudio){
|
||||
string audioTrackNum = options.InverseTrackOrder == true ? "0" : "1";
|
||||
string videoTrackNum = options.InverseTrackOrder == true ? "1" : "0";
|
||||
|
||||
if (vid.Delay.HasValue){
|
||||
double delay = vid.Delay ?? 0;
|
||||
args.Add($"--sync {audioTrackNum}:-{Math.Ceiling(delay * 1000)}");
|
||||
}
|
||||
|
||||
if (!hasVideo || options.KeepAllVideos == true){
|
||||
args.Add($"--video-tracks {videoTrackNum}");
|
||||
args.Add($"--audio-tracks {audioTrackNum}");
|
||||
|
||||
string trackName = $"{(options.VideoTitle ?? vid.Language.Name)}{(options.Simul == true ? " [Simulcast]" : " [Uncut]")}";
|
||||
args.Add($"--track-name 0:\"{trackName}\""); // Assuming trackName applies to video if present
|
||||
args.Add($"--language {audioTrackNum}:{vid.Language.Code}");
|
||||
|
||||
if (options.Defaults.Audio.Code == vid.Language.Code){
|
||||
args.Add($"--default-track {audioTrackNum}");
|
||||
} else{
|
||||
args.Add($"--default-track {audioTrackNum}:0");
|
||||
}
|
||||
|
||||
hasVideo = true;
|
||||
} else{
|
||||
args.Add("--no-video");
|
||||
args.Add($"--audio-tracks {audioTrackNum}");
|
||||
|
||||
if (options.Defaults.Audio.Code == vid.Language.Code){
|
||||
args.Add($"--default-track {audioTrackNum}");
|
||||
} else{
|
||||
args.Add($"--default-track {audioTrackNum}:0");
|
||||
}
|
||||
|
||||
args.Add($"--track-name {audioTrackNum}:\"{vid.Language.Name}\"");
|
||||
args.Add($"--language {audioTrackNum}:{vid.Language.Code}");
|
||||
}
|
||||
|
||||
args.Add($"\"{vid.Path}\"");
|
||||
}
|
||||
|
||||
foreach (var aud in options.OnlyAudio){
|
||||
string trackName = aud.Language.Name;
|
||||
args.Add($"--track-name 0:\"{trackName}\"");
|
||||
@ -245,51 +198,6 @@ public class Merger{
|
||||
return string.Join(" ", args);
|
||||
}
|
||||
|
||||
// public async Task CreateDelays(){
|
||||
// // Don't bother scanning if there is only 1 vna stream
|
||||
// if (options.VideoAndAudio.Count > 1){
|
||||
// var bin = await YamlCfg.LoadBinCfg();
|
||||
// var vnas = this.options.VideoAndAudio;
|
||||
//
|
||||
// // Get and set durations on each videoAndAudio Stream
|
||||
// foreach (var vna in vnas){
|
||||
// var streamInfo = await FFProbe(vna.Path, bin.FFProbe);
|
||||
// var videoInfo = streamInfo.Streams.Where(stream => stream.CodecType == "video").FirstOrDefault();
|
||||
// vna.Duration = int.Parse(videoInfo.Duration);
|
||||
// }
|
||||
//
|
||||
// // Sort videoAndAudio streams by duration (shortest first)
|
||||
// vnas.Sort((a, b) => {
|
||||
// if (a.Duration == 0 || b.Duration == 0) return -1;
|
||||
// return a.Duration.CompareTo(b.Duration);
|
||||
// });
|
||||
//
|
||||
// // Set Delays
|
||||
// var shortestDuration = vnas[0].Duration;
|
||||
// foreach (var (vna, index) in vnas.Select((vna, index) => (vna, index))){
|
||||
// // Don't calculate the shortestDuration track
|
||||
// if (index == 0){
|
||||
// if (!vna.IsPrimary)
|
||||
// Console.WriteLine("Shortest video isn't primary, this might lead to problems with subtitles. Please report on github or discord if you experience issues.");
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// if (vna.Duration > 0 && shortestDuration > 0){
|
||||
// // Calculate the tracks delay
|
||||
// vna.Delay = Math.Ceiling((vna.Duration - shortestDuration) * 1000) / 1000;
|
||||
//
|
||||
// var subtitles = this.options.Subtitles.Where(sub => sub.Language.Code == vna.Lang.Code).ToList();
|
||||
// foreach (var (sub, subIndex) in subtitles.Select((sub, subIndex) => (sub, subIndex))){
|
||||
// if (vna.IsPrimary)
|
||||
// subtitles[subIndex].Delay = vna.Delay;
|
||||
// else if (sub.ClosedCaption)
|
||||
// subtitles[subIndex].Delay = vna.Delay;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
public async Task Merge(string type, string bin){
|
||||
string command = type switch{
|
||||
@ -299,7 +207,7 @@ public class Merger{
|
||||
};
|
||||
|
||||
if (string.IsNullOrEmpty(command)){
|
||||
Console.WriteLine("Unable to merge files.");
|
||||
Console.Error.WriteLine("Unable to merge files.");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -309,7 +217,7 @@ public class Merger{
|
||||
if (!result.IsOk && type == "mkvmerge" && result.ErrorCode == 1){
|
||||
Console.WriteLine($"[{type}] Mkvmerge finished with at least one warning");
|
||||
} else if (!result.IsOk){
|
||||
Console.WriteLine($"[{type}] Merging failed with exit code {result.ErrorCode}");
|
||||
Console.Error.WriteLine($"[{type}] Merging failed with exit code {result.ErrorCode}");
|
||||
} else{
|
||||
Console.WriteLine($"[{type} Done]");
|
||||
}
|
||||
@ -319,7 +227,7 @@ public class Merger{
|
||||
public void CleanUp(){
|
||||
// Combine all media file lists and iterate through them
|
||||
var allMediaFiles = options.OnlyAudio.Concat(options.OnlyVid)
|
||||
.Concat(options.VideoAndAudio).ToList();
|
||||
.ToList();
|
||||
allMediaFiles.ForEach(file => DeleteFile(file.Path));
|
||||
allMediaFiles.ForEach(file => DeleteFile(file.Path + ".resume"));
|
||||
|
||||
@ -336,7 +244,7 @@ public class Merger{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
} catch (Exception ex){
|
||||
Console.WriteLine($"Failed to delete file {filePath}. Error: {ex.Message}");
|
||||
Console.Error.WriteLine($"Failed to delete file {filePath}. Error: {ex.Message}");
|
||||
// Handle exceptions if you need to log them or throw
|
||||
}
|
||||
}
|
||||
@ -382,7 +290,6 @@ public class CrunchyMuxOptions{
|
||||
}
|
||||
|
||||
public class MergerOptions{
|
||||
public List<MergerInput> VideoAndAudio{ get; set; } = new List<MergerInput>();
|
||||
public List<MergerInput> OnlyVid{ get; set; } = new List<MergerInput>();
|
||||
public List<MergerInput> OnlyAudio{ get; set; } = new List<MergerInput>();
|
||||
public List<SubtitleInput> Subtitles{ get; set; } = new List<SubtitleInput>();
|
||||
@ -390,8 +297,6 @@ public class MergerOptions{
|
||||
public string CcTag{ get; set; }
|
||||
public string Output{ get; set; }
|
||||
public string VideoTitle{ get; set; }
|
||||
public bool? Simul{ get; set; }
|
||||
public bool? InverseTrackOrder{ get; set; }
|
||||
public bool? KeepAllVideos{ get; set; }
|
||||
public List<ParsedFont> Fonts{ get; set; } = new List<ParsedFont>();
|
||||
public bool? SkipSubMux{ get; set; }
|
||||
|
@ -92,7 +92,7 @@ public class SonarrClient{
|
||||
series = JsonConvert.DeserializeObject<List<SonarrSeries>>(json) ?? [];
|
||||
} catch (Exception e){
|
||||
MainWindow.Instance.ShowError("Sonarr GetSeries error \n" + e);
|
||||
Console.WriteLine("Sonarr GetSeries error \n" + e);
|
||||
Console.Error.WriteLine("Sonarr GetSeries error \n" + e);
|
||||
}
|
||||
|
||||
return series;
|
||||
@ -107,7 +107,7 @@ public class SonarrClient{
|
||||
episodes = JsonConvert.DeserializeObject<List<SonarrEpisode>>(json) ?? [];
|
||||
} catch (Exception e){
|
||||
MainWindow.Instance.ShowError("Sonarr GetEpisodes error \n" + e);
|
||||
Console.WriteLine("Sonarr GetEpisodes error \n" + e);
|
||||
Console.Error.WriteLine("Sonarr GetEpisodes error \n" + e);
|
||||
}
|
||||
|
||||
return episodes;
|
||||
@ -121,7 +121,7 @@ public class SonarrClient{
|
||||
episode = JsonConvert.DeserializeObject<SonarrEpisode>(json) ?? new SonarrEpisode();
|
||||
} catch (Exception e){
|
||||
MainWindow.Instance.ShowError("Sonarr GetEpisode error \n" + e);
|
||||
Console.WriteLine("Sonarr GetEpisode error \n" + e);
|
||||
Console.Error.WriteLine("Sonarr GetEpisode error \n" + e);
|
||||
}
|
||||
|
||||
return episode;
|
||||
|
@ -63,7 +63,7 @@ public partial class CalendarEpisode : INotifyPropertyChanged{
|
||||
}
|
||||
} catch (Exception ex){
|
||||
// Handle exceptions
|
||||
Console.WriteLine("Failed to load image: " + ex.Message);
|
||||
Console.Error.WriteLine("Failed to load image: " + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
@ -50,9 +50,9 @@ public class CrDownloadOptions{
|
||||
[YamlIgnore]
|
||||
public bool SkipSubs{ get; set; }
|
||||
|
||||
[YamlIgnore]
|
||||
public bool NoSubs{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "mux_skip_subs", ApplyNamingConventions = false)]
|
||||
public bool SkipSubsMux{ get; set; }
|
||||
|
||||
[YamlMember(Alias = "mux_mp4", ApplyNamingConventions = false)]
|
||||
public bool Mp4{ get; set; }
|
||||
|
||||
@ -71,16 +71,16 @@ public class CrDownloadOptions{
|
||||
[YamlMember(Alias = "mux_mkvmerge", ApplyNamingConventions = false)]
|
||||
public List<string> MkvmergeOptions{ get; set; }
|
||||
|
||||
[YamlIgnore]
|
||||
public LanguageItem DefaultSub{ get; set; }
|
||||
[YamlMember(Alias = "mux_default_sub", ApplyNamingConventions = false)]
|
||||
public string DefaultSub{ get; set; }
|
||||
|
||||
[YamlIgnore]
|
||||
public LanguageItem DefaultAudio{ get; set; }
|
||||
[YamlMember(Alias = "mux_default_dub", ApplyNamingConventions = false)]
|
||||
public string DefaultAudio{ get; set; }
|
||||
|
||||
[YamlIgnore]
|
||||
public string CcTag{ get; set; }
|
||||
|
||||
[YamlIgnore]
|
||||
[YamlMember(Alias = "dl_video_once", ApplyNamingConventions = false)]
|
||||
public bool DlVideoOnce{ get; set; }
|
||||
|
||||
[YamlIgnore]
|
||||
|
@ -1,4 +1,6 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CRD.Utils.Structs;
|
||||
|
||||
@ -12,4 +14,44 @@ public class CrProfile{
|
||||
|
||||
[JsonProperty("preferred_content_subtitle_language")]
|
||||
public string? PreferredContentSubtitleLanguage{ get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public Subscription? Subscription{ get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public bool HasPremium{ get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class Subscription{
|
||||
[JsonProperty("account_id")]
|
||||
public int AccountId{ get; set; }
|
||||
[JsonProperty("ctp_account_id")]
|
||||
public string? CtpAccountId{ get; set; }
|
||||
[JsonProperty("cycle_duration")]
|
||||
public string? CycleDuration{ get; set; }
|
||||
[JsonProperty("next_renewal_date")]
|
||||
public DateTime NextRenewalDate{ get; set; }
|
||||
[JsonProperty("currency_code")]
|
||||
public string? CurrencyCode{ get; set; }
|
||||
[JsonProperty("is_active")]
|
||||
public bool IsActive{ get; set; }
|
||||
[JsonProperty("tax_included")]
|
||||
public bool TaxIncluded{ get; set; }
|
||||
[JsonProperty("subscription_products")]
|
||||
public List<SubscriptionProduct>? SubscriptionProducts{ get; set; }
|
||||
}
|
||||
|
||||
public class SubscriptionProduct{
|
||||
[JsonProperty("currency_code")]
|
||||
public string? CurrencyCode{ get; set; }
|
||||
public string? Amount{ get; set; }
|
||||
[JsonProperty("is_cancelled")]
|
||||
public bool IsCancelled{ get; set; }
|
||||
[JsonProperty("effective_date")]
|
||||
public DateTime EffectiveDate{ get; set; }
|
||||
public string? Sku{ get; set; }
|
||||
public string? Tier{ get; set; }
|
||||
[JsonProperty("active_free_trial")]
|
||||
public bool ActiveFreeTrial{ get; set; }
|
||||
}
|
95
CRD/Utils/Structs/StreamLimits.cs
Normal file
95
CRD/Utils/Structs/StreamLimits.cs
Normal file
@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CRD.Utils.Structs;
|
||||
|
||||
public class StreamError{
|
||||
[JsonPropertyName("error")]
|
||||
public string Error{ get; set; }
|
||||
|
||||
[JsonPropertyName("activeStreams")]
|
||||
public List<ActiveStream> ActiveStreams{ get; set; }
|
||||
|
||||
public static StreamError? FromJson(string json){
|
||||
try{
|
||||
return JsonConvert.DeserializeObject<StreamError>(json);
|
||||
} catch (Exception e){
|
||||
Console.Error.WriteLine(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsTooManyActiveStreamsError(){
|
||||
return Error == "TOO_MANY_ACTIVE_STREAMS";
|
||||
}
|
||||
}
|
||||
|
||||
public class ActiveStream{
|
||||
[JsonPropertyName("deviceSubtype")]
|
||||
public string DeviceSubtype{ get; set; }
|
||||
|
||||
[JsonPropertyName("accountId")]
|
||||
public string AccountId{ get; set; }
|
||||
|
||||
[JsonPropertyName("deviceType")]
|
||||
public string DeviceType{ get; set; }
|
||||
|
||||
[JsonPropertyName("subscription")]
|
||||
public string Subscription{ get; set; }
|
||||
|
||||
[JsonPropertyName("maxKeepAliveSeconds")]
|
||||
public int MaxKeepAliveSeconds{ get; set; }
|
||||
|
||||
[JsonPropertyName("ttl")]
|
||||
public int Ttl{ get; set; }
|
||||
|
||||
[JsonPropertyName("episodeIdentity")]
|
||||
public string EpisodeIdentity{ get; set; }
|
||||
|
||||
[JsonPropertyName("tabId")]
|
||||
public string TabId{ get; set; }
|
||||
|
||||
[JsonPropertyName("country")]
|
||||
public string Country{ get; set; }
|
||||
|
||||
[JsonPropertyName("clientId")]
|
||||
public string ClientId{ get; set; }
|
||||
|
||||
[JsonPropertyName("active")]
|
||||
public bool Active{ get; set; }
|
||||
|
||||
[JsonPropertyName("deviceId")]
|
||||
public string DeviceId{ get; set; }
|
||||
|
||||
[JsonPropertyName("token")]
|
||||
public string Token{ get; set; }
|
||||
|
||||
[JsonPropertyName("assetId")]
|
||||
public string AssetId{ get; set; }
|
||||
|
||||
[JsonPropertyName("sessionType")]
|
||||
public string SessionType{ get; set; }
|
||||
|
||||
[JsonPropertyName("contentId")]
|
||||
public string ContentId{ get; set; }
|
||||
|
||||
[JsonPropertyName("usesStreamLimits")]
|
||||
public bool UsesStreamLimits{ get; set; }
|
||||
|
||||
[JsonPropertyName("playbackType")]
|
||||
public string PlaybackType{ get; set; }
|
||||
|
||||
[JsonPropertyName("pk")]
|
||||
public string Pk{ get; set; }
|
||||
|
||||
[JsonPropertyName("id")]
|
||||
public string Id{ get; set; }
|
||||
|
||||
[JsonPropertyName("createdTimestamp")]
|
||||
public long CreatedTimestamp{ get; set; }
|
||||
|
||||
[JsonPropertyName("lastKeepAliveTimestamp")]
|
||||
public long LastKeepAliveTimestamp{ get; set; }
|
||||
}
|
@ -73,7 +73,7 @@ public class Updater : INotifyPropertyChanged{
|
||||
}
|
||||
}
|
||||
} catch (Exception e){
|
||||
Console.WriteLine("Failed to get Update information");
|
||||
Console.Error.WriteLine("Failed to get Update information");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -119,11 +119,11 @@ public class Updater : INotifyPropertyChanged{
|
||||
|
||||
ApplyUpdate(extractPath);
|
||||
} else{
|
||||
Console.WriteLine("Failed to get Update");
|
||||
Console.Error.WriteLine("Failed to get Update");
|
||||
}
|
||||
}
|
||||
} catch (Exception e){
|
||||
Console.WriteLine($"Failed to get Update: {e.Message}");
|
||||
Console.Error.WriteLine($"Failed to get Update: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,31 +1,73 @@
|
||||
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.Utils.Structs;
|
||||
using CRD.Views.Utils;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
|
||||
namespace CRD.ViewModels;
|
||||
|
||||
public partial class AccountPageViewModel : ViewModelBase{
|
||||
[ObservableProperty] private Bitmap? _profileImage;
|
||||
[ObservableProperty]
|
||||
private Bitmap? _profileImage;
|
||||
|
||||
[ObservableProperty] private string _profileName = "";
|
||||
[ObservableProperty]
|
||||
private string _profileName = "";
|
||||
|
||||
[ObservableProperty] private string _loginLogoutText = "";
|
||||
[ObservableProperty]
|
||||
private string _loginLogoutText = "";
|
||||
|
||||
[ObservableProperty]
|
||||
private string _remainingTime = "";
|
||||
|
||||
private static DispatcherTimer? _timer;
|
||||
private DateTime _targetTime;
|
||||
private bool IsCancelled = false;
|
||||
|
||||
public AccountPageViewModel(){
|
||||
UpdatetProfile();
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void Timer_Tick(object sender, EventArgs e){
|
||||
var remaining = _targetTime - DateTime.UtcNow;
|
||||
if (remaining <= TimeSpan.Zero){
|
||||
RemainingTime = "No active Subscription";
|
||||
_timer.Stop();
|
||||
} else{
|
||||
RemainingTime = $"{(IsCancelled ? "Subscription ending in: ":"Subscription refreshing in: ")}{remaining:dd\\:hh\\:mm\\:ss}";
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdatetProfile(){
|
||||
ProfileName = Crunchyroll.Instance.Profile.Username; // Default or fetched user name
|
||||
LoginLogoutText = Crunchyroll.Instance.Profile.Username == "???" ? "Login" : "Logout"; // Default state
|
||||
LoadProfileImage("https://static.crunchyroll.com/assets/avatar/170x170/" + Crunchyroll.Instance.Profile.Avatar);
|
||||
|
||||
if (Crunchyroll.Instance.Profile.Subscription != null && Crunchyroll.Instance.Profile.Subscription?.SubscriptionProducts != null){
|
||||
var sub = Crunchyroll.Instance.Profile.Subscription?.SubscriptionProducts.First();
|
||||
_targetTime = Crunchyroll.Instance.Profile.Subscription.NextRenewalDate;
|
||||
if (sub != null){
|
||||
IsCancelled = sub.IsCancelled;
|
||||
}
|
||||
|
||||
_timer = new DispatcherTimer{
|
||||
Interval = TimeSpan.FromSeconds(1)
|
||||
};
|
||||
_timer.Tick += Timer_Tick;
|
||||
_timer.Start();
|
||||
} else{
|
||||
RemainingTime = "No active Subscription";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
@ -60,7 +102,7 @@ public partial class AccountPageViewModel : ViewModelBase{
|
||||
}
|
||||
} catch (Exception ex){
|
||||
// Handle exceptions
|
||||
Console.WriteLine("Failed to load image: " + ex.Message);
|
||||
Console.Error.WriteLine("Failed to load image: " + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
@ -156,7 +156,7 @@ public partial class AddDownloadPageViewModel : ViewModelBase{
|
||||
}
|
||||
}
|
||||
} else{
|
||||
Console.WriteLine("Unnkown input");
|
||||
Console.Error.WriteLine("Unnkown input");
|
||||
}
|
||||
}
|
||||
|
||||
@ -248,7 +248,7 @@ public class ItemModel(string imageUrl, string description, string time, string
|
||||
}
|
||||
} catch (Exception ex){
|
||||
// Handle exceptions
|
||||
Console.WriteLine("Failed to load image: " + ex.Message);
|
||||
Console.Error.WriteLine("Failed to load image: " + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
@ -42,7 +42,7 @@ public partial class DownloadsPageViewModel : ViewModelBase{
|
||||
if (downloadItem != null){
|
||||
Crunchyroll.Instance.DownloadItemModels.Remove(downloadItem);
|
||||
} else{
|
||||
Console.WriteLine("Failed to Remove Episode from list");
|
||||
Console.Error.WriteLine("Failed to Remove Episode from list");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -252,7 +252,7 @@ public partial class DownloadItemModel : INotifyPropertyChanged{
|
||||
}
|
||||
} catch (Exception ex){
|
||||
// Handle exceptions
|
||||
Console.WriteLine("Failed to load image: " + ex.Message);
|
||||
Console.Error.WriteLine("Failed to load image: " + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
@ -37,7 +37,7 @@ public partial class MainWindowViewModel : ViewModelBase{
|
||||
File.Delete(backupFilePath);
|
||||
Console.WriteLine($"Deleted old updater file: {backupFilePath}");
|
||||
} catch (Exception ex) {
|
||||
Console.WriteLine($"Failed to delete old updater file: {ex.Message}");
|
||||
Console.Error.WriteLine($"Failed to delete old updater file: {ex.Message}");
|
||||
}
|
||||
} else {
|
||||
Console.WriteLine("No old updater file found to delete.");
|
||||
|
@ -87,7 +87,7 @@ public partial class SeriesPageViewModel : ViewModelBase{
|
||||
UseShellExecute = true
|
||||
});
|
||||
} catch (Exception e){
|
||||
Console.WriteLine($"An error occurred while trying to open URL - {url} : {e.Message}");
|
||||
Console.Error.WriteLine($"An error occurred while trying to open URL - {url} : {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Net.Mime;
|
||||
using System.Reflection;
|
||||
@ -34,6 +35,12 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _muxToMp4;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _downloadVideoForEveryDub;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _skipSubMux;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _history;
|
||||
@ -51,10 +58,16 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
||||
private string _fileName = "";
|
||||
|
||||
[ObservableProperty]
|
||||
private string _mkvMergeOptions = "";
|
||||
private ObservableCollection<MuxingParam> _mkvMergeOptions = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private string _ffmpegOptions = "";
|
||||
private string _mkvMergeOption = "";
|
||||
|
||||
[ObservableProperty]
|
||||
private string _ffmpegOption = "";
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<MuxingParam> _ffmpegOptions = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private string _selectedSubs = "all";
|
||||
@ -68,6 +81,12 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<ListBoxItem> _selectedDubLang = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem _selectedDefaultDubLang;
|
||||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem _selectedDefaultSubLang;
|
||||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem? _selectedVideoQuality;
|
||||
|
||||
@ -103,7 +122,7 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _sonarrUseSonarrNumbering = false;
|
||||
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _logMode = false;
|
||||
|
||||
@ -190,6 +209,14 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
||||
public ObservableCollection<ComboBoxItem> 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" },
|
||||
@ -210,6 +237,8 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
||||
HardSubLangList.Add(new ComboBoxItem{ Content = languageItem.CrLocale });
|
||||
SubLangList.Add(new ListBoxItem{ Content = languageItem.CrLocale });
|
||||
DubLangList.Add(new ComboBoxItem{ Content = languageItem.CrLocale });
|
||||
DefaultDubLangList.Add(new ComboBoxItem{ Content = languageItem.CrLocale });
|
||||
DefaultSubLangList.Add(new ComboBoxItem{ Content = languageItem.CrLocale });
|
||||
}
|
||||
|
||||
CrDownloadOptions options = Crunchyroll.Instance.CrunOptions;
|
||||
@ -217,6 +246,12 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
||||
ComboBoxItem? hsLang = HardSubLangList.FirstOrDefault(a => a.Content != null && (string)a.Content == options.Hslang) ?? 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];
|
||||
|
||||
var softSubLang = SubLangList.Where(a => options.DlSubs.Contains(a.Content)).ToList();
|
||||
|
||||
SelectedSubLang.Clear();
|
||||
@ -246,8 +281,10 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
||||
UseNonDrmEndpoint = options.UseNonDrmStreams;
|
||||
DownloadVideo = !options.Novids;
|
||||
DownloadAudio = !options.Noaudio;
|
||||
DownloadVideoForEveryDub = !options.DlVideoOnce;
|
||||
DownloadChapters = options.Chapters;
|
||||
MuxToMp4 = options.Mp4;
|
||||
SkipSubMux = options.SkipSubsMux;
|
||||
LeadingNumbers = options.Numbers;
|
||||
FileName = options.FileName;
|
||||
SimultaneousDownloads = options.SimultaneousDownloads;
|
||||
@ -268,11 +305,26 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
||||
|
||||
History = options.History;
|
||||
|
||||
//TODO - Mux Options
|
||||
MkvMergeOptions.Clear();
|
||||
if (options.MkvmergeOptions != null){
|
||||
foreach (var mkvmergeParam in options.MkvmergeOptions){
|
||||
MkvMergeOptions.Add(new MuxingParam(){ ParamValue = mkvmergeParam });
|
||||
}
|
||||
}
|
||||
|
||||
FfmpegOptions.Clear();
|
||||
if (options.FfmpegOptions != null){
|
||||
foreach (var ffmpegParam in options.FfmpegOptions){
|
||||
FfmpegOptions.Add(new MuxingParam(){ ParamValue = ffmpegParam });
|
||||
}
|
||||
}
|
||||
|
||||
SelectedSubLang.CollectionChanged += Changes;
|
||||
SelectedDubLang.CollectionChanged += Changes;
|
||||
|
||||
MkvMergeOptions.CollectionChanged += Changes;
|
||||
FfmpegOptions.CollectionChanged += Changes;
|
||||
|
||||
settingsLoaded = true;
|
||||
}
|
||||
|
||||
@ -285,8 +337,10 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
||||
|
||||
Crunchyroll.Instance.CrunOptions.Novids = !DownloadVideo;
|
||||
Crunchyroll.Instance.CrunOptions.Noaudio = !DownloadAudio;
|
||||
Crunchyroll.Instance.CrunOptions.DlVideoOnce = !DownloadVideoForEveryDub;
|
||||
Crunchyroll.Instance.CrunOptions.Chapters = DownloadChapters;
|
||||
Crunchyroll.Instance.CrunOptions.Mp4 = MuxToMp4;
|
||||
Crunchyroll.Instance.CrunOptions.SkipSubsMux = SkipSubMux;
|
||||
Crunchyroll.Instance.CrunOptions.Numbers = LeadingNumbers;
|
||||
Crunchyroll.Instance.CrunOptions.FileName = FileName;
|
||||
|
||||
@ -302,6 +356,8 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
||||
|
||||
Crunchyroll.Instance.CrunOptions.Hslang = hslang != "none" ? Languages.FindLang(hslang).Locale : hslang;
|
||||
|
||||
Crunchyroll.Instance.CrunOptions.DefaultAudio = SelectedDefaultDubLang.Content + "";
|
||||
Crunchyroll.Instance.CrunOptions.DefaultSub = SelectedDefaultSubLang.Content + "";
|
||||
|
||||
List<string> dubLangs = new List<string>();
|
||||
foreach (var listBoxItem in SelectedDubLang){
|
||||
@ -340,9 +396,20 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
||||
Crunchyroll.Instance.CrunOptions.SonarrProperties = props;
|
||||
|
||||
Crunchyroll.Instance.CrunOptions.LogMode = LogMode;
|
||||
|
||||
|
||||
//TODO - Mux Options
|
||||
List<string> mkvmergeParams = new List<string>();
|
||||
foreach (var mkvmergeParam in MkvMergeOptions){
|
||||
mkvmergeParams.Add(mkvmergeParam.ParamValue);
|
||||
}
|
||||
|
||||
Crunchyroll.Instance.CrunOptions.MkvmergeOptions = mkvmergeParams;
|
||||
|
||||
List<string> ffmpegParams = new List<string>();
|
||||
foreach (var ffmpegParam in FfmpegOptions){
|
||||
ffmpegParams.Add(ffmpegParam.ParamValue);
|
||||
}
|
||||
|
||||
Crunchyroll.Instance.CrunOptions.FfmpegOptions = ffmpegParams;
|
||||
|
||||
CfgManager.WriteSettingsToFile();
|
||||
|
||||
@ -369,6 +436,32 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void AddMkvMergeParam(){
|
||||
MkvMergeOptions.Add(new MuxingParam(){ ParamValue = MkvMergeOption });
|
||||
MkvMergeOption = "";
|
||||
RaisePropertyChanged(nameof(MkvMergeOptions));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void RemoveMkvMergeParam(MuxingParam param){
|
||||
MkvMergeOptions.Remove(param);
|
||||
RaisePropertyChanged(nameof(MkvMergeOptions));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void AddFfmpegParam(){
|
||||
FfmpegOptions.Add(new MuxingParam(){ ParamValue = FfmpegOption });
|
||||
FfmpegOption = "";
|
||||
RaisePropertyChanged(nameof(FfmpegOptions));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void RemoveFfmpegParam(MuxingParam param){
|
||||
FfmpegOptions.Remove(param);
|
||||
RaisePropertyChanged(nameof(FfmpegOptions));
|
||||
}
|
||||
|
||||
partial void OnCurrentAppThemeChanged(ComboBoxItem? value){
|
||||
if (value?.Content?.ToString() == "System"){
|
||||
_faTheme.PreferSystemTheme = true;
|
||||
@ -419,7 +512,6 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
||||
UpdateSettings();
|
||||
}
|
||||
|
||||
|
||||
private void Changes(object? sender, NotifyCollectionChangedEventArgs e){
|
||||
UpdateSettings();
|
||||
}
|
||||
@ -500,4 +592,24 @@ public partial class SettingsPageViewModel : ViewModelBase{
|
||||
CfgManager.DisableLogMode();
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnSelectedDefaultDubLangChanged(ComboBoxItem value){
|
||||
UpdateSettings();
|
||||
}
|
||||
|
||||
partial void OnSelectedDefaultSubLangChanged(ComboBoxItem value){
|
||||
UpdateSettings();
|
||||
}
|
||||
|
||||
partial void OnSkipSubMuxChanged(bool value){
|
||||
UpdateSettings();
|
||||
}
|
||||
|
||||
partial void OnDownloadVideoForEveryDubChanged(bool value){
|
||||
UpdateSettings();
|
||||
}
|
||||
}
|
||||
|
||||
public class MuxingParam{
|
||||
public string ParamValue{ get; set; }
|
||||
}
|
@ -22,10 +22,13 @@
|
||||
</Image>
|
||||
|
||||
<!-- Profile Name -->
|
||||
<TextBlock Text="{Binding ProfileName}" TextAlignment="Center" FontSize="20" Margin="10" />
|
||||
<TextBlock Text="{Binding ProfileName}" HorizontalAlignment="Center" TextAlignment="Center" FontSize="20" Margin="10" />
|
||||
|
||||
<!-- Subscription End -->
|
||||
<TextBlock x:Name="SubscriptionTextBlock" FontSize="24" Text="{Binding RemainingTime, Mode=OneWay}" TextAlignment="Center" HorizontalAlignment="Center" />
|
||||
|
||||
<!-- Login/Logout Button -->
|
||||
<Button Content="{Binding LoginLogoutText}" Width="170" Margin="20" Command="{Binding Button_PressCommand}" />
|
||||
<Button Content="{Binding LoginLogoutText}" HorizontalAlignment="Center" Width="170" Margin="20" Command="{Binding Button_PressCommand}" />
|
||||
</StackPanel>
|
||||
|
||||
|
||||
|
@ -20,7 +20,7 @@
|
||||
|
||||
<controls:SettingsExpander Header="Dub language"
|
||||
IconSource="Speaker2"
|
||||
Description="Change the selected dub language">
|
||||
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" -->
|
||||
<!-- -->
|
||||
@ -137,6 +137,12 @@
|
||||
<CheckBox IsChecked="{Binding DownloadVideo}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Download Video for every dub">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding DownloadVideoForEveryDub}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Video Quality">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
@ -218,18 +224,102 @@
|
||||
<CheckBox IsChecked="{Binding MuxToMp4}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem Content="Keep Subtitles separate">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding SkipSubMux}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
<controls:SettingsExpanderItem 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 Content="Default Subtitle ">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<ComboBox HorizontalContentAlignment="Center" MinWidth="210" MaxDropDownHeight="400"
|
||||
ItemsSource="{Binding DefaultSubLangList}"
|
||||
SelectedItem="{Binding SelectedDefaultSubLang}">
|
||||
</ComboBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
|
||||
<controls:SettingsExpanderItem Content="Additional MKVMerge Options">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<TextBox IsEnabled="False" Name="TargetTextBox2" HorizontalAlignment="Left" MinWidth="250"
|
||||
Text="{Binding MkvMergeOptions}" />
|
||||
|
||||
<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="#4a4a4a" BorderThickness="1" CornerRadius="10" Margin="2">
|
||||
<StackPanel Orientation="Horizontal" Margin="5">
|
||||
<TextBlock Text="{Binding ParamValue}" 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 Content="Additional FFMpeg Options">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<TextBox IsEnabled="False" Name="TargetTextBox3" HorizontalAlignment="Left" MinWidth="250"
|
||||
Text="{Binding FfmpegOptions}" />
|
||||
<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="#4a4a4a" BorderThickness="1" CornerRadius="10" Margin="2">
|
||||
<StackPanel Orientation="Horizontal" Margin="5">
|
||||
<TextBlock Text="{Binding ParamValue}" 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>
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using CRD.Downloader;
|
||||
@ -18,4 +19,5 @@ public partial class SettingsPageView : UserControl{
|
||||
Crunchyroll.Instance.RefreshSonarr();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user