mirror of
https://github.com/Crunchy-DL/Crunchy-Downloader.git
synced 2026-01-11 20:10:26 +00:00
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:
parent
65200147a0
commit
e80568cbb0
33 changed files with 522 additions and 161 deletions
|
|
@ -173,7 +173,7 @@ public class CrEpisode(){
|
|||
}
|
||||
|
||||
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\)");
|
||||
|
||||
|
|
@ -190,6 +190,7 @@ public class CrEpisode(){
|
|||
epMeta.SeriesId = item.SeriesId;
|
||||
epMeta.AbsolutEpisodeNumberE = epNum;
|
||||
epMeta.Image = images[images.Count / 2].FirstOrDefault()?.Source;
|
||||
epMeta.ImageBig = images[images.Count / 2].LastOrDefault()?.Source;
|
||||
epMeta.DownloadProgress = new DownloadProgress(){
|
||||
IsDownloading = false,
|
||||
Done = false,
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ public class CrMovies{
|
|||
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();
|
||||
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.AbsolutEpisodeNumberE = "";
|
||||
epMeta.Image = images[images.Count / 2].FirstOrDefault()?.Source;
|
||||
epMeta.ImageBig = images[images.Count / 2].LastOrDefault()?.Source;
|
||||
epMeta.DownloadProgress = new DownloadProgress(){
|
||||
IsDownloading = false,
|
||||
Done = false,
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ public class CrMusic{
|
|||
|
||||
|
||||
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();
|
||||
|
|
@ -179,6 +179,7 @@ public class CrMusic{
|
|||
epMeta.SeriesId = episodeP.GetSeriesId();
|
||||
epMeta.AbsolutEpisodeNumberE = "";
|
||||
epMeta.Image = images[images.Count / 2].Source;
|
||||
epMeta.ImageBig = images[images.Count / 2].Source;
|
||||
epMeta.DownloadProgress = new DownloadProgress(){
|
||||
IsDownloading = false,
|
||||
Done = false,
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ public class CrSeries{
|
|||
}
|
||||
|
||||
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\)");
|
||||
|
||||
|
|
@ -71,6 +71,7 @@ public class CrSeries{
|
|||
epMeta.SeriesId = item.SeriesId;
|
||||
epMeta.AbsolutEpisodeNumberE = epNum;
|
||||
epMeta.Image = images[images.Count / 2].FirstOrDefault()?.Source ?? "";
|
||||
epMeta.ImageBig = images[images.Count / 2].LastOrDefault()?.Source;
|
||||
epMeta.DownloadProgress = new DownloadProgress(){
|
||||
IsDownloading = false,
|
||||
Done = false,
|
||||
|
|
@ -265,7 +266,7 @@ public class CrSeries{
|
|||
crunchySeriesList.List = sortedEpisodes.Select(kvp => {
|
||||
var key = kvp.Key;
|
||||
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 langList = value.Langs.Select(a => a.CrLocale).ToList();
|
||||
Languages.SortListByLangList(langList);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.InteropServices;
|
||||
|
|
@ -108,8 +106,8 @@ public class CrunchyrollManager{
|
|||
options.Partsize = 10;
|
||||
options.DlSubs = new List<string>{ "en-US" };
|
||||
options.SkipMuxing = false;
|
||||
options.MkvmergeOptions = new List<string>{ "--no-date", "--disable-track-statistics-tags", "--engage no_variable_data" };
|
||||
options.FfmpegOptions = new();
|
||||
options.MkvmergeOptions =[];
|
||||
options.FfmpegOptions =[];
|
||||
options.DefaultAudio = "ja-JP";
|
||||
options.DefaultSub = "en-US";
|
||||
options.QualityAudio = "best";
|
||||
|
|
@ -339,6 +337,7 @@ public class CrunchyrollManager{
|
|||
Mp4 = options.Mp4,
|
||||
Mp3 = options.AudioOnlyToMp3,
|
||||
MuxFonts = options.MuxFonts,
|
||||
MuxCover = options.MuxCover,
|
||||
VideoTitle = res.VideoTitle,
|
||||
Novids = options.Novids,
|
||||
NoCleanup = options.Nocleanup,
|
||||
|
|
@ -405,6 +404,7 @@ public class CrunchyrollManager{
|
|||
Mp4 = options.Mp4,
|
||||
Mp3 = options.AudioOnlyToMp3,
|
||||
MuxFonts = options.MuxFonts,
|
||||
MuxCover = options.MuxCover,
|
||||
VideoTitle = res.VideoTitle,
|
||||
Novids = options.Novids,
|
||||
NoCleanup = options.Nocleanup,
|
||||
|
|
@ -520,8 +520,6 @@ public class CrunchyrollManager{
|
|||
if (CrunOptions.ShutdownWhenQueueEmpty){
|
||||
Helpers.ShutdownComputer();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
@ -678,6 +676,7 @@ public class CrunchyrollManager{
|
|||
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() :[],
|
||||
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)){
|
||||
|
|
@ -1291,6 +1290,7 @@ public class CrunchyrollManager{
|
|||
pssh = item.pssh,
|
||||
language = item.language,
|
||||
bandwidth = item.bandwidth,
|
||||
audioSamplingRate = item.audioSamplingRate,
|
||||
resolutionText = $"{Math.Round(item.bandwidth / 1000.0)}kB/s"
|
||||
}).ToList();
|
||||
|
||||
|
|
@ -1307,6 +1307,7 @@ public class CrunchyrollManager{
|
|||
.GroupBy(a => new{ a.bandwidth, a.language }) // Add more properties if needed
|
||||
.Select(g => g.First())
|
||||
.OrderBy(a => a.bandwidth)
|
||||
.ThenBy(a => a.audioSamplingRate)
|
||||
.ToList();
|
||||
|
||||
if (string.IsNullOrEmpty(data.VideoQuality)){
|
||||
|
|
@ -1375,7 +1376,7 @@ public class CrunchyrollManager{
|
|||
|
||||
Console.WriteLine("Available Audio Qualities:");
|
||||
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));
|
||||
|
|
@ -1396,7 +1397,7 @@ public class CrunchyrollManager{
|
|||
|
||||
Console.WriteLine($"Selected quality:");
|
||||
Console.WriteLine($"\tVideo: {chosenVideoSegments.resolutionText}");
|
||||
Console.WriteLine($"\tAudio: {chosenAudioSegments.resolutionText}");
|
||||
Console.WriteLine($"\tAudio: {chosenAudioSegments.resolutionText} / {chosenAudioSegments.audioSamplingRate}");
|
||||
Console.WriteLine($"\tServer: {selectedServer}");
|
||||
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){
|
||||
Console.WriteLine("Skipping video download...");
|
||||
} 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);
|
||||
|
||||
tsFile = videoDownloadResult.tsFile;
|
||||
|
|
@ -1467,6 +1482,20 @@ public class CrunchyrollManager{
|
|||
|
||||
|
||||
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);
|
||||
|
||||
tsFile = audioDownloadResult.tsFile;
|
||||
|
|
@ -1517,20 +1546,25 @@ public class CrunchyrollManager{
|
|||
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 } };
|
||||
|
||||
var encryptionKeys = await _widevine.getKeys(chosenVideoSegments.pssh, ApiUrls.WidevineLicenceUrl, authDataDict);
|
||||
var encryptionKeys = chosenVideoSegments.encryptionKeys;
|
||||
|
||||
if (encryptionKeys.Count == 0){
|
||||
Console.Error.WriteLine("Failed to get encryption keys");
|
||||
dlFailed = true;
|
||||
return new DownloadResponse{
|
||||
Data = files,
|
||||
Error = dlFailed,
|
||||
FileName = fileName.Length > 0 ? (Path.IsPathRooted(fileName) ? fileName : Path.Combine(fileDir, fileName)) : "./unknown",
|
||||
ErrorText = "Couldn't get DRM encryption keys"
|
||||
};
|
||||
encryptionKeys = await _widevine.getKeys(chosenVideoSegments.pssh, ApiUrls.WidevineLicenceUrl, authDataDict);
|
||||
|
||||
if (encryptionKeys.Count == 0){
|
||||
Console.Error.WriteLine("Failed to get encryption keys");
|
||||
dlFailed = true;
|
||||
return new DownloadResponse{
|
||||
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)){
|
||||
Console.WriteLine("Video and Audio PSSH different requesting Audio encryption keys");
|
||||
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.");
|
||||
}
|
||||
|
||||
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 = "";
|
||||
if (options.DownloadToTempFolder){
|
||||
|
|
@ -1928,7 +1983,6 @@ public class CrunchyrollManager{
|
|||
var isCc = subsItem.isCC;
|
||||
var isDuplicate = false;
|
||||
|
||||
|
||||
|
||||
if ((!options.IncludeSignsSubs && isSigns) || (!options.IncludeCcSubs && isCc)){
|
||||
continue;
|
||||
|
|
@ -1945,8 +1999,9 @@ public class CrunchyrollManager{
|
|||
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);
|
||||
|
||||
Helpers.EnsureDirectoriesExist(sxData.Path);
|
||||
|
|
@ -1962,7 +2017,6 @@ public class CrunchyrollManager{
|
|||
|
||||
if (subsAssReqResponse.IsOk){
|
||||
if (subsItem.format == "ass"){
|
||||
subsAssReqResponse.ResponseContent = '\ufeff' + subsAssReqResponse.ResponseContent;
|
||||
var sBodySplit = subsAssReqResponse.ResponseContent.Split(new[]{ "\r\n" }, StringSplitOptions.None).ToList();
|
||||
|
||||
if (sBodySplit.Count > 2){
|
||||
|
|
@ -1999,12 +2053,12 @@ public class CrunchyrollManager{
|
|||
|
||||
assBuilder.AppendLine();
|
||||
assBuilder.AppendLine("[V4+ Styles]");
|
||||
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");
|
||||
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");
|
||||
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("[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
|
||||
string normalizedContent = subsAssReqResponse.ResponseContent.Replace("\r\n", "\n").Replace("\r", "\n");
|
||||
|
|
@ -2100,10 +2154,22 @@ public class CrunchyrollManager{
|
|||
FsRetryTime = options.RetryDelay * 1000,
|
||||
Retries = options.RetryAttempts,
|
||||
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);
|
||||
}
|
||||
|
|
@ -2158,10 +2224,21 @@ public class CrunchyrollManager{
|
|||
FsRetryTime = options.RetryDelay * 1000,
|
||||
Retries = options.RetryAttempts,
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,9 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
|
||||
[ObservableProperty]
|
||||
private bool _muxFonts;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _muxCover;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _syncTimings;
|
||||
|
|
@ -359,6 +362,7 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
MuxToMp4 = options.Mp4;
|
||||
MuxToMp3 = options.AudioOnlyToMp3;
|
||||
MuxFonts = options.MuxFonts;
|
||||
MuxCover = options.MuxCover;
|
||||
SyncTimings = options.SyncTiming;
|
||||
SkipSubMux = options.SkipSubsMux;
|
||||
LeadingNumbers = options.Numbers;
|
||||
|
|
@ -428,6 +432,7 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
CrunchyrollManager.Instance.CrunOptions.Mp4 = MuxToMp4;
|
||||
CrunchyrollManager.Instance.CrunOptions.AudioOnlyToMp3 = MuxToMp3;
|
||||
CrunchyrollManager.Instance.CrunOptions.MuxFonts = MuxFonts;
|
||||
CrunchyrollManager.Instance.CrunOptions.MuxCover = MuxCover;
|
||||
CrunchyrollManager.Instance.CrunOptions.SyncTiming = SyncTimings;
|
||||
CrunchyrollManager.Instance.CrunOptions.SkipSubsMux = SkipSubMux;
|
||||
CrunchyrollManager.Instance.CrunOptions.Numbers = Math.Clamp((int)(LeadingNumbers ?? 0), 0, 10);
|
||||
|
|
|
|||
|
|
@ -411,6 +411,12 @@
|
|||
<CheckBox IsChecked="{Binding MuxFonts}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</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"
|
||||
Description="${seriesTitle} ${seasonTitle} ${title} ${season} ${episode} ${height} ${width} ${dubs}">
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ using CRD.Utils;
|
|||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.History;
|
||||
using CRD.Utils.Updater;
|
||||
using ExtendedXmlSerializer.Core.Sources;
|
||||
using FluentAvalonia.Styling;
|
||||
|
||||
namespace CRD.Downloader;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ using CRD.Downloader.Crunchyroll;
|
|||
using CRD.Utils;
|
||||
using CRD.Utils.CustomList;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.Crunchyroll;
|
||||
using CRD.Utils.Structs.History;
|
||||
using CRD.ViewModels;
|
||||
using CRD.Views;
|
||||
|
|
@ -182,12 +181,12 @@ public partial class QueueManager : ObservableObject{
|
|||
case EpisodeDownloadMode.OnlyVideo:
|
||||
newOptions.Novids = false;
|
||||
newOptions.Noaudio = true;
|
||||
selected.DownloadSubs = ["none"];
|
||||
selected.DownloadSubs =["none"];
|
||||
break;
|
||||
case EpisodeDownloadMode.OnlyAudio:
|
||||
newOptions.Novids = true;
|
||||
newOptions.Noaudio = false;
|
||||
selected.DownloadSubs = ["none"];
|
||||
selected.DownloadSubs =["none"];
|
||||
break;
|
||||
case EpisodeDownloadMode.OnlySubs:
|
||||
newOptions.Novids = true;
|
||||
|
|
@ -250,12 +249,12 @@ public partial class QueueManager : ObservableObject{
|
|||
case EpisodeDownloadMode.OnlyVideo:
|
||||
newOptions.Novids = false;
|
||||
newOptions.Noaudio = true;
|
||||
movieMeta.DownloadSubs = ["none"];
|
||||
movieMeta.DownloadSubs =["none"];
|
||||
break;
|
||||
case EpisodeDownloadMode.OnlyAudio:
|
||||
newOptions.Novids = true;
|
||||
newOptions.Noaudio = false;
|
||||
movieMeta.DownloadSubs = ["none"];
|
||||
movieMeta.DownloadSubs =["none"];
|
||||
break;
|
||||
case EpisodeDownloadMode.OnlySubs:
|
||||
newOptions.Novids = true;
|
||||
|
|
@ -345,7 +344,9 @@ public partial class QueueManager : ObservableObject{
|
|||
public async Task CrAddSeriesToQueue(CrunchySeriesList list, CrunchyMultiDownload data){
|
||||
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()){
|
||||
if (crunchyEpMeta.Data?.First() != null){
|
||||
|
|
@ -411,15 +412,30 @@ public partial class QueueManager : ObservableObject{
|
|||
|
||||
|
||||
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{
|
||||
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?");
|
||||
} else{
|
||||
} else if (selected.Values.Count > 0 && !partialAdd){
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.IsolatedStorage;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
|
|
|||
|
|
@ -182,6 +182,9 @@ public enum DownloadMediaType{
|
|||
|
||||
[EnumMember(Value = "Description")]
|
||||
Description,
|
||||
|
||||
[EnumMember(Value = "Cover")]
|
||||
Cover,
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
|
|
|
|||
|
|
@ -4,13 +4,8 @@ using System.IO;
|
|||
using System.IO.Compression;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils.Structs.Crunchyroll;
|
||||
using Newtonsoft.Json;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
|
||||
namespace CRD.Utils.Files;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.IO;
|
|||
using System.Net.Http;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CRD.Downloader;
|
||||
using CRD.Utils.Parser.Utils;
|
||||
|
|
@ -18,8 +19,9 @@ public class HlsDownloader{
|
|||
private CrunchyEpMeta _currentEpMeta;
|
||||
private bool _isVideo;
|
||||
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){
|
||||
throw new Exception("Playlist is empty");
|
||||
}
|
||||
|
|
@ -29,6 +31,8 @@ public class HlsDownloader{
|
|||
_isVideo = isVideo;
|
||||
_isAudio = isAudio;
|
||||
|
||||
_newDownloadMethode = newDownloadMethode;
|
||||
|
||||
if (options?.M3U8Json != null)
|
||||
_data = new Data{
|
||||
Parts = new PartsData{
|
||||
|
|
@ -152,6 +156,11 @@ public class HlsDownloader{
|
|||
_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++){
|
||||
// Start time
|
||||
_data.DateStart = DateTimeOffset.Now.ToUnixTimeMilliseconds();
|
||||
|
|
@ -271,6 +280,7 @@ public class HlsDownloader{
|
|||
File.Delete(downloadItemDownloadedFile);
|
||||
}
|
||||
} catch (Exception e){
|
||||
Console.Error.WriteLine(e.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -292,24 +302,182 @@ public class HlsDownloader{
|
|||
return (Ok: true, _data.Parts);
|
||||
}
|
||||
|
||||
public static Info GetDownloadInfo(long dateStartUnix, int partsDownloaded, int partsTotal, long downloadedBytes, long totalDownloadedBytes){
|
||||
// Convert Unix timestamp to DateTime
|
||||
DateTime dateStart = DateTimeOffset.FromUnixTimeMilliseconds(dateStartUnix).UtcDateTime;
|
||||
double dateElapsed = (DateTime.UtcNow - dateStart).TotalMilliseconds;
|
||||
private static readonly object _resumeLock = new object();
|
||||
|
||||
// Calculate percentage
|
||||
int percentFixed = (int)((double)partsDownloaded / partsTotal * 100);
|
||||
int percent = percentFixed < 100 ? percentFixed : (partsTotal == partsDownloaded ? 100 : 99);
|
||||
public async Task<(bool Ok, PartsData Parts)> DownloadSegmentsBufferedResumeAsync(List<dynamic> segments, string fn){
|
||||
var totalSeg = _data.Parts.Total;
|
||||
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;
|
||||
double remainingTime = (partsLeft * ((double)totalDownloadedBytes / partsDownloaded)) / downloadSpeed;
|
||||
string resumeFile = $"{fn}.new.resume";
|
||||
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{
|
||||
Percent = percent,
|
||||
Time = remainingTime,
|
||||
DownloadSpeed = downloadSpeed
|
||||
Time = etaSec,
|
||||
DownloadSpeed = speed
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -333,15 +501,15 @@ public class HlsDownloader{
|
|||
}
|
||||
|
||||
if (dec != null){
|
||||
_data.BytesDownloaded += dec.Length;
|
||||
_data.TotalBytes += dec.Length;
|
||||
Interlocked.Add(ref _data.BytesDownloaded, dec.Length);
|
||||
Interlocked.Add(ref _data.TotalBytes, dec.Length);
|
||||
}
|
||||
} else{
|
||||
part = await GetData(p, sUri, seg.ByteRange != null ? seg.ByteRange.ToDictionary() : new Dictionary<string, string>(), segOffset, false, _data.Timeout, _data.Retries);
|
||||
dec = part;
|
||||
if (dec != null){
|
||||
_data.BytesDownloaded += dec.Length;
|
||||
_data.TotalBytes += dec.Length;
|
||||
Interlocked.Add(ref _data.BytesDownloaded, dec.Length);
|
||||
Interlocked.Add(ref _data.TotalBytes, dec.Length);
|
||||
}
|
||||
}
|
||||
} catch (Exception ex){
|
||||
|
|
@ -455,12 +623,11 @@ public class HlsDownloader{
|
|||
throw; // rethrow after last retry
|
||||
|
||||
await Task.Delay(_data.WaitTime);
|
||||
}catch (Exception ex) {
|
||||
|
||||
} catch (Exception ex){
|
||||
Console.Error.WriteLine($"Unexpected exception at part {partIndex + 1 + segOffset}:");
|
||||
Console.Error.WriteLine($"\tType: {ex.GetType()}");
|
||||
Console.Error.WriteLine($"\tMessage: {ex.Message}");
|
||||
throw;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -591,11 +758,12 @@ public class Data{
|
|||
public int Timeout{ get; set; }
|
||||
public bool CheckPartLength{ get; set; }
|
||||
public bool IsResume{ get; set; }
|
||||
public long BytesDownloaded{ get; set; }
|
||||
public int WaitTime{ get; set; }
|
||||
public string? Override{ get; set; }
|
||||
public long DateStart{ get; set; }
|
||||
public long TotalBytes{ get; set; }
|
||||
|
||||
public long BytesDownloaded;
|
||||
public long TotalBytes;
|
||||
}
|
||||
|
||||
public class PartsData{
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ using CRD.Utils.Files;
|
|||
using CRD.Utils.HLS;
|
||||
using CRD.Utils.JsonConv;
|
||||
using CRD.Utils.Structs;
|
||||
using CRD.Utils.Structs.Crunchyroll;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Microsoft.Win32;
|
||||
using Newtonsoft.Json;
|
||||
|
|
@ -852,12 +851,43 @@ public class Helpers{
|
|||
} else{
|
||||
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{
|
||||
FileName = shutdownCmd,
|
||||
Arguments = shutdownArgs,
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false
|
||||
});
|
||||
process.ErrorDataReceived += (sender, e) => {
|
||||
if (!string.IsNullOrEmpty(e.Data)){
|
||||
Console.Error.WriteLine($"{e.Data}");
|
||||
}
|
||||
};
|
||||
|
||||
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}");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -32,36 +32,84 @@ public class FontsManager{
|
|||
|
||||
#endregion
|
||||
|
||||
public Dictionary<string, List<string>> Fonts{ get; private set; } = new(){
|
||||
{ "Adobe Arabic", new List<string>{ "AdobeArabic-Bold.otf" } },
|
||||
{ "Andale Mono", new List<string>{ "andalemo.ttf" } },
|
||||
{ "Arial", new List<string>{ "arial.ttf", "arialbd.ttf", "arialbi.ttf", "ariali.ttf" } },
|
||||
{ "Arial Unicode MS", new List<string>{ "arialuni.ttf" } },
|
||||
{ "Arial Black", new List<string>{ "ariblk.ttf" } },
|
||||
{ "Comic Sans MS", new List<string>{ "comic.ttf", "comicbd.ttf" } },
|
||||
{ "Courier New", new List<string>{ "cour.ttf", "courbd.ttf", "courbi.ttf", "couri.ttf" } },
|
||||
{ "DejaVu LGC Sans Mono", new List<string>{ "DejaVuLGCSansMono-Bold.ttf", "DejaVuLGCSansMono-BoldOblique.ttf", "DejaVuLGCSansMono-Oblique.ttf", "DejaVuLGCSansMono.ttf" } },
|
||||
{ "DejaVu Sans", new List<string>{ "DejaVuSans-Bold.ttf", "DejaVuSans-BoldOblique.ttf", "DejaVuSans-ExtraLight.ttf", "DejaVuSans-Oblique.ttf", "DejaVuSans.ttf" } },
|
||||
{ "DejaVu Sans Condensed", new List<string>{ "DejaVuSansCondensed-Bold.ttf", "DejaVuSansCondensed-BoldOblique.ttf", "DejaVuSansCondensed-Oblique.ttf", "DejaVuSansCondensed.ttf" } },
|
||||
{ "DejaVu Sans Mono", new List<string>{ "DejaVuSansMono-Bold.ttf", "DejaVuSansMono-BoldOblique.ttf", "DejaVuSansMono-Oblique.ttf", "DejaVuSansMono.ttf" } },
|
||||
{ "Georgia", new List<string>{ "georgia.ttf", "georgiab.ttf", "georgiai.ttf", "georgiaz.ttf" } },
|
||||
{ "Impact", new List<string>{ "impact.ttf" } },
|
||||
{ "Rubik Black", new List<string>{ "Rubik-Black.ttf", "Rubik-BlackItalic.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" } },
|
||||
{ "Tahoma", new List<string>{ "tahoma.ttf" } },
|
||||
{ "Times New Roman", new List<string>{ "times.ttf", "timesbd.ttf", "timesbi.ttf", "timesi.ttf" } },
|
||||
{ "Trebuchet MS", new List<string>{ "trebuc.ttf", "trebucbd.ttf", "trebucbi.ttf", "trebucit.ttf" } },
|
||||
{ "Verdana", new List<string>{ "verdana.ttf", "verdanab.ttf", "verdanai.ttf", "verdanaz.ttf" } },
|
||||
{ "Vrinda", new List<string>{ "vrinda.ttf", "vrindab.ttf"} },
|
||||
{ "Webdings", new List<string>{ "webdings.ttf" } },
|
||||
public Dictionary<string, string> Fonts{ get; private set; } = new(){
|
||||
{ "Adobe Arabic", "AdobeArabic-Bold.otf" },
|
||||
{ "Andale Mono", "andalemo.ttf" },
|
||||
{ "Arial", "arial.ttf" },
|
||||
{ "Arial Black", "ariblk.ttf" },
|
||||
{ "Arial Bold", "arialbd.ttf" },
|
||||
{ "Arial Bold Italic", "arialbi.ttf" },
|
||||
{ "Arial Italic", "ariali.ttf" },
|
||||
{ "Arial Unicode MS", "arialuni.ttf" },
|
||||
{ "Comic Sans MS", "comic.ttf" },
|
||||
{ "Comic Sans MS Bold", "comicbd.ttf" },
|
||||
{ "Courier New", "cour.ttf" },
|
||||
{ "Courier New Bold", "courbd.ttf" },
|
||||
{ "Courier New Bold Italic", "courbi.ttf" },
|
||||
{ "Courier New Italic", "couri.ttf" },
|
||||
{ "DejaVu LGC Sans Mono", "DejaVuLGCSansMono.ttf" },
|
||||
{ "DejaVu LGC Sans Mono Bold", "DejaVuLGCSansMono-Bold.ttf" },
|
||||
{ "DejaVu LGC Sans Mono Bold Oblique", "DejaVuLGCSansMono-BoldOblique.ttf" },
|
||||
{ "DejaVu LGC Sans Mono Oblique", "DejaVuLGCSansMono-Oblique.ttf" },
|
||||
{ "DejaVu Sans", "DejaVuSans.ttf" },
|
||||
{ "DejaVu Sans Bold", "DejaVuSans-Bold.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 async Task GetFontsAsync(){
|
||||
Console.WriteLine("Downloading fonts...");
|
||||
var fonts = Fonts.Values.SelectMany(f => f).ToList();
|
||||
var fonts = Fonts.Values.ToList();
|
||||
|
||||
foreach (var font in fonts){
|
||||
var fontLoc = Path.Combine(CfgManager.PathFONTS_DIR, font);
|
||||
|
|
@ -125,8 +173,8 @@ public class FontsManager{
|
|||
return styles.Distinct().ToList(); // Using Linq to remove duplicates
|
||||
}
|
||||
|
||||
public Dictionary<string, List<string>> GetDictFromKeyList(List<string> keysList){
|
||||
Dictionary<string, List<string>> filteredDictionary = new Dictionary<string, List<string>>();
|
||||
public Dictionary<string, string> GetDictFromKeyList(List<string> keysList){
|
||||
Dictionary<string, string> filteredDictionary = new Dictionary<string, string>();
|
||||
|
||||
foreach (string key in keysList){
|
||||
if (Fonts.TryGetValue(key, out var font)){
|
||||
|
|
@ -148,7 +196,7 @@ public class FontsManager{
|
|||
}
|
||||
|
||||
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<ParsedFont> fontsList = new List<ParsedFont>();
|
||||
bool isNstr = true;
|
||||
|
|
@ -175,13 +223,11 @@ public class FontsManager{
|
|||
List<string> missingFonts = new List<string>();
|
||||
|
||||
foreach (var f in fontsNameList){
|
||||
if (Fonts.TryGetValue(f.Key, out var fontFiles)){
|
||||
foreach (var fontFile in fontFiles){
|
||||
string fontPath = Path.Combine(fontsDir, fontFile);
|
||||
string mime = GetFontMimeType(fontFile);
|
||||
if (File.Exists(fontPath) && new FileInfo(fontPath).Length != 0){
|
||||
fontsList.Add(new ParsedFont{ Name = fontFile, Path = fontPath, Mime = mime });
|
||||
}
|
||||
if (Fonts.TryGetValue(f.Key, out var fontFile)){
|
||||
string fontPath = Path.Combine(fontsDir, fontFile);
|
||||
string mime = GetFontMimeType(fontFile);
|
||||
if (File.Exists(fontPath) && new FileInfo(fontPath).Length != 0){
|
||||
fontsList.Add(new ParsedFont{ Name = fontFile, Path = fontPath, Mime = mime });
|
||||
}
|
||||
} else{
|
||||
missingFonts.Add(f.Key);
|
||||
|
|
@ -198,5 +244,5 @@ public class FontsManager{
|
|||
|
||||
public class SubtitleFonts{
|
||||
public LanguageItem Language{ get; set; }
|
||||
public Dictionary<string, List<string>> Fonts{ get; set; }
|
||||
public Dictionary<string, string> Fonts{ get; set; }
|
||||
}
|
||||
|
|
@ -1,13 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils.Files;
|
||||
using CRD.Utils.Structs;
|
||||
|
||||
|
|
@ -264,6 +262,14 @@ public class Merger{
|
|||
if (options.Description is{ Count: > 0 }){
|
||||
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);
|
||||
|
|
@ -425,8 +431,11 @@ public class Merger{
|
|||
.ToList();
|
||||
allMediaFiles.ForEach(file => Helpers.DeleteFile(file.Path));
|
||||
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.Cover?.ForEach(cover => Helpers.DeleteFile(cover.Path));
|
||||
|
||||
// Delete chapter files if any
|
||||
options.Chapters?.ForEach(chapter => Helpers.DeleteFile(chapter.Path));
|
||||
|
|
@ -471,6 +480,7 @@ public class CrunchyMuxOptions{
|
|||
public bool Mp4{ get; set; }
|
||||
public bool Mp3{ get; set; }
|
||||
public bool MuxFonts{ get; set; }
|
||||
public bool MuxCover{ get; set; }
|
||||
public bool MuxDescription{ get; set; }
|
||||
public string ForceMuxer{ get; set; }
|
||||
public bool? NoCleanup{ get; set; }
|
||||
|
|
@ -511,6 +521,7 @@ public class MergerOptions{
|
|||
public bool CcSubsMuxingFlag{ get; set; }
|
||||
public bool SignsSubsAsForced{ get; set; }
|
||||
public List<MergerInput> Description{ get; set; } = new List<MergerInput>();
|
||||
public List<MergerInput> Cover{ get; set; } =[];
|
||||
}
|
||||
|
||||
public class MuxOptions{
|
||||
|
|
|
|||
|
|
@ -179,6 +179,7 @@ public class ToM3u8Class{
|
|||
playlist.attributes = new ExpandoObject();
|
||||
playlist.attributes.NAME = item.attributes.id;
|
||||
playlist.attributes.BANDWIDTH = item.attributes.bandwidth;
|
||||
playlist.attributes.AUDIOSAMPLINGRATE = item.attributes.audioSamplingRate;
|
||||
playlist.attributes.CODECS = item.attributes.codecs;
|
||||
playlist.uri = string.Empty;
|
||||
playlist.endList = item.attributes.type == "static";
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using CRD.Utils.DRM;
|
||||
using CRD.Utils.HLS;
|
||||
using CRD.Utils.Parser;
|
||||
using CRD.Utils.Parser.Utils;
|
||||
|
|
@ -28,12 +29,15 @@ public class Map{
|
|||
|
||||
public class PlaylistItem{
|
||||
public string? pssh{ get; set; }
|
||||
public List<ContentKey> encryptionKeys{ get; set; } =[];
|
||||
public int bandwidth{ get; set; }
|
||||
public List<Segment> segments{ get; set; }
|
||||
}
|
||||
|
||||
public class AudioPlaylist : PlaylistItem{
|
||||
public LanguageItem? language{ get; set; }
|
||||
|
||||
public int audioSamplingRate{ get; set; }
|
||||
public bool @default{ get; set; }
|
||||
}
|
||||
|
||||
|
|
@ -92,6 +96,7 @@ public static class MPDParser{
|
|||
|
||||
var pItem = new AudioPlaylist{
|
||||
bandwidth = playlist.attributes.BANDWIDTH,
|
||||
audioSamplingRate = ObjectUtilities.GetMemberValue(playlist.attributes ,"AUDIOSAMPLINGRATE") ?? 0,
|
||||
language = audioLang,
|
||||
@default = item.@default,
|
||||
segments = segments.Select(segment => new Segment{
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ public class ParseAttribute{
|
|||
{ "width", Width },
|
||||
{ "height", Height },
|
||||
{ "bandwidth", Bandwidth },
|
||||
{ "audioSamplingRate", AudioSamplingRate },
|
||||
{ "frameRate", FrameRate },
|
||||
{ "startNumber", StartNumber },
|
||||
{ "timescale", Timescale },
|
||||
|
|
@ -40,6 +41,7 @@ public class ParseAttribute{
|
|||
public static object Width(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 AudioSamplingRate(string value) => int.Parse(value);
|
||||
public static object FrameRate(string value) => DivisionValueParser.ParseDivisionValue(value);
|
||||
public static object StartNumber(string value) => int.Parse(value);
|
||||
public static object Timescale(string value) => int.Parse(value);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
using CRD.Utils.Sonarr;
|
||||
using CRD.ViewModels;
|
||||
using Newtonsoft.Json;
|
||||
using YamlDotNet.Serialization;
|
||||
|
||||
namespace CRD.Utils.Structs.Crunchyroll;
|
||||
|
||||
|
|
@ -29,6 +28,9 @@ public class CrDownloadOptions{
|
|||
|
||||
[JsonIgnore]
|
||||
public string Force{ get; set; } = "";
|
||||
|
||||
[JsonProperty("download_methode_new")]
|
||||
public bool DownloadMethodeNew{ get; set; }
|
||||
|
||||
[JsonProperty("simultaneous_downloads")]
|
||||
public int SimultaneousDownloads{ get; set; }
|
||||
|
|
@ -208,6 +210,9 @@ public class CrDownloadOptions{
|
|||
|
||||
[JsonProperty("mux_fonts")]
|
||||
public bool MuxFonts{ get; set; }
|
||||
|
||||
[JsonProperty("mux_cover")]
|
||||
public bool MuxCover{ get; set; }
|
||||
|
||||
[JsonProperty("mux_video_title")]
|
||||
public string? VideoTitle{ get; set; }
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ public class CrunchyMovie{
|
|||
|
||||
public string Description{ get; set; }
|
||||
|
||||
public Images? Images{ get; set; }
|
||||
public Images Images{ get; set; } = new();
|
||||
|
||||
[JsonProperty("media_type")]
|
||||
public string? MediaType{ get; set; }
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ public class CrunchyEpisode : IHistorySource{
|
|||
[JsonProperty("availability_starts")]
|
||||
public DateTime AvailabilityStarts{ get; set; }
|
||||
|
||||
public Images? Images{ get; set; }
|
||||
public Images Images{ get; set; } = new();
|
||||
|
||||
[JsonProperty("season_id")]
|
||||
public string SeasonId{ get; set; }
|
||||
|
|
@ -289,7 +289,7 @@ public class CrunchyEpisode : IHistorySource{
|
|||
public EpisodeType GetEpisodeType(){
|
||||
return EpisodeType;
|
||||
}
|
||||
|
||||
|
||||
public string GetImageUrl(){
|
||||
if (Images != null){
|
||||
return Images.Thumbnail?.First().First().Source ?? string.Empty;
|
||||
|
|
@ -303,15 +303,15 @@ public class CrunchyEpisode : IHistorySource{
|
|||
|
||||
public class Images{
|
||||
[JsonProperty("poster_tall")]
|
||||
public List<List<Image>>? PosterTall{ get; set; }
|
||||
public List<List<Image>> PosterTall{ get; set; } =[];
|
||||
|
||||
[JsonProperty("poster_wide")]
|
||||
public List<List<Image>>? PosterWide{ get; set; }
|
||||
public List<List<Image>> PosterWide{ get; set; } =[];
|
||||
|
||||
[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{
|
||||
|
|
@ -368,6 +368,7 @@ public class CrunchyEpMeta{
|
|||
public string? SeriesId{ get; set; }
|
||||
public string? AbsolutEpisodeNumberE{ get; set; }
|
||||
public string? Image{ get; set; }
|
||||
public string? ImageBig{ get; set; }
|
||||
public bool Paused{ get; set; }
|
||||
public DownloadProgress DownloadProgress{ get; set; } = new();
|
||||
|
||||
|
|
@ -389,7 +390,6 @@ public class CrunchyEpMeta{
|
|||
public bool OnlySubs{ get; set; }
|
||||
|
||||
public CrDownloadOptions? DownloadSettings;
|
||||
|
||||
}
|
||||
|
||||
public class DownloadProgress{
|
||||
|
|
@ -410,7 +410,7 @@ public class CrunchyEpMetaData{
|
|||
public List<EpisodeVersion>? Versions{ get; set; }
|
||||
public bool IsSubbed{ get; set; }
|
||||
public bool IsDubbed{ get; set; }
|
||||
|
||||
|
||||
public (string? seasonID, string? guid) GetOriginalIds(){
|
||||
var version = Versions?.FirstOrDefault(a => a.Original);
|
||||
if (version != null && !string.IsNullOrEmpty(version.Guid) && !string.IsNullOrEmpty(version.SeasonGuid)){
|
||||
|
|
@ -419,7 +419,6 @@ public class CrunchyEpMetaData{
|
|||
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class CrunchyRollEpisodeData{
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ public class SxItem{
|
|||
public string? Path{ get; set; }
|
||||
public string? File{ 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{
|
||||
|
|
|
|||
|
|
@ -10,9 +10,7 @@ using Avalonia.Media.Imaging;
|
|||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils.CustomList;
|
||||
using CRD.Utils.Files;
|
||||
using CRD.Views;
|
||||
using Newtonsoft.Json;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace CRD.Utils.Structs.History;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ using System.Collections.ObjectModel;
|
|||
using System.Linq;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CRD.Downloader;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
using CRD.Utils.Structs.Crunchyroll.Music;
|
||||
using CRD.Utils.Structs.History;
|
||||
|
|
|
|||
|
|
@ -3,9 +3,6 @@ using System.Collections.Generic;
|
|||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.Imaging;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CRD.Downloader.Crunchyroll;
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ public partial class GeneralSettingsViewModel : ViewModelBase{
|
|||
|
||||
[ObservableProperty]
|
||||
private bool _history;
|
||||
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _historyCountMissing;
|
||||
|
||||
|
|
@ -54,12 +54,15 @@ public partial class GeneralSettingsViewModel : ViewModelBase{
|
|||
[ObservableProperty]
|
||||
private double? _simultaneousDownloads;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _downloadMethodeNew;
|
||||
|
||||
[ObservableProperty]
|
||||
private double? _downloadSpeed;
|
||||
|
||||
|
||||
[ObservableProperty]
|
||||
private double? _retryAttempts;
|
||||
|
||||
|
||||
[ObservableProperty]
|
||||
private double? _retryDelay;
|
||||
|
||||
|
|
@ -268,8 +271,9 @@ public partial class GeneralSettingsViewModel : ViewModelBase{
|
|||
HistorySkipUnmonitored = options.HistorySkipUnmonitored;
|
||||
HistoryCountSonarr = options.HistoryCountSonarr;
|
||||
DownloadSpeed = options.DownloadSpeedLimit;
|
||||
RetryAttempts = Math.Clamp((options.RetryAttempts), 1, 10);
|
||||
RetryDelay = Math.Clamp((options.RetryDelay), 1, 30);
|
||||
DownloadMethodeNew = options.DownloadMethodeNew;
|
||||
RetryAttempts = Math.Clamp((options.RetryAttempts), 1, 10);
|
||||
RetryDelay = Math.Clamp((options.RetryDelay), 1, 30);
|
||||
DownloadToTempFolder = options.DownloadToTempFolder;
|
||||
SimultaneousDownloads = options.SimultaneousDownloads;
|
||||
LogMode = options.LogMode;
|
||||
|
|
@ -292,12 +296,14 @@ public partial class GeneralSettingsViewModel : ViewModelBase{
|
|||
}
|
||||
|
||||
var settings = CrunchyrollManager.Instance.CrunOptions;
|
||||
|
||||
|
||||
settings.DownloadFinishedPlaySound = DownloadFinishedPlaySound;
|
||||
|
||||
settings.DownloadMethodeNew = DownloadMethodeNew;
|
||||
|
||||
settings.BackgroundImageBlurRadius = Math.Clamp((BackgroundImageBlurRadius ?? 0), 0, 40);
|
||||
settings.BackgroundImageOpacity = Math.Clamp((BackgroundImageOpacity ?? 0), 0, 1);
|
||||
|
||||
|
||||
settings.RetryAttempts = Math.Clamp((int)(RetryAttempts ?? 0), 1, 10);
|
||||
settings.RetryDelay = Math.Clamp((int)(RetryDelay ?? 0), 1, 30);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using CRD.ViewModels;
|
||||
|
||||
namespace CRD.Views;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
using System.Linq;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.VisualTree;
|
||||
using CRD.Utils.Sonarr;
|
||||
using CRD.ViewModels;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using CRD.Utils.UI;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace CRD.Views.Utils;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using CRD.Utils.UI;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace CRD.Views.Utils;
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
</ComboBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
|
||||
<controls:SettingsExpanderItem Content="History count Missing" Description="The missing count (orange corner) shows all missing episodes, not just new ones">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding HistoryCountMissing}"> </CheckBox>
|
||||
|
|
@ -70,6 +70,12 @@
|
|||
Description="Adjust download settings"
|
||||
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"
|
||||
Description="Download in Kb/s - 0 is full speed">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
|
|
@ -595,13 +601,7 @@
|
|||
VerticalAlignment="Center"
|
||||
DockPanel.Dock="Left" />
|
||||
|
||||
<controls:ColorPickerButton Color="{Binding CustomAccentColor}"
|
||||
IsMoreButtonVisible="True"
|
||||
UseSpectrum="True"
|
||||
UseColorWheel="False"
|
||||
UseColorTriangle="False"
|
||||
UseColorPalette="False"
|
||||
IsCompact="True" ShowAcceptDismissButtons="True"
|
||||
<ColorPicker Color="{Binding CustomAccentColor}"
|
||||
DockPanel.Dock="Right" />
|
||||
</DockPanel>
|
||||
</StackPanel>
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
- **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)
|
||||
|
||||
### 2. Acquire Widevine CDM Files
|
||||
|
|
|
|||
Loading…
Reference in a new issue