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

View file

@ -281,4 +281,18 @@ public class CrEpisode(){
return complete; 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(){ private CrDownloadOptions InitDownloadOptions(){
var options = new CrDownloadOptions(); var options = new CrDownloadOptions();
options.UseCrBetaApi = true;
options.AutoDownload = false; options.AutoDownload = false;
options.RemoveFinishedDownload = false; options.RemoveFinishedDownload = false;
options.Chapters = true; options.Chapters = true;
@ -146,6 +147,7 @@ public class CrunchyrollManager{
if (Path.Exists(CfgManager.PathCrDownloadOptionsOld)){ if (Path.Exists(CfgManager.PathCrDownloadOptionsOld)){
var optionsYaml = new CrDownloadOptionsYaml(); var optionsYaml = new CrDownloadOptionsYaml();
optionsYaml.UseCrBetaApi = true;
optionsYaml.AutoDownload = false; optionsYaml.AutoDownload = false;
optionsYaml.RemoveFinishedDownload = false; optionsYaml.RemoveFinishedDownload = false;
optionsYaml.Chapters = true; optionsYaml.Chapters = true;
@ -349,6 +351,8 @@ public class CrunchyrollManager{
foreach (var keyValue in groupByDub){ foreach (var keyValue in groupByDub){
var result = await MuxStreams(keyValue.Value, var result = await MuxStreams(keyValue.Value,
new CrunchyMuxOptions{ new CrunchyMuxOptions{
DubLangList = options.DubLang,
SubLangList = options.DlSubs,
FfmpegOptions = options.FfmpegOptions, FfmpegOptions = options.FfmpegOptions,
SkipSubMux = options.SkipSubsMux, SkipSubMux = options.SkipSubsMux,
Output = fileNameAndPath, Output = fileNameAndPath,
@ -364,7 +368,11 @@ public class CrunchyrollManager{
CcTag = options.CcTag, CcTag = options.CcTag,
KeepAllVideos = true, KeepAllVideos = true,
MuxDescription = options.IncludeVideoDescription, MuxDescription = options.IncludeVideoDescription,
DlVideoOnce = options.DlVideoOnce DlVideoOnce = options.DlVideoOnce,
DefaultSubSigns = options.DefaultSubSigns,
DefaultSubForcedDisplay = options.DefaultSubForcedDisplay,
CcSubsMuxingFlag = options.CcSubsMuxingFlag,
SignsSubsAsForced = options.SignsSubsAsForced,
}, },
fileNameAndPath); fileNameAndPath);
@ -403,6 +411,8 @@ public class CrunchyrollManager{
} else{ } else{
var result = await MuxStreams(res.Data, var result = await MuxStreams(res.Data,
new CrunchyMuxOptions{ new CrunchyMuxOptions{
DubLangList = options.DubLang,
SubLangList = options.DlSubs,
FfmpegOptions = options.FfmpegOptions, FfmpegOptions = options.FfmpegOptions,
SkipSubMux = options.SkipSubsMux, SkipSubMux = options.SkipSubsMux,
Output = fileNameAndPath, Output = fileNameAndPath,
@ -418,7 +428,11 @@ public class CrunchyrollManager{
CcTag = options.CcTag, CcTag = options.CcTag,
KeepAllVideos = true, KeepAllVideos = true,
MuxDescription = options.IncludeVideoDescription, MuxDescription = options.IncludeVideoDescription,
DlVideoOnce = options.DlVideoOnce DlVideoOnce = options.DlVideoOnce,
DefaultSubSigns = options.DefaultSubSigns,
DefaultSubForcedDisplay = options.DefaultSubForcedDisplay,
CcSubsMuxingFlag = options.CcSubsMuxingFlag,
SignsSubsAsForced = options.SignsSubsAsForced,
}, },
fileNameAndPath); fileNameAndPath);
@ -497,6 +511,9 @@ public class CrunchyrollManager{
History.SetAsDownloaded(data.SeriesId, data.SeasonId, data.Data.First().MediaId); 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; return true;
} }
@ -623,6 +640,8 @@ public class CrunchyrollManager{
var merger = new Merger(new MergerOptions{ 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(), OnlyVid = data.Where(a => a.Type == DownloadMediaType.Video).Select(a => new MergerInput{ Language = a.Lang, Path = a.Path ?? string.Empty }).ToList(),
SkipSubMux = options.SkipSubMux, SkipSubMux = options.SkipSubMux,
OnlyAudio = data.Where(a => a.Type == DownloadMediaType.Audio).Select(a => new MergerInput{ Language = a.Lang, Path = a.Path ?? string.Empty }).ToList(), 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, CcTag = options.CcTag,
mp3 = muxToMp3, 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() :[], 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 }){ if (data.Data is{ Count: > 0 }){
options.Partsize = options.Partsize > 0 ? options.Partsize : 1; options.Partsize = options.Partsize > 0 ? options.Partsize : 1;
var sortedMetaData = data.Data 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) .OrderBy(metaData => options.DubLang.IndexOf(metaData.Lang?.CrLocale ?? string.Empty) != -1 ? options.DubLang.IndexOf(metaData.Lang?.CrLocale ?? string.Empty) : int.MaxValue)
.ToList(); .ToList();
@ -1332,7 +1355,7 @@ public class CrunchyrollManager{
Data = files, Data = files,
Error = dlFailed, Error = dlFailed,
FileName = fileName.Length > 0 ? (Path.IsPathRooted(fileName) ? fileName : Path.Combine(fileDir, fileName)) : "./unknown", 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] [ObservableProperty]
private bool _searchFetchFeaturedMusic; private bool _searchFetchFeaturedMusic;
[ObservableProperty]
private bool _useCrBetaApi;
[ObservableProperty]
private bool _downloadFirstAvailableDub;
[ObservableProperty]
private bool _markAsWatched;
private bool settingsLoaded; private bool settingsLoaded;
public CrunchyrollSettingsViewModel(){ public CrunchyrollSettingsViewModel(){
@ -280,6 +289,9 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
AddScaledBorderAndShadow = options.SubsAddScaledBorder is ScaledBorderAndShadowSelection.ScaledBorderAndShadowNo or ScaledBorderAndShadowSelection.ScaledBorderAndShadowYes; AddScaledBorderAndShadow = options.SubsAddScaledBorder is ScaledBorderAndShadowSelection.ScaledBorderAndShadowNo or ScaledBorderAndShadowSelection.ScaledBorderAndShadowYes;
SelectedScaledBorderAndShadow = GetScaledBorderAndShadowFromOptions(options); SelectedScaledBorderAndShadow = GetScaledBorderAndShadowFromOptions(options);
MarkAsWatched = options.MarkAsWatched;
DownloadFirstAvailableDub = options.DownloadFirstAvailableDub;
UseCrBetaApi = options.UseCrBetaApi;
CCSubsFont = options.CcSubsFont ?? ""; CCSubsFont = options.CcSubsFont ?? "";
CCSubsMuxingFlag = options.CcSubsMuxingFlag; CCSubsMuxingFlag = options.CcSubsMuxingFlag;
SignsSubsAsForced = options.SignsSubsAsForced; SignsSubsAsForced = options.SignsSubsAsForced;
@ -344,6 +356,9 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
return; return;
} }
CrunchyrollManager.Instance.CrunOptions.MarkAsWatched = MarkAsWatched;
CrunchyrollManager.Instance.CrunOptions.DownloadFirstAvailableDub = DownloadFirstAvailableDub;
CrunchyrollManager.Instance.CrunOptions.UseCrBetaApi = UseCrBetaApi;
CrunchyrollManager.Instance.CrunOptions.SignsSubsAsForced = SignsSubsAsForced; CrunchyrollManager.Instance.CrunOptions.SignsSubsAsForced = SignsSubsAsForced;
CrunchyrollManager.Instance.CrunOptions.CcSubsMuxingFlag = CCSubsMuxingFlag; CrunchyrollManager.Instance.CrunOptions.CcSubsMuxingFlag = CCSubsMuxingFlag;
CrunchyrollManager.Instance.CrunOptions.CcSubsFont = CCSubsFont; CrunchyrollManager.Instance.CrunOptions.CcSubsFont = CCSubsFont;

View file

@ -9,7 +9,7 @@
x:DataType="vm:CrunchyrollSettingsViewModel" x:DataType="vm:CrunchyrollSettingsViewModel"
x:Class="CRD.Downloader.Crunchyroll.Views.CrunchyrollSettingsView"> x:Class="CRD.Downloader.Crunchyroll.Views.CrunchyrollSettingsView">
<Design.DataContext> <Design.DataContext>
<vm:CrunchyrollSettingsViewModel /> <vm:CrunchyrollSettingsViewModel />
</Design.DataContext> </Design.DataContext>
@ -17,6 +17,7 @@
<ScrollViewer Padding="20 20 20 0"> <ScrollViewer Padding="20 20 20 0">
<StackPanel Spacing="8"> <StackPanel Spacing="8">
<controls:SettingsExpander Header="Dub language" <controls:SettingsExpander Header="Dub language"
IconSource="Speaker2" IconSource="Speaker2"
Description="Change the selected dub language (with multiple dubs some can be out of sync)"> Description="Change the selected dub language (with multiple dubs some can be out of sync)">
@ -203,10 +204,10 @@
<CheckBox IsChecked="{Binding SearchFetchFeaturedMusic}"> </CheckBox> <CheckBox IsChecked="{Binding SearchFetchFeaturedMusic}"> </CheckBox>
</controls:SettingsExpanderItem.Footer> </controls:SettingsExpanderItem.Footer>
</controls:SettingsExpanderItem> </controls:SettingsExpanderItem>
</controls:SettingsExpander> </controls:SettingsExpander>
<controls:SettingsExpander Header="Download Settings" <controls:SettingsExpander Header="Download Settings"
IconSource="Download" IconSource="Download"
Description="Adjust download settings" Description="Adjust download settings"
@ -265,6 +266,12 @@
</controls:SettingsExpanderItem.Footer> </controls:SettingsExpanderItem.Footer>
</controls:SettingsExpanderItem> </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 Content="Audio Quality">
<controls:SettingsExpanderItem.Footer> <controls:SettingsExpanderItem.Footer>
<ComboBox HorizontalContentAlignment="Center" MinWidth="210" MaxDropDownHeight="400" <ComboBox HorizontalContentAlignment="Center" MinWidth="210" MaxDropDownHeight="400"
@ -279,6 +286,12 @@
<CheckBox IsChecked="{Binding DownloadChapters}"> </CheckBox> <CheckBox IsChecked="{Binding DownloadChapters}"> </CheckBox>
</controls:SettingsExpanderItem.Footer> </controls:SettingsExpanderItem.Footer>
</controls:SettingsExpanderItem> </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>
</controls:SettingsExpander.Footer> </controls:SettingsExpander.Footer>
@ -541,6 +554,27 @@
</controls:SettingsExpander.Footer> </controls:SettingsExpander.Footer>
</controls:SettingsExpander> </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> </StackPanel>
</ScrollViewer> </ScrollViewer>

View file

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia; using Avalonia;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
@ -175,7 +176,10 @@ public partial class ProgramManager : ObservableObject{
private void CleanUpOldUpdater(){ 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)){ if (File.Exists(backupFilePath)){
try{ try{

View file

@ -5,8 +5,10 @@ using System.Collections.Specialized;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using CRD.Downloader.Crunchyroll; using CRD.Downloader.Crunchyroll;
using CRD.Utils;
using CRD.Utils.CustomList; using CRD.Utils.CustomList;
using CRD.Utils.Structs; using CRD.Utils.Structs;
using CRD.Utils.Structs.Crunchyroll;
using CRD.Utils.Structs.History; using CRD.Utils.Structs.History;
using CRD.ViewModels; using CRD.ViewModels;
using CRD.Views; using CRD.Views;
@ -61,6 +63,8 @@ public class QueueManager{
Console.Error.WriteLine("Failed to Remove Episode from list"); Console.Error.WriteLine("Failed to Remove Episode from list");
} }
} }
} else if (e.Action == NotifyCollectionChangedAction.Reset && Queue.Count == 0){
DownloadItemModels.Clear();
} }
UpdateDownloadListItems(); UpdateDownloadListItems();
@ -151,14 +155,41 @@ public class QueueManager{
selected.OnlySubs = onlySubs; 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); 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.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: "); 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) => 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( Console.Error.WriteLine(
$"{selected.SeasonTitle} - Season {selected.Season} - {selected.EpisodeTitle} dubs - [{string.Join(", ", languages)}] subs - [{string.Join(", ", selected.AvailableSubs ??[])}]"); $"{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: "); Console.Error.WriteLine("Episode couldn't be added to Queue - Available dubs/subs: ");
var languages = sList.EpisodeAndLanguages.Items.Select((a, index) => 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 ??[])}]"); 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)); 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){ if (movieMeta != null){
movieMeta.DownloadSubs = CrunchyrollManager.Instance.CrunOptions.DlSubs; movieMeta.DownloadSubs = CrunchyrollManager.Instance.CrunOptions.DlSubs;
movieMeta.OnlySubs = onlySubs; 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); Queue.Add(movieMeta);
Console.WriteLine("Added Movie to Queue"); Console.WriteLine("Added Movie to Queue");
@ -199,6 +242,9 @@ public class QueueManager{
public void CrAddMusicMetaToQueue(CrunchyEpMeta epMeta){ public void CrAddMusicMetaToQueue(CrunchyEpMeta epMeta){
var newOptions = Helpers.DeepCopy(CrunchyrollManager.Instance.CrunOptions);
epMeta.DownloadSettings = newOptions;
Queue.Add(epMeta); Queue.Add(epMeta);
MessageBus.Current.SendMessage(new ToastMessage($"Added episode to the queue", ToastType.Information, 1)); MessageBus.Current.SendMessage(new ToastMessage($"Added episode to the queue", ToastType.Information, 1));
} }
@ -210,15 +256,18 @@ public class QueueManager{
if (musicVideo != null){ if (musicVideo != null){
var musicVideoMeta = CrunchyrollManager.Instance.CrMusic.EpisodeMeta(musicVideo); var musicVideoMeta = CrunchyrollManager.Instance.CrMusic.EpisodeMeta(musicVideo);
(HistoryEpisode? historyEpisode, List<string> dublist, List<string> sublist, string downloadDirPath, string videoQuality) historyEpisode = (null, [], [], "", ""); (HistoryEpisode? historyEpisode, List<string> dublist, List<string> sublist, string downloadDirPath, string videoQuality) historyEpisode = (null, [], [], "", "");
if (CrunchyrollManager.Instance.CrunOptions.History){ if (CrunchyrollManager.Instance.CrunOptions.History){
historyEpisode = CrunchyrollManager.Instance.History.GetHistoryEpisodeWithDubListAndDownloadDir(musicVideoMeta.SeriesId, musicVideoMeta.SeasonId, musicVideoMeta.Data.First().MediaId); 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; 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); Queue.Add(musicVideoMeta);
MessageBus.Current.SendMessage(new ToastMessage($"Added music video to the queue", ToastType.Information, 1)); MessageBus.Current.SendMessage(new ToastMessage($"Added music video to the queue", ToastType.Information, 1));
} }
@ -231,14 +280,18 @@ public class QueueManager{
if (concert != null){ if (concert != null){
var concertMeta = CrunchyrollManager.Instance.CrMusic.EpisodeMeta(concert); var concertMeta = CrunchyrollManager.Instance.CrMusic.EpisodeMeta(concert);
(HistoryEpisode? historyEpisode, List<string> dublist, List<string> sublist, string downloadDirPath, string videoQuality) historyEpisode = (null, [], [], "", ""); (HistoryEpisode? historyEpisode, List<string> dublist, List<string> sublist, string downloadDirPath, string videoQuality) historyEpisode = (null, [], [], "", "");
if (CrunchyrollManager.Instance.CrunOptions.History){ if (CrunchyrollManager.Instance.CrunOptions.History){
historyEpisode = CrunchyrollManager.Instance.History.GetHistoryEpisodeWithDubListAndDownloadDir(concertMeta.SeriesId, concertMeta.SeasonId, concertMeta.Data.First().MediaId); 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; 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); Queue.Add(concertMeta);
MessageBus.Current.SendMessage(new ToastMessage($"Added concert to the queue", ToastType.Information, 1)); 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; 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); Queue.Add(crunchyEpMeta);
} else{ } else{
failed = true; failed = true;

View file

@ -21,6 +21,7 @@ using CRD.Utils.Structs;
using CRD.Utils.Structs.Crunchyroll; using CRD.Utils.Structs.Crunchyroll;
using Microsoft.Win32; using Microsoft.Win32;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace CRD.Utils; namespace CRD.Utils;
@ -39,10 +40,19 @@ public class Helpers{
} }
public static T DeepCopy<T>(T obj){ 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); return JsonConvert.DeserializeObject<T>(json);
} }
public static string ConvertTimeFormat(string time){ public static string ConvertTimeFormat(string time){
var timeParts = time.Split(':', '.'); var timeParts = time.Split(':', '.');
int hours = int.Parse(timeParts[0]); 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 ApiBeta = "https://beta-api.crunchyroll.com";
public static readonly string ApiN = "https://www.crunchyroll.com"; public static readonly string ApiN = "https://www.crunchyroll.com";
public static readonly string Anilist = "https://graphql.anilist.co"; public static readonly string Anilist = "https://graphql.anilist.co";
public static readonly string Auth = ApiN + "/auth/v1/token"; public static string Auth => (CrunchyrollManager.Instance.CrunOptions.UseCrBetaApi ? ApiBeta : ApiN) + "/auth/v1/token";
public static readonly string Profile = ApiN + "/accounts/v1/me/profile"; public static string Profile => (CrunchyrollManager.Instance.CrunOptions.UseCrBetaApi ? ApiBeta : ApiN) + "/accounts/v1/me/profile";
public static readonly string CmsToken = ApiN + "/index/v2"; public static string CmsToken => (CrunchyrollManager.Instance.CrunOptions.UseCrBetaApi ? ApiBeta : ApiN) + "/index/v2";
public static readonly string Search = ApiN + "/content/v2/discover/search"; public static string Search => (CrunchyrollManager.Instance.CrunOptions.UseCrBetaApi ? ApiBeta : ApiN) + "/content/v2/discover/search";
public static readonly string Browse = ApiN + "/content/v2/discover/browse"; public static string Browse => (CrunchyrollManager.Instance.CrunOptions.UseCrBetaApi ? ApiBeta : ApiN) + "/content/v2/discover/browse";
public static readonly string Cms = ApiN + "/content/v2/cms"; public static string Cms => (CrunchyrollManager.Instance.CrunOptions.UseCrBetaApi ? ApiBeta : ApiN) + "/content/v2/cms";
public static readonly string Content = ApiN + "/content/v2"; 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 BetaBrowse = ApiBeta + "/content/v1/browse";
public static readonly string BetaCms = ApiBeta + "/cms/v2"; public static readonly string BetaCms = ApiBeta + "/cms/v2";
public static readonly string DRM = ApiBeta + "/drm/v1/auth"; 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 authBasic = "Basic bm9haWhkZXZtXzZpeWcwYThsMHE6";
public static readonly string authBasicMob = "Basic ZG1yeWZlc2NkYm90dWJldW56NXo6NU45aThPV2cyVmtNcm1oekNfNUNXekRLOG55SXo0QU0="; public static readonly string authBasicMob = "Basic ZG1yeWZlc2NkYm90dWJldW56NXo6NU45aThPV2cyVmtNcm1oekNfNUNXekRLOG55SXo0QU0=";

View file

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

View file

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

View file

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

View file

@ -15,6 +15,7 @@ namespace CRD.Utils.Updater;
public class Updater : INotifyPropertyChanged{ public class Updater : INotifyPropertyChanged{
public double progress = 0; public double progress = 0;
public bool failed = false;
#region Singelton #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"; private readonly string apiEndpoint = "https://api.github.com/repos/Crunchy-DL/Crunchy-Downloader/releases/latest";
public async Task<bool> CheckForUpdatesAsync(){ public async Task<bool> CheckForUpdatesAsync(){
if (Directory.Exists(tempPath)){ if (File.Exists(tempPath)){
Directory.Delete(tempPath, true); File.Delete(tempPath);
} }
if (Directory.Exists(extractPath)){ if (Directory.Exists(extractPath)){
@ -123,6 +124,7 @@ public class Updater : INotifyPropertyChanged{
public async Task DownloadAndUpdateAsync(){ public async Task DownloadAndUpdateAsync(){
try{ try{
failed = false;
Helpers.EnsureDirectoriesExist(tempPath); Helpers.EnsureDirectoriesExist(tempPath);
// Download the zip file // Download the zip file
@ -164,19 +166,54 @@ public class Updater : INotifyPropertyChanged{
ApplyUpdate(extractPath); ApplyUpdate(extractPath);
} else{ } else{
Console.Error.WriteLine("Failed to get Update"); Console.Error.WriteLine("Failed to get Update");
failed = true;
OnPropertyChanged(nameof(failed));
} }
} catch (Exception e){ } catch (Exception e){
Console.Error.WriteLine($"Failed to get Update: {e.Message}"); Console.Error.WriteLine($"Failed to get Update: {e.Message}");
failed = true;
OnPropertyChanged(nameof(failed));
} }
} }
private void ApplyUpdate(string updateFolder){ private void ApplyUpdate(string updateFolder){
var ExecutableExtension = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : string.Empty; var executableExtension = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : string.Empty;
var currentPath = AppDomain.CurrentDomain.BaseDirectory; var currentPath = Path.GetFullPath(AppContext.BaseDirectory);
var updaterPath = Path.Combine(currentPath, "Updater" + ExecutableExtension); var updaterPath = Path.Combine(currentPath, "Updater" + executableExtension);
var arguments = $"\"{currentPath.Substring(0, currentPath.Length - 1)}\" \"{updateFolder}\"";
System.Diagnostics.Process.Start(updaterPath, arguments); if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)){
Environment.Exit(0); 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; CrunchyrollManager.Instance.CrunOptions.RemoveFinishedDownload = value;
CfgManager.WriteCrSettings(); 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{ public partial class DownloadItemModel : INotifyPropertyChanged{
@ -210,7 +230,7 @@ public partial class DownloadItemModel : INotifyPropertyChanged{
newOptions.Noaudio = true; 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] [ObservableProperty]
private double _progress; private double _progress;
[ObservableProperty]
private bool _failed;
private AccountPageViewModel accountPageViewModel; private AccountPageViewModel accountPageViewModel;
@ -28,7 +30,11 @@ public partial class ContentDialogUpdateViewModel : ViewModelBase{
private void Progress_PropertyChanged(object? sender, PropertyChangedEventArgs e){ private void Progress_PropertyChanged(object? sender, PropertyChangedEventArgs e){
if (e.PropertyName == nameof(Updater.Instance.progress)){ if (e.PropertyName == nameof(Updater.Instance.progress)){
Progress = 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="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </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 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> <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> </StackPanel>

View file

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

View file

@ -9,7 +9,8 @@
x:Class="CRD.Views.Utils.ContentDialogUpdateView"> x:Class="CRD.Views.Utils.ContentDialogUpdateView">
<StackPanel Spacing="10" MinWidth="400"> <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"/> <ProgressBar Minimum="0" Maximum="100" Value="{Binding Progress}" HorizontalAlignment="Center" VerticalAlignment="Center" Width="350"/>
</StackPanel> </StackPanel>
</UserControl> </UserControl>