Add - Added a new download method, which can be enabled in the general download settings

Add - Added missing fonts
Chg - Changed to request encryption keys before downloading
Chg - Changed some log messages
This commit is contained in:
Elwador 2025-07-28 20:42:00 +00:00
parent 65200147a0
commit e80568cbb0
33 changed files with 522 additions and 161 deletions

View file

@ -173,7 +173,7 @@ public class CrEpisode(){
} }
var epNum = episodeP.Key.StartsWith('E') ? episodeP.Key[1..] : episodeP.Key; var epNum = episodeP.Key.StartsWith('E') ? episodeP.Key[1..] : episodeP.Key;
var images = (item.Images?.Thumbnail ?? new List<List<Image>>{ new List<Image>{ new Image{ Source = "/notFound.png" } } }); var images = (item.Images?.Thumbnail ??[new List<Image>{ new(){ Source = "/notFound.jpg" } }]);
Regex dubPattern = new Regex(@"\(\w+ Dub\)"); Regex dubPattern = new Regex(@"\(\w+ Dub\)");
@ -190,6 +190,7 @@ public class CrEpisode(){
epMeta.SeriesId = item.SeriesId; epMeta.SeriesId = item.SeriesId;
epMeta.AbsolutEpisodeNumberE = epNum; epMeta.AbsolutEpisodeNumberE = epNum;
epMeta.Image = images[images.Count / 2].FirstOrDefault()?.Source; epMeta.Image = images[images.Count / 2].FirstOrDefault()?.Source;
epMeta.ImageBig = images[images.Count / 2].LastOrDefault()?.Source;
epMeta.DownloadProgress = new DownloadProgress(){ epMeta.DownloadProgress = new DownloadProgress(){
IsDownloading = false, IsDownloading = false,
Done = false, Done = false,

View file

@ -61,7 +61,7 @@ public class CrMovies{
return null; return null;
} }
var images = (episodeP.Images?.Thumbnail ?? new List<List<Image>>{ new List<Image>{ new Image{ Source = "/notFound.png" } } }); var images = (episodeP.Images?.Thumbnail ??[new List<Image>(){ new(){ Source = "/notFound.jpg" } }]);
var epMeta = new CrunchyEpMeta(); var epMeta = new CrunchyEpMeta();
epMeta.Data = new List<CrunchyEpMetaData>{ new(){ MediaId = episodeP.Id, Versions = null, IsSubbed = episodeP.IsSubbed, IsDubbed = episodeP.IsDubbed } }; epMeta.Data = new List<CrunchyEpMetaData>{ new(){ MediaId = episodeP.Id, Versions = null, IsSubbed = episodeP.IsSubbed, IsDubbed = episodeP.IsDubbed } };
@ -74,6 +74,7 @@ public class CrMovies{
epMeta.SeriesId = ""; epMeta.SeriesId = "";
epMeta.AbsolutEpisodeNumberE = ""; epMeta.AbsolutEpisodeNumberE = "";
epMeta.Image = images[images.Count / 2].FirstOrDefault()?.Source; epMeta.Image = images[images.Count / 2].FirstOrDefault()?.Source;
epMeta.ImageBig = images[images.Count / 2].LastOrDefault()?.Source;
epMeta.DownloadProgress = new DownloadProgress(){ epMeta.DownloadProgress = new DownloadProgress(){
IsDownloading = false, IsDownloading = false,
Done = false, Done = false,

View file

@ -165,7 +165,7 @@ public class CrMusic{
public CrunchyEpMeta EpisodeMeta(CrunchyMusicVideo episodeP){ public CrunchyEpMeta EpisodeMeta(CrunchyMusicVideo episodeP){
var images = (episodeP.Images?.Thumbnail ?? new List<Image>{ new Image{ Source = "/notFound.png" } }); var images = (episodeP.Images?.Thumbnail ??[new Image{ Source = "/notFound.jpg" }]);
var epMeta = new CrunchyEpMeta(); var epMeta = new CrunchyEpMeta();
@ -179,6 +179,7 @@ public class CrMusic{
epMeta.SeriesId = episodeP.GetSeriesId(); epMeta.SeriesId = episodeP.GetSeriesId();
epMeta.AbsolutEpisodeNumberE = ""; epMeta.AbsolutEpisodeNumberE = "";
epMeta.Image = images[images.Count / 2].Source; epMeta.Image = images[images.Count / 2].Source;
epMeta.ImageBig = images[images.Count / 2].Source;
epMeta.DownloadProgress = new DownloadProgress(){ epMeta.DownloadProgress = new DownloadProgress(){
IsDownloading = false, IsDownloading = false,
Done = false, Done = false,

View file

@ -56,7 +56,7 @@ public class CrSeries{
} }
var epNum = key.StartsWith('E') ? key[1..] : key; var epNum = key.StartsWith('E') ? key[1..] : key;
var images = (item.Images?.Thumbnail ?? new List<List<Image>>{ new List<Image>{ new Image{ Source = "/notFound.png" } } }); var images = (item.Images?.Thumbnail ??[new List<Image>{ new(){ Source = "/notFound.jpg" } }]);
Regex dubPattern = new Regex(@"\(\w+ Dub\)"); Regex dubPattern = new Regex(@"\(\w+ Dub\)");
@ -71,6 +71,7 @@ public class CrSeries{
epMeta.SeriesId = item.SeriesId; epMeta.SeriesId = item.SeriesId;
epMeta.AbsolutEpisodeNumberE = epNum; epMeta.AbsolutEpisodeNumberE = epNum;
epMeta.Image = images[images.Count / 2].FirstOrDefault()?.Source ?? ""; epMeta.Image = images[images.Count / 2].FirstOrDefault()?.Source ?? "";
epMeta.ImageBig = images[images.Count / 2].LastOrDefault()?.Source;
epMeta.DownloadProgress = new DownloadProgress(){ epMeta.DownloadProgress = new DownloadProgress(){
IsDownloading = false, IsDownloading = false,
Done = false, Done = false,
@ -265,7 +266,7 @@ public class CrSeries{
crunchySeriesList.List = sortedEpisodes.Select(kvp => { crunchySeriesList.List = sortedEpisodes.Select(kvp => {
var key = kvp.Key; var key = kvp.Key;
var value = kvp.Value; var value = kvp.Value;
var images = (value.Items[0].Images?.Thumbnail ?? new List<List<Image>>{ new List<Image>{ new Image{ Source = "/notFound.png" } } }); var images = (value.Items[0].Images?.Thumbnail ??[new List<Image>{ new(){ Source = "/notFound.jpg" } }]);
var seconds = (int)Math.Floor(value.Items[0].DurationMs / 1000.0); var seconds = (int)Math.Floor(value.Items[0].DurationMs / 1000.0);
var langList = value.Langs.Select(a => a.CrLocale).ToList(); var langList = value.Langs.Select(a => a.CrLocale).ToList();
Languages.SortListByLangList(langList); Languages.SortListByLangList(langList);

View file

@ -1,10 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -108,8 +106,8 @@ public class CrunchyrollManager{
options.Partsize = 10; options.Partsize = 10;
options.DlSubs = new List<string>{ "en-US" }; options.DlSubs = new List<string>{ "en-US" };
options.SkipMuxing = false; options.SkipMuxing = false;
options.MkvmergeOptions = new List<string>{ "--no-date", "--disable-track-statistics-tags", "--engage no_variable_data" }; options.MkvmergeOptions =[];
options.FfmpegOptions = new(); options.FfmpegOptions =[];
options.DefaultAudio = "ja-JP"; options.DefaultAudio = "ja-JP";
options.DefaultSub = "en-US"; options.DefaultSub = "en-US";
options.QualityAudio = "best"; options.QualityAudio = "best";
@ -339,6 +337,7 @@ public class CrunchyrollManager{
Mp4 = options.Mp4, Mp4 = options.Mp4,
Mp3 = options.AudioOnlyToMp3, Mp3 = options.AudioOnlyToMp3,
MuxFonts = options.MuxFonts, MuxFonts = options.MuxFonts,
MuxCover = options.MuxCover,
VideoTitle = res.VideoTitle, VideoTitle = res.VideoTitle,
Novids = options.Novids, Novids = options.Novids,
NoCleanup = options.Nocleanup, NoCleanup = options.Nocleanup,
@ -405,6 +404,7 @@ public class CrunchyrollManager{
Mp4 = options.Mp4, Mp4 = options.Mp4,
Mp3 = options.AudioOnlyToMp3, Mp3 = options.AudioOnlyToMp3,
MuxFonts = options.MuxFonts, MuxFonts = options.MuxFonts,
MuxCover = options.MuxCover,
VideoTitle = res.VideoTitle, VideoTitle = res.VideoTitle,
Novids = options.Novids, Novids = options.Novids,
NoCleanup = options.Nocleanup, NoCleanup = options.Nocleanup,
@ -520,8 +520,6 @@ public class CrunchyrollManager{
if (CrunOptions.ShutdownWhenQueueEmpty){ if (CrunOptions.ShutdownWhenQueueEmpty){
Helpers.ShutdownComputer(); Helpers.ShutdownComputer();
} }
} }
return true; return true;
@ -678,6 +676,7 @@ public class CrunchyrollManager{
CcSubsMuxingFlag = options.CcSubsMuxingFlag, CcSubsMuxingFlag = options.CcSubsMuxingFlag,
SignsSubsAsForced = options.SignsSubsAsForced, 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() :[],
Cover = options.MuxCover ? data.Where(a => a.Type == DownloadMediaType.Cover).Select(a => new MergerInput{ Path = a.Path ?? string.Empty }).ToList() : [],
}); });
if (!File.Exists(CfgManager.PathFFMPEG)){ if (!File.Exists(CfgManager.PathFFMPEG)){
@ -1291,6 +1290,7 @@ public class CrunchyrollManager{
pssh = item.pssh, pssh = item.pssh,
language = item.language, language = item.language,
bandwidth = item.bandwidth, bandwidth = item.bandwidth,
audioSamplingRate = item.audioSamplingRate,
resolutionText = $"{Math.Round(item.bandwidth / 1000.0)}kB/s" resolutionText = $"{Math.Round(item.bandwidth / 1000.0)}kB/s"
}).ToList(); }).ToList();
@ -1307,6 +1307,7 @@ public class CrunchyrollManager{
.GroupBy(a => new{ a.bandwidth, a.language }) // Add more properties if needed .GroupBy(a => new{ a.bandwidth, a.language }) // Add more properties if needed
.Select(g => g.First()) .Select(g => g.First())
.OrderBy(a => a.bandwidth) .OrderBy(a => a.bandwidth)
.ThenBy(a => a.audioSamplingRate)
.ToList(); .ToList();
if (string.IsNullOrEmpty(data.VideoQuality)){ if (string.IsNullOrEmpty(data.VideoQuality)){
@ -1375,7 +1376,7 @@ public class CrunchyrollManager{
Console.WriteLine("Available Audio Qualities:"); Console.WriteLine("Available Audio Qualities:");
for (int i = 0; i < audios.Count; i++){ for (int i = 0; i < audios.Count; i++){
Console.WriteLine($"\t[{i + 1}] {audios[i].resolutionText}"); Console.WriteLine($"\t[{i + 1}] {audios[i].resolutionText} / {audios[i].audioSamplingRate}");
} }
variables.Add(new Variable("height", chosenVideoSegments.quality.height, false)); variables.Add(new Variable("height", chosenVideoSegments.quality.height, false));
@ -1396,7 +1397,7 @@ public class CrunchyrollManager{
Console.WriteLine($"Selected quality:"); Console.WriteLine($"Selected quality:");
Console.WriteLine($"\tVideo: {chosenVideoSegments.resolutionText}"); Console.WriteLine($"\tVideo: {chosenVideoSegments.resolutionText}");
Console.WriteLine($"\tAudio: {chosenAudioSegments.resolutionText}"); Console.WriteLine($"\tAudio: {chosenAudioSegments.resolutionText} / {chosenAudioSegments.audioSamplingRate}");
Console.WriteLine($"\tServer: {selectedServer}"); Console.WriteLine($"\tServer: {selectedServer}");
Console.WriteLine("Stream URL:" + chosenVideoSegments.segments[0].uri.Split(new[]{ ",.urlset" }, StringSplitOptions.None)[0]); Console.WriteLine("Stream URL:" + chosenVideoSegments.segments[0].uri.Split(new[]{ ",.urlset" }, StringSplitOptions.None)[0]);
@ -1448,6 +1449,20 @@ public class CrunchyrollManager{
} else if (options.Novids){ } else if (options.Novids){
Console.WriteLine("Skipping video download..."); Console.WriteLine("Skipping video download...");
} else{ } else{
await CrAuth.RefreshToken(true);
Dictionary<string, string> authDataDict = new Dictionary<string, string>
{ { "authorization", "Bearer " + Token?.access_token },{ "x-cr-content-id", mediaGuid },{ "x-cr-video-token", pbData.Meta?.Token ?? string.Empty } };
chosenVideoSegments.encryptionKeys = await _widevine.getKeys(chosenVideoSegments.pssh, ApiUrls.WidevineLicenceUrl, authDataDict);
if (!string.IsNullOrEmpty(chosenVideoSegments.pssh) && !chosenVideoSegments.pssh.Equals(chosenAudioSegments.pssh)){
if (chosenAudioSegments.segments.Count > 0 && !options.Noaudio && !dlFailed){
Console.WriteLine("Video and Audio PSSH different requesting Audio encryption keys");
chosenAudioSegments.encryptionKeys = await _widevine.getKeys(chosenAudioSegments.pssh, ApiUrls.WidevineLicenceUrl, authDataDict);
}
}
var videoDownloadResult = await DownloadVideo(chosenVideoSegments, options, outFile, tempTsFile, data, fileDir); var videoDownloadResult = await DownloadVideo(chosenVideoSegments, options, outFile, tempTsFile, data, fileDir);
tsFile = videoDownloadResult.tsFile; tsFile = videoDownloadResult.tsFile;
@ -1467,6 +1482,20 @@ public class CrunchyrollManager{
if (chosenAudioSegments.segments.Count > 0 && !options.Noaudio && !dlFailed){ if (chosenAudioSegments.segments.Count > 0 && !options.Noaudio && !dlFailed){
await CrAuth.RefreshToken(true);
if (chosenVideoSegments.encryptionKeys.Count == 0){
Dictionary<string, string> authDataDict = new Dictionary<string, string>
{ { "authorization", "Bearer " + Token?.access_token },{ "x-cr-content-id", mediaGuid },{ "x-cr-video-token", pbData.Meta?.Token ?? string.Empty } };
chosenVideoSegments.encryptionKeys = await _widevine.getKeys(chosenVideoSegments.pssh, ApiUrls.WidevineLicenceUrl, authDataDict);
if (!string.IsNullOrEmpty(chosenVideoSegments.pssh) && !chosenVideoSegments.pssh.Equals(chosenAudioSegments.pssh)){
Console.WriteLine("Video and Audio PSSH different requesting Audio encryption keys");
chosenAudioSegments.encryptionKeys = await _widevine.getKeys(chosenAudioSegments.pssh, ApiUrls.WidevineLicenceUrl, authDataDict);
}
}
var audioDownloadResult = await DownloadAudio(chosenAudioSegments, options, outFile, tempTsFile, data, fileDir); var audioDownloadResult = await DownloadAudio(chosenAudioSegments, options, outFile, tempTsFile, data, fileDir);
tsFile = audioDownloadResult.tsFile; tsFile = audioDownloadResult.tsFile;
@ -1517,20 +1546,25 @@ public class CrunchyrollManager{
Dictionary<string, string> authDataDict = new Dictionary<string, string> Dictionary<string, string> authDataDict = new Dictionary<string, string>
{ { "authorization", "Bearer " + Token?.access_token },{ "x-cr-content-id", mediaGuid },{ "x-cr-video-token", pbData.Meta?.Token ?? string.Empty } }; { { "authorization", "Bearer " + Token?.access_token },{ "x-cr-content-id", mediaGuid },{ "x-cr-video-token", pbData.Meta?.Token ?? string.Empty } };
var encryptionKeys = await _widevine.getKeys(chosenVideoSegments.pssh, ApiUrls.WidevineLicenceUrl, authDataDict); var encryptionKeys = chosenVideoSegments.encryptionKeys;
if (encryptionKeys.Count == 0){ if (encryptionKeys.Count == 0){
Console.Error.WriteLine("Failed to get encryption keys"); encryptionKeys = await _widevine.getKeys(chosenVideoSegments.pssh, ApiUrls.WidevineLicenceUrl, authDataDict);
dlFailed = true;
return new DownloadResponse{ if (encryptionKeys.Count == 0){
Data = files, Console.Error.WriteLine("Failed to get encryption keys");
Error = dlFailed, dlFailed = true;
FileName = fileName.Length > 0 ? (Path.IsPathRooted(fileName) ? fileName : Path.Combine(fileDir, fileName)) : "./unknown", return new DownloadResponse{
ErrorText = "Couldn't get DRM encryption keys" Data = files,
}; Error = dlFailed,
FileName = fileName.Length > 0 ? (Path.IsPathRooted(fileName) ? fileName : Path.Combine(fileDir, fileName)) : "./unknown",
ErrorText = "Couldn't get DRM encryption keys"
};
}
} }
List<ContentKey> encryptionKeysAudio =[];
List<ContentKey> encryptionKeysAudio = chosenAudioSegments.encryptionKeys;
if (!string.IsNullOrEmpty(chosenVideoSegments.pssh) && !chosenVideoSegments.pssh.Equals(chosenAudioSegments.pssh)){ if (!string.IsNullOrEmpty(chosenVideoSegments.pssh) && !chosenVideoSegments.pssh.Equals(chosenAudioSegments.pssh)){
Console.WriteLine("Video and Audio PSSH different requesting Audio encryption keys"); Console.WriteLine("Video and Audio PSSH different requesting Audio encryption keys");
encryptionKeysAudio = await _widevine.getKeys(chosenAudioSegments.pssh, ApiUrls.WidevineLicenceUrl, authDataDict); encryptionKeysAudio = await _widevine.getKeys(chosenAudioSegments.pssh, ApiUrls.WidevineLicenceUrl, authDataDict);
@ -1861,6 +1895,27 @@ public class CrunchyrollManager{
Console.WriteLine($"{fileName}.xml has been created with the description."); Console.WriteLine($"{fileName}.xml has been created with the description.");
} }
if (options.MuxCover){
if (!string.IsNullOrEmpty(data.ImageBig) && !File.Exists(fileDir + "cover.png")){
var bitmap = await Helpers.LoadImage(data.ImageBig);
if (bitmap != null){
string coverPath = Path.Combine(fileDir, "cover.png");
Helpers.EnsureDirectoriesExist(coverPath);
await using (var fs = File.OpenWrite(coverPath)){
bitmap.Save(fs); // always saves PNG
}
bitmap.Dispose();
files.Add(new DownloadedMedia{
Type = DownloadMediaType.Cover,
Lang = Languages.DEFAULT_lang,
Path = coverPath
});
}
}
}
var tempFolderPath = ""; var tempFolderPath = "";
if (options.DownloadToTempFolder){ if (options.DownloadToTempFolder){
@ -1928,7 +1983,6 @@ public class CrunchyrollManager{
var isCc = subsItem.isCC; var isCc = subsItem.isCC;
var isDuplicate = false; var isDuplicate = false;
if ((!options.IncludeSignsSubs && isSigns) || (!options.IncludeCcSubs && isCc)){ if ((!options.IncludeSignsSubs && isSigns) || (!options.IncludeCcSubs && isCc)){
continue; continue;
@ -1945,8 +1999,9 @@ public class CrunchyrollManager{
continue; continue;
} }
} }
sxData.File = Languages.SubsFile(fileName, index + "", langItem, isDuplicate ? videoDownloadMedia.Lang.CrLocale : "",isCc, options.CcTag, isSigns, subsItem.format, !(data.DownloadSubs.Count == 1 && !data.DownloadSubs.Contains("all"))); sxData.File = Languages.SubsFile(fileName, index + "", langItem, isDuplicate ? videoDownloadMedia.Lang.CrLocale : "", isCc, options.CcTag, isSigns, subsItem.format,
!(data.DownloadSubs.Count == 1 && !data.DownloadSubs.Contains("all")));
sxData.Path = Path.Combine(fileDir, sxData.File); sxData.Path = Path.Combine(fileDir, sxData.File);
Helpers.EnsureDirectoriesExist(sxData.Path); Helpers.EnsureDirectoriesExist(sxData.Path);
@ -1962,7 +2017,6 @@ public class CrunchyrollManager{
if (subsAssReqResponse.IsOk){ if (subsAssReqResponse.IsOk){
if (subsItem.format == "ass"){ if (subsItem.format == "ass"){
subsAssReqResponse.ResponseContent = '\ufeff' + subsAssReqResponse.ResponseContent;
var sBodySplit = subsAssReqResponse.ResponseContent.Split(new[]{ "\r\n" }, StringSplitOptions.None).ToList(); var sBodySplit = subsAssReqResponse.ResponseContent.Split(new[]{ "\r\n" }, StringSplitOptions.None).ToList();
if (sBodySplit.Count > 2){ if (sBodySplit.Count > 2){
@ -1999,12 +2053,12 @@ public class CrunchyrollManager{
assBuilder.AppendLine(); assBuilder.AppendLine();
assBuilder.AppendLine("[V4+ Styles]"); assBuilder.AppendLine("[V4+ Styles]");
assBuilder.AppendLine("Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, " assBuilder.AppendLine("Format: Name,Fontname,Fontsize,PrimaryColour,SecondaryColour,OutlineColour,BackColour,Bold,Italic,"
+ "Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding"); + "Underline,Strikeout,ScaleX,ScaleY,Spacing,Angle,BorderStyle,Outline,Shadow,Alignment,MarginL,MarginR,MarginV,Encoding");
assBuilder.AppendLine($"Style: Default,{options.CcSubsFont ?? "Trebuchet MS"},24,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,1,2,0010,0010,0018,1"); assBuilder.AppendLine($"Style: Default,{options.CcSubsFont ?? "Trebuchet MS"},24,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,1,2,0010,0010,0018,1");
assBuilder.AppendLine(); assBuilder.AppendLine();
assBuilder.AppendLine("[Events]"); assBuilder.AppendLine("[Events]");
assBuilder.AppendLine("Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text"); assBuilder.AppendLine("Format: Layer,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text");
// Parse the VTT content // Parse the VTT content
string normalizedContent = subsAssReqResponse.ResponseContent.Replace("\r\n", "\n").Replace("\r", "\n"); string normalizedContent = subsAssReqResponse.ResponseContent.Replace("\r\n", "\n").Replace("\r", "\n");
@ -2100,10 +2154,22 @@ public class CrunchyrollManager{
FsRetryTime = options.RetryDelay * 1000, FsRetryTime = options.RetryDelay * 1000,
Retries = options.RetryAttempts, Retries = options.RetryAttempts,
Override = options.Force, Override = options.Force,
}, data, true, false); }, data, true, false, options.DownloadMethodeNew);
var defParts = new PartsData{
First = 0,
Total = 0,
Completed = 0,
};
var videoDownloadResult = (Ok: false, Parts: defParts);
try{
videoDownloadResult = await videoDownloader.Download();
} catch (Exception e){
Console.WriteLine(e);
}
var videoDownloadResult = await videoDownloader.Download();
return (videoDownloadResult.Ok, videoDownloadResult.Parts, tsFile); return (videoDownloadResult.Ok, videoDownloadResult.Parts, tsFile);
} }
@ -2158,10 +2224,21 @@ public class CrunchyrollManager{
FsRetryTime = options.RetryDelay * 1000, FsRetryTime = options.RetryDelay * 1000,
Retries = options.RetryAttempts, Retries = options.RetryAttempts,
Override = options.Force, Override = options.Force,
}, data, false, true); }, data, false, true, options.DownloadMethodeNew);
var audioDownloadResult = await audioDownloader.Download(); var defParts = new PartsData{
First = 0,
Total = 0,
Completed = 0,
};
var audioDownloadResult = (Ok: false, Parts: defParts);
try{
audioDownloadResult = await audioDownloader.Download();
} catch (Exception e){
Console.WriteLine(e);
}
return (audioDownloadResult.Ok, audioDownloadResult.Parts, tsFile); return (audioDownloadResult.Ok, audioDownloadResult.Parts, tsFile);
} }

View file

@ -69,6 +69,9 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
[ObservableProperty] [ObservableProperty]
private bool _muxFonts; private bool _muxFonts;
[ObservableProperty]
private bool _muxCover;
[ObservableProperty] [ObservableProperty]
private bool _syncTimings; private bool _syncTimings;
@ -359,6 +362,7 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
MuxToMp4 = options.Mp4; MuxToMp4 = options.Mp4;
MuxToMp3 = options.AudioOnlyToMp3; MuxToMp3 = options.AudioOnlyToMp3;
MuxFonts = options.MuxFonts; MuxFonts = options.MuxFonts;
MuxCover = options.MuxCover;
SyncTimings = options.SyncTiming; SyncTimings = options.SyncTiming;
SkipSubMux = options.SkipSubsMux; SkipSubMux = options.SkipSubsMux;
LeadingNumbers = options.Numbers; LeadingNumbers = options.Numbers;
@ -428,6 +432,7 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
CrunchyrollManager.Instance.CrunOptions.Mp4 = MuxToMp4; CrunchyrollManager.Instance.CrunOptions.Mp4 = MuxToMp4;
CrunchyrollManager.Instance.CrunOptions.AudioOnlyToMp3 = MuxToMp3; CrunchyrollManager.Instance.CrunOptions.AudioOnlyToMp3 = MuxToMp3;
CrunchyrollManager.Instance.CrunOptions.MuxFonts = MuxFonts; CrunchyrollManager.Instance.CrunOptions.MuxFonts = MuxFonts;
CrunchyrollManager.Instance.CrunOptions.MuxCover = MuxCover;
CrunchyrollManager.Instance.CrunOptions.SyncTiming = SyncTimings; CrunchyrollManager.Instance.CrunOptions.SyncTiming = SyncTimings;
CrunchyrollManager.Instance.CrunOptions.SkipSubsMux = SkipSubMux; CrunchyrollManager.Instance.CrunOptions.SkipSubsMux = SkipSubMux;
CrunchyrollManager.Instance.CrunOptions.Numbers = Math.Clamp((int)(LeadingNumbers ?? 0), 0, 10); CrunchyrollManager.Instance.CrunOptions.Numbers = Math.Clamp((int)(LeadingNumbers ?? 0), 0, 10);

View file

@ -411,6 +411,12 @@
<CheckBox IsChecked="{Binding MuxFonts}"> </CheckBox> <CheckBox IsChecked="{Binding MuxFonts}"> </CheckBox>
</controls:SettingsExpanderItem.Footer> </controls:SettingsExpanderItem.Footer>
</controls:SettingsExpanderItem> </controls:SettingsExpanderItem>
<controls:SettingsExpanderItem IsVisible="{Binding !SkipMuxing}" Content="Include episode thumbnail" Description="Embeds the episode thumbnail into the MKV as cover">
<controls:SettingsExpanderItem.Footer>
<CheckBox IsChecked="{Binding MuxCover}"> </CheckBox>
</controls:SettingsExpanderItem.Footer>
</controls:SettingsExpanderItem>
<controls:SettingsExpanderItem IsVisible="{Binding !SkipMuxing}" Content="File title" <controls:SettingsExpanderItem IsVisible="{Binding !SkipMuxing}" Content="File title"
Description="${seriesTitle} ${seasonTitle} ${title} ${season} ${episode} ${height} ${width} ${dubs}"> Description="${seriesTitle} ${seasonTitle} ${title} ${season} ${episode} ${height} ${width} ${dubs}">

View file

@ -15,7 +15,6 @@ using CRD.Utils;
using CRD.Utils.Structs; using CRD.Utils.Structs;
using CRD.Utils.Structs.History; using CRD.Utils.Structs.History;
using CRD.Utils.Updater; using CRD.Utils.Updater;
using ExtendedXmlSerializer.Core.Sources;
using FluentAvalonia.Styling; using FluentAvalonia.Styling;
namespace CRD.Downloader; namespace CRD.Downloader;

View file

@ -9,7 +9,6 @@ using CRD.Downloader.Crunchyroll;
using CRD.Utils; 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;
@ -182,12 +181,12 @@ public partial class QueueManager : ObservableObject{
case EpisodeDownloadMode.OnlyVideo: case EpisodeDownloadMode.OnlyVideo:
newOptions.Novids = false; newOptions.Novids = false;
newOptions.Noaudio = true; newOptions.Noaudio = true;
selected.DownloadSubs = ["none"]; selected.DownloadSubs =["none"];
break; break;
case EpisodeDownloadMode.OnlyAudio: case EpisodeDownloadMode.OnlyAudio:
newOptions.Novids = true; newOptions.Novids = true;
newOptions.Noaudio = false; newOptions.Noaudio = false;
selected.DownloadSubs = ["none"]; selected.DownloadSubs =["none"];
break; break;
case EpisodeDownloadMode.OnlySubs: case EpisodeDownloadMode.OnlySubs:
newOptions.Novids = true; newOptions.Novids = true;
@ -250,12 +249,12 @@ public partial class QueueManager : ObservableObject{
case EpisodeDownloadMode.OnlyVideo: case EpisodeDownloadMode.OnlyVideo:
newOptions.Novids = false; newOptions.Novids = false;
newOptions.Noaudio = true; newOptions.Noaudio = true;
movieMeta.DownloadSubs = ["none"]; movieMeta.DownloadSubs =["none"];
break; break;
case EpisodeDownloadMode.OnlyAudio: case EpisodeDownloadMode.OnlyAudio:
newOptions.Novids = true; newOptions.Novids = true;
newOptions.Noaudio = false; newOptions.Noaudio = false;
movieMeta.DownloadSubs = ["none"]; movieMeta.DownloadSubs =["none"];
break; break;
case EpisodeDownloadMode.OnlySubs: case EpisodeDownloadMode.OnlySubs:
newOptions.Novids = true; newOptions.Novids = true;
@ -345,7 +344,9 @@ public partial class QueueManager : ObservableObject{
public async Task CrAddSeriesToQueue(CrunchySeriesList list, CrunchyMultiDownload data){ public async Task CrAddSeriesToQueue(CrunchySeriesList list, CrunchyMultiDownload data){
var selected = CrunchyrollManager.Instance.CrSeries.ItemSelectMultiDub(list.Data, data.DubLang, data.But, data.AllEpisodes, data.E); var selected = CrunchyrollManager.Instance.CrSeries.ItemSelectMultiDub(list.Data, data.DubLang, data.But, data.AllEpisodes, data.E);
bool failed = false; var failed = false;
var partialAdd = false;
foreach (var crunchyEpMeta in selected.Values.ToList()){ foreach (var crunchyEpMeta in selected.Values.ToList()){
if (crunchyEpMeta.Data?.First() != null){ if (crunchyEpMeta.Data?.First() != null){
@ -411,15 +412,30 @@ public partial class QueueManager : ObservableObject{
Queue.Add(crunchyEpMeta); Queue.Add(crunchyEpMeta);
if (crunchyEpMeta.Data.Count < data.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: ");
partialAdd = true;
var languages = (crunchyEpMeta.Data.First().Versions ??[]).Select(version => $"{(version.IsPremiumOnly ? "+ " : "")}{version.AudioLocale}").ToArray();
Console.Error.WriteLine(
$"{crunchyEpMeta.SeasonTitle} - Season {crunchyEpMeta.Season} - {crunchyEpMeta.EpisodeTitle} dubs - [{string.Join(", ", languages)}] subs - [{string.Join(", ", crunchyEpMeta.AvailableSubs ??[])}]");
MessageBus.Current.SendMessage(new ToastMessage($"Added episode to the queue but couldn't find all selected dubs", ToastType.Warning, 2));
}
} else{ } else{
failed = true; failed = true;
} }
} }
if (failed){ if (failed && !partialAdd){
MainWindow.Instance.ShowError("Not all episodes could be added make sure that you are signed in with an account that has an active premium subscription?"); MainWindow.Instance.ShowError("Not all episodes could be added make sure that you are signed in with an account that has an active premium subscription?");
} else{ } else if (selected.Values.Count > 0 && !partialAdd){
MessageBus.Current.SendMessage(new ToastMessage($"Added episodes to the queue", ToastType.Information, 1)); MessageBus.Current.SendMessage(new ToastMessage($"Added episodes to the queue", ToastType.Information, 1));
} else if (!partialAdd){
MessageBus.Current.SendMessage(new ToastMessage($"Couldn't add episode(s) to the queue with current dub settings", ToastType.Error, 2));
} }
} }
} }

View file

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.IO.IsolatedStorage;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;

View file

@ -182,6 +182,9 @@ public enum DownloadMediaType{
[EnumMember(Value = "Description")] [EnumMember(Value = "Description")]
Description, Description,
[EnumMember(Value = "Cover")]
Cover,
} }
[JsonConverter(typeof(StringEnumConverter))] [JsonConverter(typeof(StringEnumConverter))]

View file

@ -4,13 +4,8 @@ using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using CRD.Downloader;
using CRD.Downloader.Crunchyroll; using CRD.Downloader.Crunchyroll;
using CRD.Utils.Structs.Crunchyroll;
using Newtonsoft.Json; using Newtonsoft.Json;
using YamlDotNet.RepresentationModel;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
namespace CRD.Utils.Files; namespace CRD.Utils.Files;

View file

@ -4,6 +4,7 @@ using System.IO;
using System.Net.Http; using System.Net.Http;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CRD.Downloader; using CRD.Downloader;
using CRD.Utils.Parser.Utils; using CRD.Utils.Parser.Utils;
@ -18,8 +19,9 @@ public class HlsDownloader{
private CrunchyEpMeta _currentEpMeta; private CrunchyEpMeta _currentEpMeta;
private bool _isVideo; private bool _isVideo;
private bool _isAudio; private bool _isAudio;
private bool _newDownloadMethode;
public HlsDownloader(HlsOptions options, CrunchyEpMeta meta, bool isVideo, bool isAudio){ public HlsDownloader(HlsOptions options, CrunchyEpMeta meta, bool isVideo, bool isAudio, bool newDownloadMethode){
if (options == null || options.M3U8Json == null || options.M3U8Json.Segments == null){ if (options == null || options.M3U8Json == null || options.M3U8Json.Segments == null){
throw new Exception("Playlist is empty"); throw new Exception("Playlist is empty");
} }
@ -29,6 +31,8 @@ public class HlsDownloader{
_isVideo = isVideo; _isVideo = isVideo;
_isAudio = isAudio; _isAudio = isAudio;
_newDownloadMethode = newDownloadMethode;
if (options?.M3U8Json != null) if (options?.M3U8Json != null)
_data = new Data{ _data = new Data{
Parts = new PartsData{ Parts = new PartsData{
@ -152,6 +156,11 @@ public class HlsDownloader{
_data.Parts.Completed = _data.Offset; _data.Parts.Completed = _data.Offset;
} }
if (_newDownloadMethode){
return await DownloadSegmentsBufferedResumeAsync(segments, fn);
}
for (int p = 0; p < Math.Ceiling((double)segments.Count / _data.Threads); p++){ for (int p = 0; p < Math.Ceiling((double)segments.Count / _data.Threads); p++){
// Start time // Start time
_data.DateStart = DateTimeOffset.Now.ToUnixTimeMilliseconds(); _data.DateStart = DateTimeOffset.Now.ToUnixTimeMilliseconds();
@ -271,6 +280,7 @@ public class HlsDownloader{
File.Delete(downloadItemDownloadedFile); File.Delete(downloadItemDownloadedFile);
} }
} catch (Exception e){ } catch (Exception e){
Console.Error.WriteLine(e.Message);
} }
} }
} }
@ -292,24 +302,182 @@ public class HlsDownloader{
return (Ok: true, _data.Parts); return (Ok: true, _data.Parts);
} }
public static Info GetDownloadInfo(long dateStartUnix, int partsDownloaded, int partsTotal, long downloadedBytes, long totalDownloadedBytes){ private static readonly object _resumeLock = new object();
// Convert Unix timestamp to DateTime
DateTime dateStart = DateTimeOffset.FromUnixTimeMilliseconds(dateStartUnix).UtcDateTime;
double dateElapsed = (DateTime.UtcNow - dateStart).TotalMilliseconds;
// Calculate percentage public async Task<(bool Ok, PartsData Parts)> DownloadSegmentsBufferedResumeAsync(List<dynamic> segments, string fn){
int percentFixed = (int)((double)partsDownloaded / partsTotal * 100); var totalSeg = _data.Parts.Total;
int percent = percentFixed < 100 ? percentFixed : (partsTotal == partsDownloaded ? 100 : 99); string sessionId = Path.GetFileNameWithoutExtension(fn);
string tempDir = Path.Combine(Path.GetDirectoryName(fn), $"{sessionId}_temp");
double downloadSpeed = downloadedBytes / (dateElapsed / 1000); Directory.CreateDirectory(tempDir);
int partsLeft = partsTotal - partsDownloaded; string resumeFile = $"{fn}.new.resume";
double remainingTime = (partsLeft * ((double)totalDownloadedBytes / partsDownloaded)) / downloadSpeed; int downloadedParts = 0;
int mergedParts = 0;
if (File.Exists(resumeFile)){
try{
var resumeData = JsonConvert.DeserializeObject<dynamic>(File.ReadAllText(resumeFile));
downloadedParts = (int?)resumeData?.DownloadedParts ?? 0;
mergedParts = (int?)resumeData?.MergedParts ?? 0;
} catch{
}
}
if (downloadedParts > totalSeg) downloadedParts = totalSeg;
if (mergedParts > downloadedParts) mergedParts = downloadedParts;
var semaphore = new SemaphoreSlim(_data.Threads);
var downloadTasks = new List<Task>();
bool errorOccurred = false;
var _lastUiUpdate = DateTimeOffset.Now.ToUnixTimeMilliseconds();
for (int i = 0; i < segments.Count; i++){
if (File.Exists(Path.Combine(tempDir, $"part_{i:D6}.tmp")))
continue;
int index = i;
await semaphore.WaitAsync();
downloadTasks.Add(Task.Run(async () => {
try{
var segment = new Segment{
Uri = ObjectUtilities.GetMemberValue(segments[index], "uri"),
Key = ObjectUtilities.GetMemberValue(segments[index], "key"),
ByteRange = ObjectUtilities.GetMemberValue(segments[index], "byteRange")
};
var data = await DownloadPart(segment, index, _data.Offset);
string tempFile = Path.Combine(tempDir, $"part_{index:D6}.tmp");
await File.WriteAllBytesAsync(tempFile, data);
int currentDownloaded = Directory.GetFiles(tempDir, "part_*.tmp").Length;
lock (_resumeLock){
File.WriteAllText(resumeFile, JsonConvert.SerializeObject(new{
DownloadedParts = currentDownloaded,
MergedParts = mergedParts,
Total = totalSeg
}));
}
if (DateTimeOffset.Now.ToUnixTimeMilliseconds() - _lastUiUpdate > 500){
var dataLog = GetDownloadInfo(
_lastUiUpdate,
currentDownloaded,
totalSeg,
_data.BytesDownloaded,
_data.TotalBytes
);
_data.BytesDownloaded = 0;
_lastUiUpdate = DateTimeOffset.Now.ToUnixTimeMilliseconds();
Console.WriteLine($"{currentDownloaded}/{totalSeg} [{dataLog.Percent}%] Speed: {dataLog.DownloadSpeed / 1000000.0:F2} MB/s ETA: {FormatTime(dataLog.Time)}");
_currentEpMeta.DownloadProgress = new DownloadProgress{
IsDownloading = true,
Percent = dataLog.Percent,
Time = dataLog.Time,
DownloadSpeed = dataLog.DownloadSpeed,
Doing = _isAudio ? "Downloading Audio" : (_isVideo ? "Downloading Video" : "")
};
}
if (!QueueManager.Instance.Queue.Contains(_currentEpMeta))
return;
QueueManager.Instance.Queue.Refresh();
while (_currentEpMeta.Paused){
await Task.Delay(500);
if (!QueueManager.Instance.Queue.Contains(_currentEpMeta))
return;
}
} catch (Exception ex){
Console.Error.WriteLine($"Error downloading part {index}: {ex.Message}");
errorOccurred = true;
} finally{
semaphore.Release();
}
}));
}
await Task.WhenAll(downloadTasks);
if (errorOccurred)
return (false, _data.Parts);
using (var output = new FileStream(fn, FileMode.Append, FileAccess.Write, FileShare.None)){
for (int i = mergedParts; i < segments.Count; i++){
string tempFile = Path.Combine(tempDir, $"part_{i:D6}.tmp");
if (!File.Exists(tempFile)){
Console.Error.WriteLine($"Missing temp file for part {i}, aborting merge.");
return (false, _data.Parts);
}
byte[] data = await File.ReadAllBytesAsync(tempFile);
await output.WriteAsync(data, 0, data.Length);
mergedParts++;
File.WriteAllText(resumeFile, JsonConvert.SerializeObject(new{
DownloadedParts = totalSeg,
MergedParts = mergedParts,
Total = totalSeg
}));
var dataLog = GetDownloadInfo(_data.DateStart, mergedParts, totalSeg, _data.BytesDownloaded, _data.TotalBytes);
Console.WriteLine($"{mergedParts}/{totalSeg} parts merged [{dataLog.Percent}%]");
_currentEpMeta.DownloadProgress = new DownloadProgress{
IsDownloading = true,
Percent = dataLog.Percent,
Time = dataLog.Time,
DownloadSpeed = dataLog.DownloadSpeed,
Doing = _isAudio ? "Merging Audio" : (_isVideo ? "Merging Video" : "")
};
if (!QueueManager.Instance.Queue.Contains(_currentEpMeta))
return (false, _data.Parts);
}
}
// Cleanup temp files
Directory.Delete(tempDir, true);
File.Delete(resumeFile);
return (true, _data.Parts);
}
public static Info GetDownloadInfo(long dateStartUnix, int partsDownloaded, int partsTotal, long incrementalBytes, long totalDownloadedBytes){
DateTime lastStart = DateTimeOffset.FromUnixTimeMilliseconds(dateStartUnix).UtcDateTime;
double elapsedMs = (DateTime.UtcNow - lastStart).TotalMilliseconds;
if (elapsedMs <= 0) elapsedMs = 1;
double speed = incrementalBytes / (elapsedMs / 1000);
if (speed < 1) speed = 1;
int percent = (int)((double)partsDownloaded / partsTotal * 100);
if (percent > 100) percent = 100;
double etaSec = 0;
if (partsDownloaded > 0){
double avgPartSize = (double)totalDownloadedBytes / partsDownloaded;
double remainingBytes = avgPartSize * (partsTotal - partsDownloaded);
etaSec = remainingBytes / speed;
}
if (etaSec > TimeSpan.MaxValue.TotalSeconds)
etaSec = TimeSpan.MaxValue.TotalSeconds;
return new Info{ return new Info{
Percent = percent, Percent = percent,
Time = remainingTime, Time = etaSec,
DownloadSpeed = downloadSpeed DownloadSpeed = speed
}; };
} }
@ -333,15 +501,15 @@ public class HlsDownloader{
} }
if (dec != null){ if (dec != null){
_data.BytesDownloaded += dec.Length; Interlocked.Add(ref _data.BytesDownloaded, dec.Length);
_data.TotalBytes += dec.Length; Interlocked.Add(ref _data.TotalBytes, dec.Length);
} }
} else{ } else{
part = await GetData(p, sUri, seg.ByteRange != null ? seg.ByteRange.ToDictionary() : new Dictionary<string, string>(), segOffset, false, _data.Timeout, _data.Retries); part = await GetData(p, sUri, seg.ByteRange != null ? seg.ByteRange.ToDictionary() : new Dictionary<string, string>(), segOffset, false, _data.Timeout, _data.Retries);
dec = part; dec = part;
if (dec != null){ if (dec != null){
_data.BytesDownloaded += dec.Length; Interlocked.Add(ref _data.BytesDownloaded, dec.Length);
_data.TotalBytes += dec.Length; Interlocked.Add(ref _data.TotalBytes, dec.Length);
} }
} }
} catch (Exception ex){ } catch (Exception ex){
@ -455,12 +623,11 @@ public class HlsDownloader{
throw; // rethrow after last retry throw; // rethrow after last retry
await Task.Delay(_data.WaitTime); await Task.Delay(_data.WaitTime);
}catch (Exception ex) { } catch (Exception ex){
Console.Error.WriteLine($"Unexpected exception at part {partIndex + 1 + segOffset}:"); Console.Error.WriteLine($"Unexpected exception at part {partIndex + 1 + segOffset}:");
Console.Error.WriteLine($"\tType: {ex.GetType()}"); Console.Error.WriteLine($"\tType: {ex.GetType()}");
Console.Error.WriteLine($"\tMessage: {ex.Message}"); Console.Error.WriteLine($"\tMessage: {ex.Message}");
throw; throw;
} }
} }
} }
@ -591,11 +758,12 @@ public class Data{
public int Timeout{ get; set; } public int Timeout{ get; set; }
public bool CheckPartLength{ get; set; } public bool CheckPartLength{ get; set; }
public bool IsResume{ get; set; } public bool IsResume{ get; set; }
public long BytesDownloaded{ get; set; }
public int WaitTime{ get; set; } public int WaitTime{ get; set; }
public string? Override{ get; set; } public string? Override{ get; set; }
public long DateStart{ get; set; } public long DateStart{ get; set; }
public long TotalBytes{ get; set; }
public long BytesDownloaded;
public long TotalBytes;
} }
public class PartsData{ public class PartsData{

View file

@ -21,7 +21,6 @@ using CRD.Utils.Files;
using CRD.Utils.HLS; using CRD.Utils.HLS;
using CRD.Utils.JsonConv; using CRD.Utils.JsonConv;
using CRD.Utils.Structs; using CRD.Utils.Structs;
using CRD.Utils.Structs.Crunchyroll;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using Microsoft.Win32; using Microsoft.Win32;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -852,12 +851,43 @@ public class Helpers{
} else{ } else{
throw new PlatformNotSupportedException(); throw new PlatformNotSupportedException();
} }
try{
using (var process = new Process()){
process.StartInfo.FileName = shutdownCmd;
process.StartInfo.Arguments = shutdownArgs;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = true;
Process.Start(new ProcessStartInfo{ process.ErrorDataReceived += (sender, e) => {
FileName = shutdownCmd, if (!string.IsNullOrEmpty(e.Data)){
Arguments = shutdownArgs, Console.Error.WriteLine($"{e.Data}");
CreateNoWindow = true, }
UseShellExecute = false };
});
process.OutputDataReceived += (sender, e) => {
if (!string.IsNullOrEmpty(e.Data)){
Console.Error.WriteLine(e.Data);
}
};
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
if (process.ExitCode != 0){
Console.Error.WriteLine($"Shutdown failed with exit code {process.ExitCode}");
}
}
} catch (Exception ex){
Console.Error.WriteLine($"Failed to start shutdown process: {ex.Message}");
}
} }
} }

View file

@ -32,36 +32,84 @@ public class FontsManager{
#endregion #endregion
public Dictionary<string, List<string>> Fonts{ get; private set; } = new(){ public Dictionary<string, string> Fonts{ get; private set; } = new(){
{ "Adobe Arabic", new List<string>{ "AdobeArabic-Bold.otf" } }, { "Adobe Arabic", "AdobeArabic-Bold.otf" },
{ "Andale Mono", new List<string>{ "andalemo.ttf" } }, { "Andale Mono", "andalemo.ttf" },
{ "Arial", new List<string>{ "arial.ttf", "arialbd.ttf", "arialbi.ttf", "ariali.ttf" } }, { "Arial", "arial.ttf" },
{ "Arial Unicode MS", new List<string>{ "arialuni.ttf" } }, { "Arial Black", "ariblk.ttf" },
{ "Arial Black", new List<string>{ "ariblk.ttf" } }, { "Arial Bold", "arialbd.ttf" },
{ "Comic Sans MS", new List<string>{ "comic.ttf", "comicbd.ttf" } }, { "Arial Bold Italic", "arialbi.ttf" },
{ "Courier New", new List<string>{ "cour.ttf", "courbd.ttf", "courbi.ttf", "couri.ttf" } }, { "Arial Italic", "ariali.ttf" },
{ "DejaVu LGC Sans Mono", new List<string>{ "DejaVuLGCSansMono-Bold.ttf", "DejaVuLGCSansMono-BoldOblique.ttf", "DejaVuLGCSansMono-Oblique.ttf", "DejaVuLGCSansMono.ttf" } }, { "Arial Unicode MS", "arialuni.ttf" },
{ "DejaVu Sans", new List<string>{ "DejaVuSans-Bold.ttf", "DejaVuSans-BoldOblique.ttf", "DejaVuSans-ExtraLight.ttf", "DejaVuSans-Oblique.ttf", "DejaVuSans.ttf" } }, { "Comic Sans MS", "comic.ttf" },
{ "DejaVu Sans Condensed", new List<string>{ "DejaVuSansCondensed-Bold.ttf", "DejaVuSansCondensed-BoldOblique.ttf", "DejaVuSansCondensed-Oblique.ttf", "DejaVuSansCondensed.ttf" } }, { "Comic Sans MS Bold", "comicbd.ttf" },
{ "DejaVu Sans Mono", new List<string>{ "DejaVuSansMono-Bold.ttf", "DejaVuSansMono-BoldOblique.ttf", "DejaVuSansMono-Oblique.ttf", "DejaVuSansMono.ttf" } }, { "Courier New", "cour.ttf" },
{ "Georgia", new List<string>{ "georgia.ttf", "georgiab.ttf", "georgiai.ttf", "georgiaz.ttf" } }, { "Courier New Bold", "courbd.ttf" },
{ "Impact", new List<string>{ "impact.ttf" } }, { "Courier New Bold Italic", "courbi.ttf" },
{ "Rubik Black", new List<string>{ "Rubik-Black.ttf", "Rubik-BlackItalic.ttf" } }, { "Courier New Italic", "couri.ttf" },
{ "Rubik", new List<string>{ "Rubik-Bold.ttf", "Rubik-BoldItalic.ttf", "Rubik-Italic.ttf", "Rubik-Light.ttf", "Rubik-LightItalic.ttf", "Rubik-Medium.ttf", "Rubik-MediumItalic.ttf", "Rubik-Regular.ttf" } }, { "DejaVu LGC Sans Mono", "DejaVuLGCSansMono.ttf" },
{ "Tahoma", new List<string>{ "tahoma.ttf" } }, { "DejaVu LGC Sans Mono Bold", "DejaVuLGCSansMono-Bold.ttf" },
{ "Times New Roman", new List<string>{ "times.ttf", "timesbd.ttf", "timesbi.ttf", "timesi.ttf" } }, { "DejaVu LGC Sans Mono Bold Oblique", "DejaVuLGCSansMono-BoldOblique.ttf" },
{ "Trebuchet MS", new List<string>{ "trebuc.ttf", "trebucbd.ttf", "trebucbi.ttf", "trebucit.ttf" } }, { "DejaVu LGC Sans Mono Oblique", "DejaVuLGCSansMono-Oblique.ttf" },
{ "Verdana", new List<string>{ "verdana.ttf", "verdanab.ttf", "verdanai.ttf", "verdanaz.ttf" } }, { "DejaVu Sans", "DejaVuSans.ttf" },
{ "Vrinda", new List<string>{ "vrinda.ttf", "vrindab.ttf"} }, { "DejaVu Sans Bold", "DejaVuSans-Bold.ttf" },
{ "Webdings", new List<string>{ "webdings.ttf" } }, { "DejaVu Sans Bold Oblique", "DejaVuSans-BoldOblique.ttf" },
{ "DejaVu Sans Condensed", "DejaVuSansCondensed.ttf" },
{ "DejaVu Sans Condensed Bold", "DejaVuSansCondensed-Bold.ttf" },
{ "DejaVu Sans Condensed Bold Oblique", "DejaVuSansCondensed-BoldOblique.ttf" },
{ "DejaVu Sans Condensed Oblique", "DejaVuSansCondensed-Oblique.ttf" },
{ "DejaVu Sans ExtraLight", "DejaVuSans-ExtraLight.ttf" },
{ "DejaVu Sans Mono", "DejaVuSansMono.ttf" },
{ "DejaVu Sans Mono Bold", "DejaVuSansMono-Bold.ttf" },
{ "DejaVu Sans Mono Bold Oblique", "DejaVuSansMono-BoldOblique.ttf" },
{ "DejaVu Sans Mono Oblique", "DejaVuSansMono-Oblique.ttf" },
{ "DejaVu Sans Oblique", "DejaVuSans-Oblique.ttf" },
{ "Gautami", "gautami.ttf" },
{ "Georgia", "georgia.ttf" },
{ "Georgia Bold", "georgiab.ttf" },
{ "Georgia Bold Italic", "georgiaz.ttf" },
{ "Georgia Italic", "georgiai.ttf" },
{ "Impact", "impact.ttf" },
{ "Mangal", "MANGAL.woff2" },
{ "Meera Inimai", "MeeraInimai-Regular.ttf" },
{ "Noto Sans Tamil", "NotoSansTamilVariable.ttf" },
{ "Noto Sans Telugu", "NotoSansTeluguVariable.ttf" },
{ "Noto Sans Thai", "NotoSansThai.ttf" },
{ "Rubik", "Rubik-Regular.ttf" },
{ "Rubik Black", "Rubik-Black.ttf" },
{ "Rubik Black Italic", "Rubik-BlackItalic.ttf" },
{ "Rubik Bold", "Rubik-Bold.ttf" },
{ "Rubik Bold Italic", "Rubik-BoldItalic.ttf" },
{ "Rubik Italic", "Rubik-Italic.ttf" },
{ "Rubik Light", "Rubik-Light.ttf" },
{ "Rubik Light Italic", "Rubik-LightItalic.ttf" },
{ "Rubik Medium", "Rubik-Medium.ttf" },
{ "Rubik Medium Italic", "Rubik-MediumItalic.ttf" },
{ "Tahoma", "tahoma.ttf" },
{ "Times New Roman", "times.ttf" },
{ "Times New Roman Bold", "timesbd.ttf" },
{ "Times New Roman Bold Italic", "timesbi.ttf" },
{ "Times New Roman Italic", "timesi.ttf" },
{ "Trebuchet MS", "trebuc.ttf" },
{ "Trebuchet MS Bold", "trebucbd.ttf" },
{ "Trebuchet MS Bold Italic", "trebucbi.ttf" },
{ "Trebuchet MS Italic", "trebucit.ttf" },
{ "Verdana", "verdana.ttf" },
{ "Verdana Bold", "verdanab.ttf" },
{ "Verdana Bold Italic", "verdanaz.ttf" },
{ "Verdana Italic", "verdanai.ttf" },
{ "Vrinda", "vrinda.ttf" },
{ "Vrinda Bold", "vrindab.ttf" },
{ "Webdings", "webdings.ttf" }
}; };
public string root = "https://static.crunchyroll.com/vilos-v2/web/vilos/assets/libass-fonts/"; public string root = "https://static.crunchyroll.com/vilos-v2/web/vilos/assets/libass-fonts/";
public async Task GetFontsAsync(){ public async Task GetFontsAsync(){
Console.WriteLine("Downloading fonts..."); Console.WriteLine("Downloading fonts...");
var fonts = Fonts.Values.SelectMany(f => f).ToList(); var fonts = Fonts.Values.ToList();
foreach (var font in fonts){ foreach (var font in fonts){
var fontLoc = Path.Combine(CfgManager.PathFONTS_DIR, font); var fontLoc = Path.Combine(CfgManager.PathFONTS_DIR, font);
@ -125,8 +173,8 @@ public class FontsManager{
return styles.Distinct().ToList(); // Using Linq to remove duplicates return styles.Distinct().ToList(); // Using Linq to remove duplicates
} }
public Dictionary<string, List<string>> GetDictFromKeyList(List<string> keysList){ public Dictionary<string, string> GetDictFromKeyList(List<string> keysList){
Dictionary<string, List<string>> filteredDictionary = new Dictionary<string, List<string>>(); Dictionary<string, string> filteredDictionary = new Dictionary<string, string>();
foreach (string key in keysList){ foreach (string key in keysList){
if (Fonts.TryGetValue(key, out var font)){ if (Fonts.TryGetValue(key, out var font)){
@ -148,7 +196,7 @@ public class FontsManager{
} }
public List<ParsedFont> MakeFontsList(string fontsDir, List<SubtitleFonts> subs){ public List<ParsedFont> MakeFontsList(string fontsDir, List<SubtitleFonts> subs){
Dictionary<string, List<string>> fontsNameList = new Dictionary<string, List<string>>(); Dictionary<string, string> fontsNameList = new Dictionary<string, string>();
List<string> subsList = new List<string>(); List<string> subsList = new List<string>();
List<ParsedFont> fontsList = new List<ParsedFont>(); List<ParsedFont> fontsList = new List<ParsedFont>();
bool isNstr = true; bool isNstr = true;
@ -175,13 +223,11 @@ public class FontsManager{
List<string> missingFonts = new List<string>(); List<string> missingFonts = new List<string>();
foreach (var f in fontsNameList){ foreach (var f in fontsNameList){
if (Fonts.TryGetValue(f.Key, out var fontFiles)){ if (Fonts.TryGetValue(f.Key, out var fontFile)){
foreach (var fontFile in fontFiles){ string fontPath = Path.Combine(fontsDir, fontFile);
string fontPath = Path.Combine(fontsDir, fontFile); string mime = GetFontMimeType(fontFile);
string mime = GetFontMimeType(fontFile); if (File.Exists(fontPath) && new FileInfo(fontPath).Length != 0){
if (File.Exists(fontPath) && new FileInfo(fontPath).Length != 0){ fontsList.Add(new ParsedFont{ Name = fontFile, Path = fontPath, Mime = mime });
fontsList.Add(new ParsedFont{ Name = fontFile, Path = fontPath, Mime = mime });
}
} }
} else{ } else{
missingFonts.Add(f.Key); missingFonts.Add(f.Key);
@ -198,5 +244,5 @@ public class FontsManager{
public class SubtitleFonts{ public class SubtitleFonts{
public LanguageItem Language{ get; set; } public LanguageItem Language{ get; set; }
public Dictionary<string, List<string>> Fonts{ get; set; } public Dictionary<string, string> Fonts{ get; set; }
} }

View file

@ -1,13 +1,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml; using System.Xml;
using CRD.Downloader.Crunchyroll;
using CRD.Utils.Files; using CRD.Utils.Files;
using CRD.Utils.Structs; using CRD.Utils.Structs;
@ -264,6 +262,14 @@ public class Merger{
if (options.Description is{ Count: > 0 }){ if (options.Description is{ Count: > 0 }){
args.Add($"--global-tags \"{Helpers.AddUncPrefixIfNeeded(options.Description[0].Path)}\""); args.Add($"--global-tags \"{Helpers.AddUncPrefixIfNeeded(options.Description[0].Path)}\"");
} }
if (options.Cover.Count > 0){
if (File.Exists(options.Cover.First().Path)){
args.Add($"--attach-file \"{options.Cover.First().Path}\"");
args.Add($"--attachment-mime-type image/png");
args.Add($"--attachment-name cover.png");
}
}
return string.Join(" ", args); return string.Join(" ", args);
@ -425,8 +431,11 @@ public class Merger{
.ToList(); .ToList();
allMediaFiles.ForEach(file => Helpers.DeleteFile(file.Path)); allMediaFiles.ForEach(file => Helpers.DeleteFile(file.Path));
allMediaFiles.ForEach(file => Helpers.DeleteFile(file.Path + ".resume")); allMediaFiles.ForEach(file => Helpers.DeleteFile(file.Path + ".resume"));
allMediaFiles.ForEach(file => Helpers.DeleteFile(file.Path + ".new.resume"));
options.Description?.ForEach(description => Helpers.DeleteFile(description.Path)); options.Description?.ForEach(description => Helpers.DeleteFile(description.Path));
options.Cover?.ForEach(cover => Helpers.DeleteFile(cover.Path));
// Delete chapter files if any // Delete chapter files if any
options.Chapters?.ForEach(chapter => Helpers.DeleteFile(chapter.Path)); options.Chapters?.ForEach(chapter => Helpers.DeleteFile(chapter.Path));
@ -471,6 +480,7 @@ public class CrunchyMuxOptions{
public bool Mp4{ get; set; } public bool Mp4{ get; set; }
public bool Mp3{ get; set; } public bool Mp3{ get; set; }
public bool MuxFonts{ get; set; } public bool MuxFonts{ get; set; }
public bool MuxCover{ get; set; }
public bool MuxDescription{ get; set; } public bool MuxDescription{ get; set; }
public string ForceMuxer{ get; set; } public string ForceMuxer{ get; set; }
public bool? NoCleanup{ get; set; } public bool? NoCleanup{ get; set; }
@ -511,6 +521,7 @@ public class MergerOptions{
public bool CcSubsMuxingFlag{ get; set; } public bool CcSubsMuxingFlag{ get; set; }
public bool SignsSubsAsForced{ 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>();
public List<MergerInput> Cover{ get; set; } =[];
} }
public class MuxOptions{ public class MuxOptions{

View file

@ -179,6 +179,7 @@ public class ToM3u8Class{
playlist.attributes = new ExpandoObject(); playlist.attributes = new ExpandoObject();
playlist.attributes.NAME = item.attributes.id; playlist.attributes.NAME = item.attributes.id;
playlist.attributes.BANDWIDTH = item.attributes.bandwidth; playlist.attributes.BANDWIDTH = item.attributes.bandwidth;
playlist.attributes.AUDIOSAMPLINGRATE = item.attributes.audioSamplingRate;
playlist.attributes.CODECS = item.attributes.codecs; playlist.attributes.CODECS = item.attributes.codecs;
playlist.uri = string.Empty; playlist.uri = string.Empty;
playlist.endList = item.attributes.type == "static"; playlist.endList = item.attributes.type == "static";

View file

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
using CRD.Utils.DRM;
using CRD.Utils.HLS; using CRD.Utils.HLS;
using CRD.Utils.Parser; using CRD.Utils.Parser;
using CRD.Utils.Parser.Utils; using CRD.Utils.Parser.Utils;
@ -28,12 +29,15 @@ public class Map{
public class PlaylistItem{ public class PlaylistItem{
public string? pssh{ get; set; } public string? pssh{ get; set; }
public List<ContentKey> encryptionKeys{ get; set; } =[];
public int bandwidth{ get; set; } public int bandwidth{ get; set; }
public List<Segment> segments{ get; set; } public List<Segment> segments{ get; set; }
} }
public class AudioPlaylist : PlaylistItem{ public class AudioPlaylist : PlaylistItem{
public LanguageItem? language{ get; set; } public LanguageItem? language{ get; set; }
public int audioSamplingRate{ get; set; }
public bool @default{ get; set; } public bool @default{ get; set; }
} }
@ -92,6 +96,7 @@ public static class MPDParser{
var pItem = new AudioPlaylist{ var pItem = new AudioPlaylist{
bandwidth = playlist.attributes.BANDWIDTH, bandwidth = playlist.attributes.BANDWIDTH,
audioSamplingRate = ObjectUtilities.GetMemberValue(playlist.attributes ,"AUDIOSAMPLINGRATE") ?? 0,
language = audioLang, language = audioLang,
@default = item.@default, @default = item.@default,
segments = segments.Select(segment => new Segment{ segments = segments.Select(segment => new Segment{

View file

@ -18,6 +18,7 @@ public class ParseAttribute{
{ "width", Width }, { "width", Width },
{ "height", Height }, { "height", Height },
{ "bandwidth", Bandwidth }, { "bandwidth", Bandwidth },
{ "audioSamplingRate", AudioSamplingRate },
{ "frameRate", FrameRate }, { "frameRate", FrameRate },
{ "startNumber", StartNumber }, { "startNumber", StartNumber },
{ "timescale", Timescale }, { "timescale", Timescale },
@ -40,6 +41,7 @@ public class ParseAttribute{
public static object Width(string value) => int.Parse(value); public static object Width(string value) => int.Parse(value);
public static object Height(string value) => int.Parse(value); public static object Height(string value) => int.Parse(value);
public static object Bandwidth(string value) => int.Parse(value); public static object Bandwidth(string value) => int.Parse(value);
public static object AudioSamplingRate(string value) => int.Parse(value);
public static object FrameRate(string value) => DivisionValueParser.ParseDivisionValue(value); public static object FrameRate(string value) => DivisionValueParser.ParseDivisionValue(value);
public static object StartNumber(string value) => int.Parse(value); public static object StartNumber(string value) => int.Parse(value);
public static object Timescale(string value) => int.Parse(value); public static object Timescale(string value) => int.Parse(value);

View file

@ -2,7 +2,6 @@
using CRD.Utils.Sonarr; using CRD.Utils.Sonarr;
using CRD.ViewModels; using CRD.ViewModels;
using Newtonsoft.Json; using Newtonsoft.Json;
using YamlDotNet.Serialization;
namespace CRD.Utils.Structs.Crunchyroll; namespace CRD.Utils.Structs.Crunchyroll;
@ -29,6 +28,9 @@ public class CrDownloadOptions{
[JsonIgnore] [JsonIgnore]
public string Force{ get; set; } = ""; public string Force{ get; set; } = "";
[JsonProperty("download_methode_new")]
public bool DownloadMethodeNew{ get; set; }
[JsonProperty("simultaneous_downloads")] [JsonProperty("simultaneous_downloads")]
public int SimultaneousDownloads{ get; set; } public int SimultaneousDownloads{ get; set; }
@ -208,6 +210,9 @@ public class CrDownloadOptions{
[JsonProperty("mux_fonts")] [JsonProperty("mux_fonts")]
public bool MuxFonts{ get; set; } public bool MuxFonts{ get; set; }
[JsonProperty("mux_cover")]
public bool MuxCover{ get; set; }
[JsonProperty("mux_video_title")] [JsonProperty("mux_video_title")]
public string? VideoTitle{ get; set; } public string? VideoTitle{ get; set; }

View file

@ -85,7 +85,7 @@ public class CrunchyMovie{
public string Description{ get; set; } public string Description{ get; set; }
public Images? Images{ get; set; } public Images Images{ get; set; } = new();
[JsonProperty("media_type")] [JsonProperty("media_type")]
public string? MediaType{ get; set; } public string? MediaType{ get; set; }

View file

@ -54,7 +54,7 @@ public class CrunchyEpisode : IHistorySource{
[JsonProperty("availability_starts")] [JsonProperty("availability_starts")]
public DateTime AvailabilityStarts{ get; set; } public DateTime AvailabilityStarts{ get; set; }
public Images? Images{ get; set; } public Images Images{ get; set; } = new();
[JsonProperty("season_id")] [JsonProperty("season_id")]
public string SeasonId{ get; set; } public string SeasonId{ get; set; }
@ -289,7 +289,7 @@ public class CrunchyEpisode : IHistorySource{
public EpisodeType GetEpisodeType(){ public EpisodeType GetEpisodeType(){
return EpisodeType; return EpisodeType;
} }
public string GetImageUrl(){ public string GetImageUrl(){
if (Images != null){ if (Images != null){
return Images.Thumbnail?.First().First().Source ?? string.Empty; return Images.Thumbnail?.First().First().Source ?? string.Empty;
@ -303,15 +303,15 @@ public class CrunchyEpisode : IHistorySource{
public class Images{ public class Images{
[JsonProperty("poster_tall")] [JsonProperty("poster_tall")]
public List<List<Image>>? PosterTall{ get; set; } public List<List<Image>> PosterTall{ get; set; } =[];
[JsonProperty("poster_wide")] [JsonProperty("poster_wide")]
public List<List<Image>>? PosterWide{ get; set; } public List<List<Image>> PosterWide{ get; set; } =[];
[JsonProperty("promo_image")] [JsonProperty("promo_image")]
public List<List<Image>>? PromoImage{ get; set; } public List<List<Image>> PromoImage{ get; set; } =[];
public List<List<Image>>? Thumbnail{ get; set; } public List<List<Image>> Thumbnail{ get; set; } =[];
} }
public class Image{ public class Image{
@ -368,6 +368,7 @@ public class CrunchyEpMeta{
public string? SeriesId{ get; set; } public string? SeriesId{ get; set; }
public string? AbsolutEpisodeNumberE{ get; set; } public string? AbsolutEpisodeNumberE{ get; set; }
public string? Image{ get; set; } public string? Image{ get; set; }
public string? ImageBig{ get; set; }
public bool Paused{ get; set; } public bool Paused{ get; set; }
public DownloadProgress DownloadProgress{ get; set; } = new(); public DownloadProgress DownloadProgress{ get; set; } = new();
@ -389,7 +390,6 @@ public class CrunchyEpMeta{
public bool OnlySubs{ get; set; } public bool OnlySubs{ get; set; }
public CrDownloadOptions? DownloadSettings; public CrDownloadOptions? DownloadSettings;
} }
public class DownloadProgress{ public class DownloadProgress{
@ -410,7 +410,7 @@ public class CrunchyEpMetaData{
public List<EpisodeVersion>? Versions{ get; set; } public List<EpisodeVersion>? Versions{ get; set; }
public bool IsSubbed{ get; set; } public bool IsSubbed{ get; set; }
public bool IsDubbed{ get; set; } public bool IsDubbed{ get; set; }
public (string? seasonID, string? guid) GetOriginalIds(){ public (string? seasonID, string? guid) GetOriginalIds(){
var version = Versions?.FirstOrDefault(a => a.Original); var version = Versions?.FirstOrDefault(a => a.Original);
if (version != null && !string.IsNullOrEmpty(version.Guid) && !string.IsNullOrEmpty(version.SeasonGuid)){ if (version != null && !string.IsNullOrEmpty(version.Guid) && !string.IsNullOrEmpty(version.SeasonGuid)){
@ -419,7 +419,6 @@ public class CrunchyEpMetaData{
return (null, null); return (null, null);
} }
} }
public class CrunchyRollEpisodeData{ public class CrunchyRollEpisodeData{

View file

@ -97,7 +97,7 @@ public class SxItem{
public string? Path{ get; set; } public string? Path{ get; set; }
public string? File{ get; set; } public string? File{ get; set; }
public string? Title{ get; set; } public string? Title{ get; set; }
public Dictionary<string, List<string>>? Fonts{ get; set; } public Dictionary<string, string>? Fonts{ get; set; }
} }
public class FrameData{ public class FrameData{

View file

@ -10,9 +10,7 @@ using Avalonia.Media.Imaging;
using CRD.Downloader.Crunchyroll; using CRD.Downloader.Crunchyroll;
using CRD.Utils.CustomList; using CRD.Utils.CustomList;
using CRD.Utils.Files; using CRD.Utils.Files;
using CRD.Views;
using Newtonsoft.Json; using Newtonsoft.Json;
using ReactiveUI;
namespace CRD.Utils.Structs.History; namespace CRD.Utils.Structs.History;

View file

@ -4,7 +4,6 @@ using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using CRD.Downloader;
using CRD.Downloader.Crunchyroll; using CRD.Downloader.Crunchyroll;
using CRD.Utils.Structs.Crunchyroll.Music; using CRD.Utils.Structs.Crunchyroll.Music;
using CRD.Utils.Structs.History; using CRD.Utils.Structs.History;

View file

@ -3,9 +3,6 @@ using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using CRD.Downloader.Crunchyroll; using CRD.Downloader.Crunchyroll;

View file

@ -35,7 +35,7 @@ public partial class GeneralSettingsViewModel : ViewModelBase{
[ObservableProperty] [ObservableProperty]
private bool _history; private bool _history;
[ObservableProperty] [ObservableProperty]
private bool _historyCountMissing; private bool _historyCountMissing;
@ -54,12 +54,15 @@ public partial class GeneralSettingsViewModel : ViewModelBase{
[ObservableProperty] [ObservableProperty]
private double? _simultaneousDownloads; private double? _simultaneousDownloads;
[ObservableProperty]
private bool _downloadMethodeNew;
[ObservableProperty] [ObservableProperty]
private double? _downloadSpeed; private double? _downloadSpeed;
[ObservableProperty] [ObservableProperty]
private double? _retryAttempts; private double? _retryAttempts;
[ObservableProperty] [ObservableProperty]
private double? _retryDelay; private double? _retryDelay;
@ -268,8 +271,9 @@ public partial class GeneralSettingsViewModel : ViewModelBase{
HistorySkipUnmonitored = options.HistorySkipUnmonitored; HistorySkipUnmonitored = options.HistorySkipUnmonitored;
HistoryCountSonarr = options.HistoryCountSonarr; HistoryCountSonarr = options.HistoryCountSonarr;
DownloadSpeed = options.DownloadSpeedLimit; DownloadSpeed = options.DownloadSpeedLimit;
RetryAttempts = Math.Clamp((options.RetryAttempts), 1, 10); DownloadMethodeNew = options.DownloadMethodeNew;
RetryDelay = Math.Clamp((options.RetryDelay), 1, 30); RetryAttempts = Math.Clamp((options.RetryAttempts), 1, 10);
RetryDelay = Math.Clamp((options.RetryDelay), 1, 30);
DownloadToTempFolder = options.DownloadToTempFolder; DownloadToTempFolder = options.DownloadToTempFolder;
SimultaneousDownloads = options.SimultaneousDownloads; SimultaneousDownloads = options.SimultaneousDownloads;
LogMode = options.LogMode; LogMode = options.LogMode;
@ -292,12 +296,14 @@ public partial class GeneralSettingsViewModel : ViewModelBase{
} }
var settings = CrunchyrollManager.Instance.CrunOptions; var settings = CrunchyrollManager.Instance.CrunOptions;
settings.DownloadFinishedPlaySound = DownloadFinishedPlaySound; settings.DownloadFinishedPlaySound = DownloadFinishedPlaySound;
settings.DownloadMethodeNew = DownloadMethodeNew;
settings.BackgroundImageBlurRadius = Math.Clamp((BackgroundImageBlurRadius ?? 0), 0, 40); settings.BackgroundImageBlurRadius = Math.Clamp((BackgroundImageBlurRadius ?? 0), 0, 40);
settings.BackgroundImageOpacity = Math.Clamp((BackgroundImageOpacity ?? 0), 0, 1); settings.BackgroundImageOpacity = Math.Clamp((BackgroundImageOpacity ?? 0), 0, 1);
settings.RetryAttempts = Math.Clamp((int)(RetryAttempts ?? 0), 1, 10); settings.RetryAttempts = Math.Clamp((int)(RetryAttempts ?? 0), 1, 10);
settings.RetryDelay = Math.Clamp((int)(RetryDelay ?? 0), 1, 30); settings.RetryDelay = Math.Clamp((int)(RetryDelay ?? 0), 1, 30);

View file

@ -1,6 +1,4 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Interactivity;
using CRD.ViewModels;
namespace CRD.Views; namespace CRD.Views;

View file

@ -1,7 +1,5 @@
using System.Linq; using Avalonia.Controls;
using Avalonia.Controls;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.VisualTree;
using CRD.Utils.Sonarr; using CRD.Utils.Sonarr;
using CRD.ViewModels; using CRD.ViewModels;

View file

@ -1,7 +1,4 @@
using Avalonia; using Avalonia.Controls;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using CRD.Utils.UI;
namespace CRD.Views.Utils; namespace CRD.Views.Utils;

View file

@ -1,7 +1,4 @@
using Avalonia; using Avalonia.Controls;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using CRD.Utils.UI;
namespace CRD.Views.Utils; namespace CRD.Views.Utils;

View file

@ -32,7 +32,7 @@
</ComboBox> </ComboBox>
</controls:SettingsExpanderItem.Footer> </controls:SettingsExpanderItem.Footer>
</controls:SettingsExpanderItem> </controls:SettingsExpanderItem>
<controls:SettingsExpanderItem Content="History count Missing" Description="The missing count (orange corner) shows all missing episodes, not just new ones"> <controls:SettingsExpanderItem Content="History count Missing" Description="The missing count (orange corner) shows all missing episodes, not just new ones">
<controls:SettingsExpanderItem.Footer> <controls:SettingsExpanderItem.Footer>
<CheckBox IsChecked="{Binding HistoryCountMissing}"> </CheckBox> <CheckBox IsChecked="{Binding HistoryCountMissing}"> </CheckBox>
@ -70,6 +70,12 @@
Description="Adjust download settings" Description="Adjust download settings"
IsExpanded="False"> IsExpanded="False">
<controls:SettingsExpanderItem Content="Enable New Download Method" Description="Enables the updated download handling logic. This may improve performance and stability.">
<controls:SettingsExpanderItem.Footer>
<CheckBox IsChecked="{Binding DownloadMethodeNew}"> </CheckBox>
</controls:SettingsExpanderItem.Footer>
</controls:SettingsExpanderItem>
<controls:SettingsExpanderItem Content="Max Download Speed" <controls:SettingsExpanderItem Content="Max Download Speed"
Description="Download in Kb/s - 0 is full speed"> Description="Download in Kb/s - 0 is full speed">
<controls:SettingsExpanderItem.Footer> <controls:SettingsExpanderItem.Footer>
@ -595,13 +601,7 @@
VerticalAlignment="Center" VerticalAlignment="Center"
DockPanel.Dock="Left" /> DockPanel.Dock="Left" />
<controls:ColorPickerButton Color="{Binding CustomAccentColor}" <ColorPicker Color="{Binding CustomAccentColor}"
IsMoreButtonVisible="True"
UseSpectrum="True"
UseColorWheel="False"
UseColorTriangle="False"
UseColorPalette="False"
IsCompact="True" ShowAcceptDismissButtons="True"
DockPanel.Dock="Right" /> DockPanel.Dock="Right" />
</DockPanel> </DockPanel>
</StackPanel> </StackPanel>

View file

@ -49,7 +49,7 @@ For detailed information on each feature, please refer to the [GitHub Wiki](http
Place one of the following tools in the `./lib/` directory: Place one of the following tools in the `./lib/` directory:
- **mp4decrypt:** Available at [Bento4](http://www.bento4.com/) - **mp4decrypt:** Available at [Bento4](https://www.bento4.com/downloads/)
- **shaka-packager:** Available at [Shaka Packager Releases](https://github.com/shaka-project/shaka-packager/releases/latest) - **shaka-packager:** Available at [Shaka Packager Releases](https://github.com/shaka-project/shaka-packager/releases/latest)
### 2. Acquire Widevine CDM Files ### 2. Acquire Widevine CDM Files