Add - Added **Download Only First Available Dub** option https://github.com/Crunchy-DL/Crunchy-Downloader/issues/207
Add - Added **Mark as Watched** on Download Finish https://github.com/Crunchy-DL/Crunchy-Downloader/issues/216
Chg - Changed **copy state of settings** when an episode is added to the queue https://github.com/Crunchy-DL/Crunchy-Downloader/issues/211
Chg - Changed **updater error handling** https://github.com/Crunchy-DL/Crunchy-Downloader/issues/194
Fix - Fixed **updater on Linux** https://github.com/Crunchy-DL/Crunchy-Downloader/issues/194
Fix - Fixed **subtitle ordering** https://github.com/Crunchy-DL/Crunchy-Downloader/issues/213
Fix - Fixed **muxing passed settings** to apply configurations correctly
This commit is contained in:
Elwador 2025-02-16 04:16:06 +01:00
parent 06864f51ae
commit 40b7b6d1de
18 changed files with 375 additions and 75 deletions

View file

@ -18,8 +18,8 @@ public class CrAuth{
private readonly string authorization = ApiUrls.authBasicMob;
private readonly string userAgent = ApiUrls.MobileUserAgent;
private const string DeviceType = "OnePlus CPH2449";
private const string DeviceName = "CPH2449";
private readonly string deviceType = "OnePlus CPH2449";
private readonly string deviceName = "CPH2449";
public async Task AuthAnonymous(){
string uuid = Guid.NewGuid().ToString();
@ -28,10 +28,13 @@ public class CrAuth{
{ "grant_type", "client_id" },
{ "scope", "offline_access" },
{ "device_id", uuid },
{ "device_name", DeviceName },
{ "device_type", DeviceType },
{ "device_type", deviceType },
};
if (!string.IsNullOrEmpty(deviceName)){
formData.Add("device_name", deviceName);
}
var requestContent = new FormUrlEncodedContent(formData);
var crunchyAuthHeaders = new Dictionary<string, string>{
@ -78,15 +81,18 @@ public class CrAuth{
string uuid = Guid.NewGuid().ToString();
var formData = new Dictionary<string, string>{
{ "username", data.Username }, // Replace with actual data
{ "password", data.Password }, // Replace with actual data
{ "username", data.Username },
{ "password", data.Password },
{ "grant_type", "password" },
{ "scope", "offline_access" },
{ "device_id", uuid },
{ "device_name", DeviceName },
{ "device_type", DeviceType },
{ "device_type", deviceType },
};
if (!string.IsNullOrEmpty(deviceName)){
formData.Add("device_name", deviceName);
}
var requestContent = new FormUrlEncodedContent(formData);
var crunchyAuthHeaders = new Dictionary<string, string>{
@ -192,10 +198,13 @@ public class CrAuth{
{ "scope", "offline_access" },
{ "device_id", uuid },
{ "grant_type", "refresh_token" },
{ "device_name", DeviceName },
{ "device_type", DeviceType },
{ "device_type", deviceType },
};
if (!string.IsNullOrEmpty(deviceName)){
formData.Add("device_name", deviceName);
}
var requestContent = new FormUrlEncodedContent(formData);
var crunchyAuthHeaders = new Dictionary<string, string>{
@ -250,10 +259,13 @@ public class CrAuth{
{ "grant_type", "refresh_token" },
{ "scope", "offline_access" },
{ "device_id", uuid },
{ "device_name", DeviceName },
{ "device_type", DeviceType },
{ "device_type", deviceType },
};
if (!string.IsNullOrEmpty(deviceName)){
formData.Add("device_name", deviceName);
}
var requestContent = new FormUrlEncodedContent(formData);
var crunchyAuthHeaders = new Dictionary<string, string>{

View file

@ -281,4 +281,18 @@ public class CrEpisode(){
return complete;
}
public async Task MarkAsWatched(string episodeId){
var request = HttpClientReq.CreateRequestMessage($"{ApiUrls.Content}/discover/{crunInstance.Token?.account_id}/mark_as_watched/{episodeId}", HttpMethod.Post, true,false,null);
var response = await HttpClientReq.Instance.SendHttpRequest(request);
if (!response.IsOk){
Console.Error.WriteLine($"Mark as watched for {episodeId} failed");
}
}
}

View file

@ -96,6 +96,7 @@ public class CrunchyrollManager{
private CrDownloadOptions InitDownloadOptions(){
var options = new CrDownloadOptions();
options.UseCrBetaApi = true;
options.AutoDownload = false;
options.RemoveFinishedDownload = false;
options.Chapters = true;
@ -146,6 +147,7 @@ public class CrunchyrollManager{
if (Path.Exists(CfgManager.PathCrDownloadOptionsOld)){
var optionsYaml = new CrDownloadOptionsYaml();
optionsYaml.UseCrBetaApi = true;
optionsYaml.AutoDownload = false;
optionsYaml.RemoveFinishedDownload = false;
optionsYaml.Chapters = true;
@ -349,6 +351,8 @@ public class CrunchyrollManager{
foreach (var keyValue in groupByDub){
var result = await MuxStreams(keyValue.Value,
new CrunchyMuxOptions{
DubLangList = options.DubLang,
SubLangList = options.DlSubs,
FfmpegOptions = options.FfmpegOptions,
SkipSubMux = options.SkipSubsMux,
Output = fileNameAndPath,
@ -364,7 +368,11 @@ public class CrunchyrollManager{
CcTag = options.CcTag,
KeepAllVideos = true,
MuxDescription = options.IncludeVideoDescription,
DlVideoOnce = options.DlVideoOnce
DlVideoOnce = options.DlVideoOnce,
DefaultSubSigns = options.DefaultSubSigns,
DefaultSubForcedDisplay = options.DefaultSubForcedDisplay,
CcSubsMuxingFlag = options.CcSubsMuxingFlag,
SignsSubsAsForced = options.SignsSubsAsForced,
},
fileNameAndPath);
@ -403,6 +411,8 @@ public class CrunchyrollManager{
} else{
var result = await MuxStreams(res.Data,
new CrunchyMuxOptions{
DubLangList = options.DubLang,
SubLangList = options.DlSubs,
FfmpegOptions = options.FfmpegOptions,
SkipSubMux = options.SkipSubsMux,
Output = fileNameAndPath,
@ -418,7 +428,11 @@ public class CrunchyrollManager{
CcTag = options.CcTag,
KeepAllVideos = true,
MuxDescription = options.IncludeVideoDescription,
DlVideoOnce = options.DlVideoOnce
DlVideoOnce = options.DlVideoOnce,
DefaultSubSigns = options.DefaultSubSigns,
DefaultSubForcedDisplay = options.DefaultSubForcedDisplay,
CcSubsMuxingFlag = options.CcSubsMuxingFlag,
SignsSubsAsForced = options.SignsSubsAsForced,
},
fileNameAndPath);
@ -497,6 +511,9 @@ public class CrunchyrollManager{
History.SetAsDownloaded(data.SeriesId, data.SeasonId, data.Data.First().MediaId);
}
if (options.MarkAsWatched && data.Data is{ Count: > 0 }){
_ = CrEpisode.MarkAsWatched(data.Data.First().MediaId);
}
return true;
}
@ -623,6 +640,8 @@ public class CrunchyrollManager{
var merger = new Merger(new MergerOptions{
DubLangList = options.DubLangList,
SubLangList = options.SubLangList,
OnlyVid = data.Where(a => a.Type == DownloadMediaType.Video).Select(a => new MergerInput{ Language = a.Lang, Path = a.Path ?? string.Empty }).ToList(),
SkipSubMux = options.SkipSubMux,
OnlyAudio = data.Where(a => a.Type == DownloadMediaType.Audio).Select(a => new MergerInput{ Language = a.Lang, Path = a.Path ?? string.Empty }).ToList(),
@ -643,6 +662,10 @@ public class CrunchyrollManager{
},
CcTag = options.CcTag,
mp3 = muxToMp3,
DefaultSubSigns = options.DefaultSubSigns,
DefaultSubForcedDisplay = options.DefaultSubForcedDisplay,
CcSubsMuxingFlag = options.CcSubsMuxingFlag,
SignsSubsAsForced = options.SignsSubsAsForced,
Description = muxDesc ? data.Where(a => a.Type == DownloadMediaType.Description).Select(a => new MergerInput{ Path = a.Path ?? string.Empty }).ToList() :[],
});
@ -805,7 +828,7 @@ public class CrunchyrollManager{
if (data.Data is{ Count: > 0 }){
options.Partsize = options.Partsize > 0 ? options.Partsize : 1;
var sortedMetaData = data.Data
.OrderBy(metaData => options.DubLang.IndexOf(metaData.Lang?.CrLocale ?? string.Empty) != -1 ? options.DubLang.IndexOf(metaData.Lang?.CrLocale ?? string.Empty) : int.MaxValue)
.ToList();
@ -1332,7 +1355,7 @@ public class CrunchyrollManager{
Data = files,
Error = dlFailed,
FileName = fileName.Length > 0 ? (Path.IsPathRooted(fileName) ? fileName : Path.Combine(fileDir, fileName)) : "./unknown",
ErrorText = ""
ErrorText = "Audio or Video download failed",
};
}

View file

@ -227,6 +227,15 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
[ObservableProperty]
private bool _searchFetchFeaturedMusic;
[ObservableProperty]
private bool _useCrBetaApi;
[ObservableProperty]
private bool _downloadFirstAvailableDub;
[ObservableProperty]
private bool _markAsWatched;
private bool settingsLoaded;
public CrunchyrollSettingsViewModel(){
@ -280,6 +289,9 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
AddScaledBorderAndShadow = options.SubsAddScaledBorder is ScaledBorderAndShadowSelection.ScaledBorderAndShadowNo or ScaledBorderAndShadowSelection.ScaledBorderAndShadowYes;
SelectedScaledBorderAndShadow = GetScaledBorderAndShadowFromOptions(options);
MarkAsWatched = options.MarkAsWatched;
DownloadFirstAvailableDub = options.DownloadFirstAvailableDub;
UseCrBetaApi = options.UseCrBetaApi;
CCSubsFont = options.CcSubsFont ?? "";
CCSubsMuxingFlag = options.CcSubsMuxingFlag;
SignsSubsAsForced = options.SignsSubsAsForced;
@ -344,6 +356,9 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
return;
}
CrunchyrollManager.Instance.CrunOptions.MarkAsWatched = MarkAsWatched;
CrunchyrollManager.Instance.CrunOptions.DownloadFirstAvailableDub = DownloadFirstAvailableDub;
CrunchyrollManager.Instance.CrunOptions.UseCrBetaApi = UseCrBetaApi;
CrunchyrollManager.Instance.CrunOptions.SignsSubsAsForced = SignsSubsAsForced;
CrunchyrollManager.Instance.CrunOptions.CcSubsMuxingFlag = CCSubsMuxingFlag;
CrunchyrollManager.Instance.CrunOptions.CcSubsFont = CCSubsFont;

View file

@ -9,7 +9,7 @@
x:DataType="vm:CrunchyrollSettingsViewModel"
x:Class="CRD.Downloader.Crunchyroll.Views.CrunchyrollSettingsView">
<Design.DataContext>
<vm:CrunchyrollSettingsViewModel />
</Design.DataContext>
@ -17,6 +17,7 @@
<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)">
@ -203,10 +204,10 @@
<CheckBox IsChecked="{Binding SearchFetchFeaturedMusic}"> </CheckBox>
</controls:SettingsExpanderItem.Footer>
</controls:SettingsExpanderItem>
</controls:SettingsExpander>
<controls:SettingsExpander Header="Download Settings"
IconSource="Download"
Description="Adjust download settings"
@ -265,6 +266,12 @@
</controls:SettingsExpanderItem.Footer>
</controls:SettingsExpanderItem>
<controls:SettingsExpanderItem Content="Download First Available Dub" Description="Downloads only the first available dub from the dubs list">
<controls:SettingsExpanderItem.Footer>
<CheckBox IsChecked="{Binding DownloadFirstAvailableDub}"> </CheckBox>
</controls:SettingsExpanderItem.Footer>
</controls:SettingsExpanderItem>
<controls:SettingsExpanderItem Content="Audio Quality">
<controls:SettingsExpanderItem.Footer>
<ComboBox HorizontalContentAlignment="Center" MinWidth="210" MaxDropDownHeight="400"
@ -279,6 +286,12 @@
<CheckBox IsChecked="{Binding DownloadChapters}"> </CheckBox>
</controls:SettingsExpanderItem.Footer>
</controls:SettingsExpanderItem>
<controls:SettingsExpanderItem Content="Mark as watched" Description="Mark the downloaded episodes as watched on Crunchyroll">
<controls:SettingsExpanderItem.Footer>
<CheckBox IsChecked="{Binding MarkAsWatched}"> </CheckBox>
</controls:SettingsExpanderItem.Footer>
</controls:SettingsExpanderItem>
<controls:SettingsExpander.Footer>
</controls:SettingsExpander.Footer>
@ -541,6 +554,27 @@
</controls:SettingsExpander.Footer>
</controls:SettingsExpander>
<controls:SettingsExpander Header="CR Api"
IconSource="World"
Description="Change connection settings for the api">
<controls:SettingsExpander.Footer>
</controls:SettingsExpander.Footer>
<controls:SettingsExpanderItem Content="CR Beta Api">
<controls:SettingsExpanderItem.Footer>
<CheckBox IsChecked="{Binding UseCrBetaApi}"> </CheckBox>
</controls:SettingsExpanderItem.Footer>
</controls:SettingsExpanderItem>
<!-- <controls:SettingsExpanderItem Content="CR Old Auth" Description="You have to logout and login if you change this"> -->
<!-- <controls:SettingsExpanderItem.Footer> -->
<!-- <CheckBox IsChecked="{Binding UseCrOldAuthHeader}"> </CheckBox> -->
<!-- </controls:SettingsExpanderItem.Footer> -->
<!-- </controls:SettingsExpanderItem> -->
</controls:SettingsExpander>
</StackPanel>
</ScrollViewer>

View file

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
@ -175,7 +176,10 @@ public partial class ProgramManager : ObservableObject{
private void CleanUpOldUpdater(){
string backupFilePath = Path.Combine(Directory.GetCurrentDirectory(), "Updater.exe.bak");
var executableExtension = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : string.Empty;
string backupFilePath = Path.Combine(Directory.GetCurrentDirectory(), $"Updater{executableExtension}.bak");
if (File.Exists(backupFilePath)){
try{

View file

@ -5,8 +5,10 @@ using System.Collections.Specialized;
using System.Linq;
using System.Threading.Tasks;
using CRD.Downloader.Crunchyroll;
using CRD.Utils;
using CRD.Utils.CustomList;
using CRD.Utils.Structs;
using CRD.Utils.Structs.Crunchyroll;
using CRD.Utils.Structs.History;
using CRD.ViewModels;
using CRD.Views;
@ -61,6 +63,8 @@ public class QueueManager{
Console.Error.WriteLine("Failed to Remove Episode from list");
}
}
} else if (e.Action == NotifyCollectionChangedAction.Reset && Queue.Count == 0){
DownloadItemModels.Clear();
}
UpdateDownloadListItems();
@ -151,14 +155,41 @@ public class QueueManager{
selected.OnlySubs = onlySubs;
if (CrunchyrollManager.Instance.CrunOptions.DownloadFirstAvailableDub && selected.Data.Count > 1){
var sortedMetaData = selected.Data
.OrderBy(metaData => {
var locale = metaData.Lang?.CrLocale ?? string.Empty;
var index = dubLang.IndexOf(locale);
return index != -1 ? index : int.MaxValue;
})
.ToList();
if (sortedMetaData.Count != 0){
var first = sortedMetaData.First();
selected.Data =[first];
selected.SelectedDubs =[first.Lang?.CrLocale ?? string.Empty];
}
}
var newOptions = Helpers.DeepCopy(CrunchyrollManager.Instance.CrunOptions);
if (selected.OnlySubs){
newOptions.Novids = true;
newOptions.Noaudio = true;
}
newOptions.DubLang = dubLang;
selected.DownloadSettings = newOptions;
Queue.Add(selected);
if (selected.Data.Count < dubLang.Count){
if (selected.Data.Count < dubLang.Count && !CrunchyrollManager.Instance.CrunOptions.DownloadFirstAvailableDub){
Console.WriteLine("Added Episode to Queue but couldn't find all selected dubs");
Console.Error.WriteLine("Added Episode to Queue but couldn't find all selected dubs - Available dubs/subs: ");
var languages = sList.EpisodeAndLanguages.Items.Select((a, index) =>
$"{(a.IsPremiumOnly ? "+ " : "")}{sList.EpisodeAndLanguages.Langs.ElementAtOrDefault(index).CrLocale ?? "Unknown"}").ToArray();
$"{(a.IsPremiumOnly ? "+ " : "")}{sList.EpisodeAndLanguages.Langs.ElementAtOrDefault(index)?.CrLocale ?? "Unknown"}").ToArray();
Console.Error.WriteLine(
$"{selected.SeasonTitle} - Season {selected.Season} - {selected.EpisodeTitle} dubs - [{string.Join(", ", languages)}] subs - [{string.Join(", ", selected.AvailableSubs ??[])}]");
@ -172,7 +203,7 @@ public class QueueManager{
Console.Error.WriteLine("Episode couldn't be added to Queue - Available dubs/subs: ");
var languages = sList.EpisodeAndLanguages.Items.Select((a, index) =>
$"{(a.IsPremiumOnly ? "+ " : "")}{sList.EpisodeAndLanguages.Langs.ElementAtOrDefault(index).CrLocale ?? "Unknown"}").ToArray();
$"{(a.IsPremiumOnly ? "+ " : "")}{sList.EpisodeAndLanguages.Langs.ElementAtOrDefault(index)?.CrLocale ?? "Unknown"}").ToArray();
Console.Error.WriteLine($"{selected.SeasonTitle} - Season {selected.Season} - {selected.EpisodeTitle} dubs - [{string.Join(", ", languages)}] subs - [{string.Join(", ", selected.AvailableSubs ??[])}]");
MessageBus.Current.SendMessage(new ToastMessage($"Couldn't add episode to the queue with current dub settings", ToastType.Error, 2));
@ -188,6 +219,18 @@ public class QueueManager{
if (movieMeta != null){
movieMeta.DownloadSubs = CrunchyrollManager.Instance.CrunOptions.DlSubs;
movieMeta.OnlySubs = onlySubs;
var newOptions = Helpers.DeepCopy(CrunchyrollManager.Instance.CrunOptions);
if (movieMeta.OnlySubs){
newOptions.Novids = true;
newOptions.Noaudio = true;
}
newOptions.DubLang = dubLang;
movieMeta.DownloadSettings = newOptions;
Queue.Add(movieMeta);
Console.WriteLine("Added Movie to Queue");
@ -199,6 +242,9 @@ public class QueueManager{
public void CrAddMusicMetaToQueue(CrunchyEpMeta epMeta){
var newOptions = Helpers.DeepCopy(CrunchyrollManager.Instance.CrunOptions);
epMeta.DownloadSettings = newOptions;
Queue.Add(epMeta);
MessageBus.Current.SendMessage(new ToastMessage($"Added episode to the queue", ToastType.Information, 1));
}
@ -210,15 +256,18 @@ public class QueueManager{
if (musicVideo != null){
var musicVideoMeta = CrunchyrollManager.Instance.CrMusic.EpisodeMeta(musicVideo);
(HistoryEpisode? historyEpisode, List<string> dublist, List<string> sublist, string downloadDirPath, string videoQuality) historyEpisode = (null, [], [], "", "");
if (CrunchyrollManager.Instance.CrunOptions.History){
historyEpisode = CrunchyrollManager.Instance.History.GetHistoryEpisodeWithDubListAndDownloadDir(musicVideoMeta.SeriesId, musicVideoMeta.SeasonId, musicVideoMeta.Data.First().MediaId);
}
musicVideoMeta.VideoQuality = !string.IsNullOrEmpty(historyEpisode.videoQuality) ? historyEpisode.videoQuality : CrunchyrollManager.Instance.CrunOptions.QualityVideo;
var newOptions = Helpers.DeepCopy(CrunchyrollManager.Instance.CrunOptions);
musicVideoMeta.DownloadSettings = newOptions;
Queue.Add(musicVideoMeta);
MessageBus.Current.SendMessage(new ToastMessage($"Added music video to the queue", ToastType.Information, 1));
}
@ -231,14 +280,18 @@ public class QueueManager{
if (concert != null){
var concertMeta = CrunchyrollManager.Instance.CrMusic.EpisodeMeta(concert);
(HistoryEpisode? historyEpisode, List<string> dublist, List<string> sublist, string downloadDirPath, string videoQuality) historyEpisode = (null, [], [], "", "");
if (CrunchyrollManager.Instance.CrunOptions.History){
historyEpisode = CrunchyrollManager.Instance.History.GetHistoryEpisodeWithDubListAndDownloadDir(concertMeta.SeriesId, concertMeta.SeasonId, concertMeta.Data.First().MediaId);
}
concertMeta.VideoQuality = !string.IsNullOrEmpty(historyEpisode.videoQuality) ? historyEpisode.videoQuality : CrunchyrollManager.Instance.CrunOptions.QualityVideo;
var newOptions = Helpers.DeepCopy(CrunchyrollManager.Instance.CrunOptions);
concertMeta.DownloadSettings = newOptions;
Queue.Add(concertMeta);
MessageBus.Current.SendMessage(new ToastMessage($"Added concert to the queue", ToastType.Information, 1));
}
@ -285,6 +338,34 @@ public class QueueManager{
crunchyEpMeta.DownloadSubs = subLangList.sublist.Count > 0 ? subLangList.sublist : CrunchyrollManager.Instance.CrunOptions.DlSubs;
if (CrunchyrollManager.Instance.CrunOptions.DownloadFirstAvailableDub && crunchyEpMeta.Data.Count > 1){
var sortedMetaData = crunchyEpMeta.Data
.OrderBy(metaData => {
var locale = metaData.Lang?.CrLocale ?? string.Empty;
var index = data.DubLang.IndexOf(locale);
return index != -1 ? index : int.MaxValue;
})
.ToList();
if (sortedMetaData.Count != 0){
var first = sortedMetaData.First();
crunchyEpMeta.Data =[first];
crunchyEpMeta.SelectedDubs =[first.Lang?.CrLocale ?? string.Empty];
}
}
var newOptions = Helpers.DeepCopy(CrunchyrollManager.Instance.CrunOptions);
if (crunchyEpMeta.OnlySubs){
newOptions.Novids = true;
newOptions.Noaudio = true;
}
newOptions.DubLang = data.DubLang;
crunchyEpMeta.DownloadSettings = newOptions;
Queue.Add(crunchyEpMeta);
} else{
failed = true;

View file

@ -21,6 +21,7 @@ using CRD.Utils.Structs;
using CRD.Utils.Structs.Crunchyroll;
using Microsoft.Win32;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace CRD.Utils;
@ -39,10 +40,19 @@ public class Helpers{
}
public static T DeepCopy<T>(T obj){
var json = JsonConvert.SerializeObject(obj);
var settings = new JsonSerializerSettings{
ContractResolver = new DefaultContractResolver{
IgnoreSerializableAttribute = true,
IgnoreSerializableInterface = true
},
ObjectCreationHandling = ObjectCreationHandling.Replace
};
var json = JsonConvert.SerializeObject(obj, settings);
return JsonConvert.DeserializeObject<T>(json);
}
public static string ConvertTimeFormat(string time){
var timeParts = time.Split(':', '.');
int hours = int.Parse(timeParts[0]);

View file

@ -249,21 +249,21 @@ public static class ApiUrls{
public static readonly string ApiBeta = "https://beta-api.crunchyroll.com";
public static readonly string ApiN = "https://www.crunchyroll.com";
public static readonly string Anilist = "https://graphql.anilist.co";
public static readonly string Auth = ApiN + "/auth/v1/token";
public static readonly string Profile = ApiN + "/accounts/v1/me/profile";
public static readonly string CmsToken = ApiN + "/index/v2";
public static readonly string Search = ApiN + "/content/v2/discover/search";
public static readonly string Browse = ApiN + "/content/v2/discover/browse";
public static readonly string Cms = ApiN + "/content/v2/cms";
public static readonly string Content = ApiN + "/content/v2";
public static string Auth => (CrunchyrollManager.Instance.CrunOptions.UseCrBetaApi ? ApiBeta : ApiN) + "/auth/v1/token";
public static string Profile => (CrunchyrollManager.Instance.CrunOptions.UseCrBetaApi ? ApiBeta : ApiN) + "/accounts/v1/me/profile";
public static string CmsToken => (CrunchyrollManager.Instance.CrunOptions.UseCrBetaApi ? ApiBeta : ApiN) + "/index/v2";
public static string Search => (CrunchyrollManager.Instance.CrunOptions.UseCrBetaApi ? ApiBeta : ApiN) + "/content/v2/discover/search";
public static string Browse => (CrunchyrollManager.Instance.CrunOptions.UseCrBetaApi ? ApiBeta : ApiN) + "/content/v2/discover/browse";
public static string Cms => (CrunchyrollManager.Instance.CrunOptions.UseCrBetaApi ? ApiBeta : ApiN) + "/content/v2/cms";
public static string Content => (CrunchyrollManager.Instance.CrunOptions.UseCrBetaApi ? ApiBeta : ApiN) + "/content/v2";
public static string Subscription => (CrunchyrollManager.Instance.CrunOptions.UseCrBetaApi ? ApiBeta : ApiN) + "/subs/v3/subscriptions/";
public static readonly string BetaBrowse = ApiBeta + "/content/v1/browse";
public static readonly string BetaCms = ApiBeta + "/cms/v2";
public static readonly string DRM = ApiBeta + "/drm/v1/auth";
public static readonly string Subscription = ApiN + "/subs/v3/subscriptions/";
public static readonly string authBasic = "Basic bm9haWhkZXZtXzZpeWcwYThsMHE6";
public static readonly string authBasicMob = "Basic ZG1yeWZlc2NkYm90dWJldW56NXo6NU45aThPV2cyVmtNcm1oekNfNUNXekRLOG55SXo0QU0=";

View file

@ -86,7 +86,7 @@ public class Merger{
args.Add($"-i \"{sub.value.File}\"");
metaData.Add($"-map {index}:s");
if (options.Defaults.Sub.Code == sub.value.Language.Code &&
(CrunchyrollManager.Instance.CrunOptions.DefaultSubSigns == sub.value.Signs || CrunchyrollManager.Instance.CrunOptions.DefaultSubSigns && !hasSignsSub)
(options.DefaultSubSigns == sub.value.Signs || options.DefaultSubSigns && !hasSignsSub)
&& sub.value.ClosedCaption == false){
metaData.Add($"-disposition:s:{sub.i} default");
} else{
@ -168,9 +168,9 @@ public class Merger{
args.Add($"\"{Helpers.AddUncPrefixIfNeeded(vid.Path)}\"");
}
}
var sortedAudio = options.OnlyAudio
.OrderBy(sub => CrunchyrollManager.Instance.CrunOptions.DubLang.IndexOf(sub.Language.CrLocale) != -1 ? CrunchyrollManager.Instance.CrunOptions.DubLang.IndexOf(sub.Language.CrLocale) : int.MaxValue)
.OrderBy(sub => options.DubLangList.IndexOf(sub.Language.CrLocale) != -1 ? options.DubLangList.IndexOf(sub.Language.CrLocale) : int.MaxValue)
.ToList();
foreach (var aud in sortedAudio){
@ -198,11 +198,12 @@ public class Merger{
bool hasSignsSub = options.Subtitles.Any(sub => sub.Signs && options.Defaults.Sub.Code == sub.Language.Code);
var sortedSubtitles = options.Subtitles
.OrderBy(sub => CrunchyrollManager.Instance.CrunOptions.DlSubs.IndexOf(sub.Language.CrLocale) != -1 ? CrunchyrollManager.Instance.CrunOptions.DlSubs.IndexOf(sub.Language.CrLocale) : int.MaxValue)
.ThenBy(sub => sub.Signs ? 0 : 1)
.ThenBy(sub => sub.ClosedCaption ? 0 : 1)
.OrderBy(sub => options.SubLangList.IndexOf(sub.Language.CrLocale) != -1
? options.SubLangList.IndexOf(sub.Language.CrLocale)
: int.MaxValue)
.ThenBy(sub => sub.ClosedCaption ? 2 : sub.Signs ? 1 : 0)
.ToList();
foreach (var subObj in sortedSubtitles){
bool isForced = false;
if (subObj.Delay.HasValue){
@ -210,17 +211,17 @@ public class Merger{
args.Add($"--sync 0:{delay}");
}
string trackNameExtra = subObj.ClosedCaption == true ? $" {options.CcTag}" : "";
trackNameExtra += subObj.Signs == true ? " Signs" : "";
string trackNameExtra = subObj.ClosedCaption ? $" {options.CcTag}" : "";
trackNameExtra += subObj.Signs ? " Signs" : "";
string trackName = $"0:\"{(subObj.Language.Language ?? subObj.Language.Name) + trackNameExtra}\"";
args.Add($"--track-name {trackName}");
args.Add($"--language 0:\"{subObj.Language.Code}\"");
if (options.Defaults.Sub.Code == subObj.Language.Code &&
(CrunchyrollManager.Instance.CrunOptions.DefaultSubSigns == subObj.Signs || CrunchyrollManager.Instance.CrunOptions.DefaultSubSigns && !hasSignsSub) && subObj.ClosedCaption == false){
(options.DefaultSubSigns == subObj.Signs || options.DefaultSubSigns && !hasSignsSub) && subObj.ClosedCaption == false){
args.Add("--default-track 0");
if (CrunchyrollManager.Instance.CrunOptions.DefaultSubForcedDisplay){
if (options.DefaultSubForcedDisplay){
args.Add("--forced-track 0:yes");
isForced = true;
}
@ -228,11 +229,11 @@ public class Merger{
args.Add("--default-track 0:0");
}
if (subObj.ClosedCaption == true && CrunchyrollManager.Instance.CrunOptions.CcSubsMuxingFlag){
if (subObj.ClosedCaption && options.CcSubsMuxingFlag){
args.Add("--hearing-impaired-flag 0:yes");
}
if (subObj.Signs && CrunchyrollManager.Instance.CrunOptions.SignsSubsAsForced && !isForced){
if (subObj.Signs && options.SignsSubsAsForced && !isForced){
args.Add("--forced-track 0:yes");
}
@ -302,7 +303,7 @@ public class Merger{
Console.Error.WriteLine("Failed to retrieve video durations");
return -100;
}
var extractFramesBaseEnd = await SyncingHelper.ExtractFrames(baseVideoPath, baseFramesDirEnd, baseVideoDurationTimeSpan.Value.TotalSeconds - 360, 360);
var extractFramesCompareEnd = await SyncingHelper.ExtractFrames(compareVideoPath, compareFramesDirEnd, compareVideoDurationTimeSpan.Value.TotalSeconds - 360, 360);
@ -333,21 +334,20 @@ public class Merger{
Time = GetTimeFromFileName(fp, extractFramesCompareEnd.frameRate)
}).ToList();
// Calculate offsets
var startOffset = SyncingHelper.CalculateOffset(baseFramesStart, compareFramesStart);
var endOffset = SyncingHelper.CalculateOffset(baseFramesEnd, compareFramesEnd,true);
var endOffset = SyncingHelper.CalculateOffset(baseFramesEnd, compareFramesEnd, true);
var lengthDiff = (baseVideoDurationTimeSpan.Value.TotalMicroseconds - compareVideoDurationTimeSpan.Value.TotalMicroseconds) / 1000000;
endOffset += lengthDiff;
Console.WriteLine($"Start offset: {startOffset} seconds");
Console.WriteLine($"End offset: {endOffset} seconds");
CleanupDirectory(cleanupDir);
baseFramesStart.Clear();
baseFramesEnd.Clear();
compareFramesStart.Clear();
@ -378,7 +378,7 @@ public class Merger{
private static double GetTimeFromFileName(string fileName, double frameRate){
var match = Regex.Match(Path.GetFileName(fileName), @"frame(\d+)");
if (match.Success){
return int.Parse(match.Groups[1].Value) / frameRate;
return int.Parse(match.Groups[1].Value) / frameRate;
}
return 0;
@ -452,6 +452,8 @@ public class ParsedFont{
}
public class CrunchyMuxOptions{
public List<string> DubLangList{ get; set; } = new List<string>();
public List<string> SubLangList{ get; set; } = new List<string>();
public string Output{ get; set; }
public bool? SkipSubMux{ get; set; }
public bool? KeepAllVideos{ get; set; }
@ -468,9 +470,17 @@ public class CrunchyMuxOptions{
public string CcTag{ get; set; }
public bool SyncTiming{ get; set; }
public bool DlVideoOnce{ get; set; }
public bool DefaultSubSigns{ get; set; }
public bool DefaultSubForcedDisplay{ get; set; }
public bool CcSubsMuxingFlag{ get; set; }
public bool SignsSubsAsForced{ get; set; }
}
public class MergerOptions{
public List<string> DubLangList{ get; set; } = new List<string>();
public List<string> SubLangList{ get; set; } = new List<string>();
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>();
@ -484,6 +494,10 @@ public class MergerOptions{
public MuxOptions Options{ get; set; }
public Defaults Defaults{ get; set; }
public bool mp3{ get; set; }
public bool DefaultSubSigns{ get; set; }
public bool DefaultSubForcedDisplay{ get; set; }
public bool CcSubsMuxingFlag{ get; set; }
public bool SignsSubsAsForced{ get; set; }
public List<MergerInput> Description{ get; set; } = new List<MergerInput>();
}

View file

@ -46,7 +46,7 @@ public class CrDownloadOptions{
public List<string> Override{ get; set; } =[];
[JsonIgnore]
public string CcTag{ get; set; } = "";
public string CcTag{ get; set; } = "CC";
[JsonIgnore]
public bool Nocleanup{ get; set; }
@ -115,6 +115,12 @@ public class CrDownloadOptions{
#region Crunchyroll Settings
[JsonProperty("cr_mark_as_watched")]
public bool MarkAsWatched{ get; set; }
[JsonProperty("cr_beta_api")]
public bool UseCrBetaApi{ get; set; }
[JsonProperty("hard_sub_lang")]
public string Hslang{ get; set; } = "";
@ -208,6 +214,9 @@ public class CrDownloadOptions{
[JsonProperty("keep_dubs_seperate")]
public bool KeepDubsSeperate{ get; set; }
[JsonProperty("dl_first_available_dub")]
public bool DownloadFirstAvailableDub{ get; set; }
[JsonProperty("mux_skip_muxing")]
public bool SkipMuxing{ get; set; }
@ -364,6 +373,9 @@ public class CrDownloadOptionsYaml{
#region Crunchyroll Settings
[YamlIgnore]
public bool UseCrBetaApi{ get; set; }
[YamlMember(Alias = "hard_sub_lang", ApplyNamingConventions = false)]
public string Hslang{ get; set; } = "";

View file

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using CRD.Utils.Structs.Crunchyroll;
using CRD.Utils.Structs.History;
using Newtonsoft.Json;
@ -378,6 +379,9 @@ public class CrunchyEpMeta{
public List<string> downloadedFiles{ get; set; } =[];
public bool OnlySubs{ get; set; }
public CrDownloadOptions? DownloadSettings;
}
public class DownloadProgress{

View file

@ -15,6 +15,7 @@ namespace CRD.Utils.Updater;
public class Updater : INotifyPropertyChanged{
public double progress = 0;
public bool failed = false;
#region Singelton
@ -50,8 +51,8 @@ public class Updater : INotifyPropertyChanged{
private readonly string apiEndpoint = "https://api.github.com/repos/Crunchy-DL/Crunchy-Downloader/releases/latest";
public async Task<bool> CheckForUpdatesAsync(){
if (Directory.Exists(tempPath)){
Directory.Delete(tempPath, true);
if (File.Exists(tempPath)){
File.Delete(tempPath);
}
if (Directory.Exists(extractPath)){
@ -123,6 +124,7 @@ public class Updater : INotifyPropertyChanged{
public async Task DownloadAndUpdateAsync(){
try{
failed = false;
Helpers.EnsureDirectoriesExist(tempPath);
// Download the zip file
@ -164,19 +166,54 @@ public class Updater : INotifyPropertyChanged{
ApplyUpdate(extractPath);
} else{
Console.Error.WriteLine("Failed to get Update");
failed = true;
OnPropertyChanged(nameof(failed));
}
} catch (Exception e){
Console.Error.WriteLine($"Failed to get Update: {e.Message}");
failed = true;
OnPropertyChanged(nameof(failed));
}
}
private void ApplyUpdate(string updateFolder){
var ExecutableExtension = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : string.Empty;
var currentPath = AppDomain.CurrentDomain.BaseDirectory;
var updaterPath = Path.Combine(currentPath, "Updater" + ExecutableExtension);
var arguments = $"\"{currentPath.Substring(0, currentPath.Length - 1)}\" \"{updateFolder}\"";
var executableExtension = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : string.Empty;
var currentPath = Path.GetFullPath(AppContext.BaseDirectory);
var updaterPath = Path.Combine(currentPath, "Updater" + executableExtension);
System.Diagnostics.Process.Start(updaterPath, arguments);
Environment.Exit(0);
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)){
try{
var chmodProcess = new System.Diagnostics.ProcessStartInfo{
FileName = "/bin/bash",
Arguments = $"-c \"chmod +x '{updaterPath}'\"",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
System.Diagnostics.Process.Start(chmodProcess)?.WaitForExit();
} catch (Exception ex){
Console.Error.WriteLine($"Error setting execute permissions: {ex.Message}");
failed = true;
OnPropertyChanged(nameof(failed));
return;
}
}
try{
var startInfo = new System.Diagnostics.ProcessStartInfo{
FileName = updaterPath,
UseShellExecute = false
};
startInfo.ArgumentList.Add(currentPath);
startInfo.ArgumentList.Add(updateFolder);
System.Diagnostics.Process.Start(startInfo);
Environment.Exit(0);
} catch (Exception ex){
Console.Error.WriteLine($"Error launching updater: {ex.Message}");
failed = true;
OnPropertyChanged(nameof(failed));
}
}
}

View file

@ -45,6 +45,26 @@ public partial class DownloadsPageViewModel : ViewModelBase{
CrunchyrollManager.Instance.CrunOptions.RemoveFinishedDownload = value;
CfgManager.WriteCrSettings();
}
[RelayCommand]
public void ClearQueue(){
var items = QueueManager.Instance.Queue;
QueueManager.Instance.Queue.Clear();
foreach (var crunchyEpMeta in items){
if (!crunchyEpMeta.DownloadProgress.Done){
foreach (var downloadItemDownloadedFile in crunchyEpMeta.downloadedFiles){
try{
if (File.Exists(downloadItemDownloadedFile)){
File.Delete(downloadItemDownloadedFile);
}
} catch (Exception){
// ignored
}
}
}
}
}
}
public partial class DownloadItemModel : INotifyPropertyChanged{
@ -210,7 +230,7 @@ public partial class DownloadItemModel : INotifyPropertyChanged{
newOptions.Noaudio = true;
}
await CrunchyrollManager.Instance.DownloadEpisode(epMeta, newOptions);
await CrunchyrollManager.Instance.DownloadEpisode(epMeta, epMeta.DownloadSettings ?? newOptions);
}
}

View file

@ -12,6 +12,8 @@ public partial class ContentDialogUpdateViewModel : ViewModelBase{
[ObservableProperty]
private double _progress;
[ObservableProperty]
private bool _failed;
private AccountPageViewModel accountPageViewModel;
@ -28,7 +30,11 @@ public partial class ContentDialogUpdateViewModel : ViewModelBase{
private void Progress_PropertyChanged(object? sender, PropertyChangedEventArgs e){
if (e.PropertyName == nameof(Updater.Instance.progress)){
Progress = Updater.Instance.progress;
}else if (e.PropertyName == nameof(Updater.Instance.failed)){
Failed = Updater.Instance.failed;
dialog.IsPrimaryButtonEnabled = !Failed;
}
}

View file

@ -20,9 +20,20 @@
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Right">
<StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center">
<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>
<Button BorderThickness="0"
HorizontalAlignment="Right"
Margin="0 0 10 0 "
VerticalAlignment="Center"
Command="{Binding ClearQueue}">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<controls:SymbolIcon Symbol="Delete" FontSize="22" />
<TextBlock Text="Clear Queue" HorizontalAlignment="Center" VerticalAlignment="Center" TextWrapping="Wrap" ></TextBlock>
</StackPanel>
</Button>
</StackPanel>

View file

@ -178,9 +178,11 @@ public partial class MainWindow : AppWindow{
public async void ShowUpdateDialog(){
var dialog = new ContentDialog(){
Title = "Updating",
// CloseButtonText = "Close"
PrimaryButtonText = "Close"
};
dialog.IsPrimaryButtonEnabled = false;
var viewModel = new ContentDialogUpdateViewModel(dialog);
dialog.Content = new ContentDialogUpdateView(){
DataContext = viewModel

View file

@ -9,7 +9,8 @@
x:Class="CRD.Views.Utils.ContentDialogUpdateView">
<StackPanel Spacing="10" MinWidth="400">
<TextBlock Text="Please wait while the update is being downloaded..." HorizontalAlignment="Center" Margin="0,10,0,20"/>
<TextBlock IsVisible="{Binding !Failed}" Text="Please wait while the update is being downloaded..." HorizontalAlignment="Center" Margin="0,10,0,20"/>
<TextBlock IsVisible="{Binding Failed}" Foreground="IndianRed" Text="Update failed check the log for more information" HorizontalAlignment="Center" Margin="0,10,0,20"/>
<ProgressBar Minimum="0" Maximum="100" Value="{Binding Progress}" HorizontalAlignment="Center" VerticalAlignment="Center" Width="350"/>
</StackPanel>
</UserControl>