mirror of
https://github.com/Crunchy-DL/Crunchy-Downloader.git
synced 2026-01-11 12:00:34 +00:00
- Added **fallback for hardsub** to use **no-hardsub video** if enabled
- Added **video/audio toggle for endpoints** to control which media type is used from each endpoint - **Updated packages** to the latest versions - **Updated Android phone token**
This commit is contained in:
parent
dc570bf420
commit
c5660a87e7
12 changed files with 276 additions and 132 deletions
|
|
@ -42,7 +42,7 @@ public class CrunchyrollManager{
|
|||
public ObservableCollection<HistorySeries> HistoryList = new();
|
||||
|
||||
public HistorySeries SelectedSeries = new HistorySeries{
|
||||
Seasons =[]
|
||||
Seasons = []
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
|
@ -107,8 +107,8 @@ public class CrunchyrollManager{
|
|||
options.Partsize = 10;
|
||||
options.DlSubs = new List<string>{ "en-US" };
|
||||
options.SkipMuxing = false;
|
||||
options.MkvmergeOptions =[];
|
||||
options.FfmpegOptions =[];
|
||||
options.MkvmergeOptions = [];
|
||||
options.FfmpegOptions = [];
|
||||
options.DefaultAudio = "ja-JP";
|
||||
options.DefaultSub = "en-US";
|
||||
options.QualityAudio = "best";
|
||||
|
|
@ -128,7 +128,7 @@ public class CrunchyrollManager{
|
|||
options.CalendarDubFilter = "none";
|
||||
options.CustomCalendar = true;
|
||||
options.DlVideoOnce = true;
|
||||
options.StreamEndpoint = "web/firefox";
|
||||
options.StreamEndpoint = new CrAuthSettings(){ Endpoint = "tv/android_tv", Audio = true, Video = true };
|
||||
options.SubsAddScaledBorder = ScaledBorderAndShadowSelection.DontAdd;
|
||||
options.HistoryLang = DefaultLocale;
|
||||
options.FixCccSubtitles = true;
|
||||
|
|
@ -201,13 +201,17 @@ public class CrunchyrollManager{
|
|||
|
||||
DefaultAndroidAuthSettings = new CrAuthSettings(){
|
||||
Endpoint = "android/phone",
|
||||
Authorization = "Basic YmY3MHg2aWhjYzhoZ3p3c2J2eGk6eDJjc3BQZXQzWno1d0pDdEpyVUNPSVM5Ynpad1JDcGM=",
|
||||
UserAgent = "Crunchyroll/3.90.0 Android/16 okhttp/4.12.0",
|
||||
Client_ID = "pd6uw3dfyhzghs0wxae3",
|
||||
Authorization = "Basic cGQ2dXczZGZ5aHpnaHMwd3hhZTM6NXJ5SjJFQXR3TFc0UklIOEozaWk1anVqbnZrRWRfTkY=",
|
||||
UserAgent = "Crunchyroll/3.95.2 Android/16 okhttp/4.12.0",
|
||||
Device_name = "CPH2449",
|
||||
Device_type = "OnePlus CPH2449"
|
||||
Device_type = "OnePlus CPH2449",
|
||||
Audio = true,
|
||||
Video = true,
|
||||
};
|
||||
|
||||
CrunOptions.StreamEndpoint = "tv/android_tv";
|
||||
CrunOptions.StreamEndpoint ??= new CrAuthSettings(){ Endpoint = "tv/android_tv", Audio = true, Video = true };
|
||||
CrunOptions.StreamEndpoint.Endpoint = "tv/android_tv";
|
||||
CrAuthEndpoint1.AuthSettings = new CrAuthSettings(){
|
||||
Endpoint = "tv/android_tv",
|
||||
Authorization = "Basic ZGsxYndzemRyc3lkeTR1N2xvenE6bDl0SU1BdTlzTGc4ZjA4ajlfQkQ4eWZmQmZTSms0R0o=",
|
||||
|
|
@ -236,7 +240,7 @@ public class CrunchyrollManager{
|
|||
// ApiUrls.authBasicMob = "Basic " + token;
|
||||
// }
|
||||
|
||||
var jsonFiles = Directory.Exists(CfgManager.PathENCODING_PRESETS_DIR) ? Directory.GetFiles(CfgManager.PathENCODING_PRESETS_DIR, "*.json") :[];
|
||||
var jsonFiles = Directory.Exists(CfgManager.PathENCODING_PRESETS_DIR) ? Directory.GetFiles(CfgManager.PathENCODING_PRESETS_DIR, "*.json") : [];
|
||||
|
||||
foreach (var file in jsonFiles){
|
||||
try{
|
||||
|
|
@ -276,13 +280,13 @@ public class CrunchyrollManager{
|
|||
}
|
||||
});
|
||||
} else{
|
||||
HistoryList =[];
|
||||
HistoryList = [];
|
||||
}
|
||||
} else{
|
||||
HistoryList =[];
|
||||
HistoryList = [];
|
||||
}
|
||||
} else{
|
||||
HistoryList =[];
|
||||
HistoryList = [];
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -303,7 +307,14 @@ public class CrunchyrollManager{
|
|||
Doing = "Starting"
|
||||
};
|
||||
QueueManager.Instance.Queue.Refresh();
|
||||
var res = await DownloadMediaList(data, options);
|
||||
var res = new DownloadResponse();
|
||||
try{
|
||||
res = await DownloadMediaList(data, options);
|
||||
} catch (Exception e){
|
||||
Console.WriteLine(e);
|
||||
res.Error = true;
|
||||
}
|
||||
|
||||
|
||||
if (res.Error){
|
||||
QueueManager.Instance.DecrementDownloads();
|
||||
|
|
@ -356,7 +367,7 @@ public class CrunchyrollManager{
|
|||
var fileNameAndPath = options.DownloadToTempFolder
|
||||
? Path.Combine(res.TempFolderPath ?? string.Empty, res.FileName ?? string.Empty)
|
||||
: Path.Combine(res.FolderPath ?? string.Empty, res.FileName ?? string.Empty);
|
||||
if (options is{ DlVideoOnce: false, KeepDubsSeperate: true }){
|
||||
if (options is{ DlVideoOnce: false, KeepDubsSeperate: true } && (!options.Noaudio || !options.Novids)){
|
||||
var groupByDub = Helpers.GroupByLanguageWithSubtitles(res.Data);
|
||||
var mergers = new List<Merger>();
|
||||
foreach (var keyValue in groupByDub){
|
||||
|
|
@ -421,7 +432,7 @@ public class CrunchyrollManager{
|
|||
|
||||
if (preset != null) await Helpers.RunFFmpegWithPresetAsync(merger.options.Output, preset, data);
|
||||
}
|
||||
|
||||
|
||||
if (options.DownloadToTempFolder){
|
||||
await MoveFromTempFolder(merger, data, options, res.TempFolderPath ?? CfgManager.PathTEMP_DIR, merger.options.Subtitles);
|
||||
}
|
||||
|
|
@ -481,7 +492,22 @@ public class CrunchyrollManager{
|
|||
}
|
||||
|
||||
if (options.DownloadToTempFolder){
|
||||
await MoveFromTempFolder(result.merger, data, options, res.TempFolderPath ?? CfgManager.PathTEMP_DIR, result.merger?.options.Subtitles ?? []);
|
||||
var tempFolder = res.TempFolderPath ?? CfgManager.PathTEMP_DIR;
|
||||
|
||||
List<SubtitleInput> subtitles =
|
||||
result.merger?.options.Subtitles
|
||||
?? res.Data
|
||||
.Where(d => d.Type == DownloadMediaType.Subtitle)
|
||||
.Select(d => new SubtitleInput{
|
||||
File = d.Path ?? string.Empty,
|
||||
Language = d.Language,
|
||||
ClosedCaption = d.Cc ?? false,
|
||||
Signs = d.Signs ?? false,
|
||||
RelatedVideoDownloadMedia = d.RelatedVideoDownloadMedia
|
||||
})
|
||||
.ToList();
|
||||
|
||||
await MoveFromTempFolder(result.merger, data, options, tempFolder, subtitles);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -663,7 +689,7 @@ public class CrunchyrollManager{
|
|||
foreach (var downloadedMedia in subs){
|
||||
var subt = new SubtitleFonts();
|
||||
subt.Language = downloadedMedia.Language;
|
||||
subt.Fonts = downloadedMedia.Fonts ??[];
|
||||
subt.Fonts = downloadedMedia.Fonts ?? [];
|
||||
subsList.Add(subt);
|
||||
}
|
||||
|
||||
|
|
@ -701,7 +727,7 @@ public class CrunchyrollManager{
|
|||
Subtitles = data.Where(a => a.Type == DownloadMediaType.Subtitle).Select(a => new SubtitleInput
|
||||
{ File = a.Path ?? string.Empty, Language = a.Language, ClosedCaption = a.Cc ?? false, Signs = a.Signs ?? false, RelatedVideoDownloadMedia = a.RelatedVideoDownloadMedia }).ToList(),
|
||||
KeepAllVideos = options.KeepAllVideos,
|
||||
Fonts = options.MuxFonts ? FontsManager.Instance.MakeFontsList(CfgManager.PathFONTS_DIR, subsList) :[],
|
||||
Fonts = options.MuxFonts ? FontsManager.Instance.MakeFontsList(CfgManager.PathFONTS_DIR, subsList) : [],
|
||||
Chapters = data.Where(a => a.Type == DownloadMediaType.Chapters).Select(a => new MergerInput{ Language = a.Lang, Path = a.Path ?? string.Empty }).ToList(),
|
||||
VideoTitle = options.VideoTitle,
|
||||
Options = new MuxOptions(){
|
||||
|
|
@ -718,8 +744,8 @@ public class CrunchyrollManager{
|
|||
DefaultSubForcedDisplay = options.DefaultSubForcedDisplay,
|
||||
CcSubsMuxingFlag = options.CcSubsMuxingFlag,
|
||||
SignsSubsAsForced = options.SignsSubsAsForced,
|
||||
Description = muxDesc ? data.Where(a => a.Type == DownloadMediaType.Description).Select(a => new MergerInput{ Path = a.Path ?? string.Empty }).ToList() :[],
|
||||
Cover = options.MuxCover ? data.Where(a => a.Type == DownloadMediaType.Cover).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)){
|
||||
|
|
@ -731,7 +757,7 @@ public class CrunchyrollManager{
|
|||
}
|
||||
|
||||
bool isMuxed, syncError = false;
|
||||
List<string> notSyncedDubs =[];
|
||||
List<string> notSyncedDubs = [];
|
||||
|
||||
|
||||
if (options is{ SyncTiming: true, DlVideoOnce: true } && merger.options.OnlyVid.Count > 0 && merger.options.OnlyAudio.Count > 0){
|
||||
|
|
@ -904,11 +930,10 @@ public class CrunchyrollManager{
|
|||
options.Partsize = options.Partsize > 0 ? options.Partsize : 1;
|
||||
|
||||
if (options.DownloadDescriptionAudio){
|
||||
|
||||
var alreadyAdr = new HashSet<string>(
|
||||
data.Data.Where(x => x.IsAudioRoleDescription).Select(x => x.Lang?.CrLocale ?? "err")
|
||||
);
|
||||
|
||||
|
||||
bool HasDescriptionRole(IEnumerable<string>? roles) =>
|
||||
roles?.Any(r => string.Equals(r, "description", StringComparison.OrdinalIgnoreCase)) == true;
|
||||
|
||||
|
|
@ -918,7 +943,7 @@ public class CrunchyrollManager{
|
|||
.Where(m => m.Versions?.Any(v => (v.AudioLocale == (m.Lang?.CrLocale ?? "err"))
|
||||
&& HasDescriptionRole(v.roles)) == true)
|
||||
.ToList();
|
||||
|
||||
|
||||
var additions = toDuplicate.Select(m => new CrunchyEpMetaData{
|
||||
MediaId = m.MediaId,
|
||||
Lang = m.Lang,
|
||||
|
|
@ -1024,13 +1049,18 @@ public class CrunchyrollManager{
|
|||
|
||||
#endregion
|
||||
|
||||
var fetchPlaybackData = await FetchPlaybackData(CrAuthEndpoint1, mediaId, mediaGuid, data.Music, epMeta.IsAudioRoleDescription);
|
||||
(bool IsOk, PlaybackData pbData, string error) fetchPlaybackData = default;
|
||||
(bool IsOk, PlaybackData pbData, string error) fetchPlaybackData2 = default;
|
||||
if (CrAuthEndpoint2.Profile.Username != "???"){
|
||||
fetchPlaybackData2 = await FetchPlaybackData(CrAuthEndpoint2, mediaId, mediaGuid, data.Music, epMeta.IsAudioRoleDescription);
|
||||
|
||||
if (CrAuthEndpoint1.Profile.Username != "???" && options.StreamEndpoint != null && (options.StreamEndpoint.Video || options.StreamEndpoint.Audio)){
|
||||
fetchPlaybackData = await FetchPlaybackData(CrAuthEndpoint1, mediaId, mediaGuid, data.Music, epMeta.IsAudioRoleDescription, options.StreamEndpoint);
|
||||
}
|
||||
|
||||
if (!fetchPlaybackData.IsOk){
|
||||
if (CrAuthEndpoint2.Profile.Username != "???" && options.StreamEndpointSecondSettings != null && (options.StreamEndpointSecondSettings.Video || options.StreamEndpointSecondSettings.Audio)){
|
||||
fetchPlaybackData2 = await FetchPlaybackData(CrAuthEndpoint2, mediaId, mediaGuid, data.Music, epMeta.IsAudioRoleDescription, options.StreamEndpointSecondSettings);
|
||||
}
|
||||
|
||||
if (!fetchPlaybackData.IsOk && !fetchPlaybackData2.IsOk){
|
||||
var errorJson = fetchPlaybackData.error;
|
||||
if (!string.IsNullOrEmpty(errorJson)){
|
||||
var error = StreamError.FromJson(errorJson);
|
||||
|
|
@ -1077,7 +1107,7 @@ public class CrunchyrollManager{
|
|||
}
|
||||
|
||||
if (fetchPlaybackData2.IsOk){
|
||||
if (fetchPlaybackData.pbData.Data != null && fetchPlaybackData2.pbData?.Data != null)
|
||||
if (fetchPlaybackData.pbData?.Data != null && fetchPlaybackData2.pbData?.Data != null){
|
||||
foreach (var keyValuePair in fetchPlaybackData2.pbData.Data){
|
||||
var pbDataFirstEndpoint = fetchPlaybackData.pbData?.Data;
|
||||
if (pbDataFirstEndpoint != null && pbDataFirstEndpoint.TryGetValue(keyValuePair.Key, out var value)){
|
||||
|
|
@ -1101,13 +1131,16 @@ public class CrunchyrollManager{
|
|||
}
|
||||
}
|
||||
}
|
||||
} else{
|
||||
fetchPlaybackData = fetchPlaybackData2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var pbData = fetchPlaybackData.pbData;
|
||||
|
||||
List<string> hsLangs = new List<string>();
|
||||
var pbStreams = pbData.Data;
|
||||
var pbStreams = pbData?.Data;
|
||||
var streams = new List<StreamDetailsPop>();
|
||||
|
||||
variables.Add(new Variable("title", data.EpisodeTitle ?? string.Empty, true));
|
||||
|
|
@ -1116,12 +1149,12 @@ public class CrunchyrollManager{
|
|||
variables.Add(new Variable("seriesTitle", data.SeriesTitle ?? string.Empty, true));
|
||||
variables.Add(new Variable("seasonTitle", data.SeasonTitle ?? string.Empty, true));
|
||||
variables.Add(new Variable("season", !string.IsNullOrEmpty(data.Season) ? Math.Round(double.Parse(data.Season, CultureInfo.InvariantCulture), 1) : 0, false));
|
||||
variables.Add(new Variable("dubs", string.Join(", ", data.SelectedDubs ??[]), true));
|
||||
variables.Add(new Variable("dubs", string.Join(", ", data.SelectedDubs ?? []), true));
|
||||
|
||||
|
||||
if (pbStreams?.Keys != null){
|
||||
var pb = pbStreams.Select(v => {
|
||||
if (v.Key != "none" && v.Value is{ IsHardsubbed: true, HardsubLocale: not null } && v.Value.HardsubLocale != Locale.DefaulT && !hsLangs.Contains(v.Value.HardsubLang.CrLocale)){
|
||||
if (v.Key != "none" && v.Value is{ IsHardsubbed: true, HardsubLocale: not null } && v.Value.HardsubLocale != Locale.DefaulT && !hsLangs.Contains(v.Value.HardsubLang.CrLocale)){
|
||||
hsLangs.Add(v.Value.HardsubLang.CrLocale);
|
||||
}
|
||||
|
||||
|
|
@ -1216,14 +1249,22 @@ public class CrunchyrollManager{
|
|||
};
|
||||
}
|
||||
} else{
|
||||
dlFailed = true;
|
||||
if (options.HsRawFallback){
|
||||
streams = streams.Where((s) => !s.IsHardsubbed).ToList();
|
||||
if (streams.Count < 1){
|
||||
Console.Error.WriteLine("Raw streams not available!");
|
||||
dlFailed = true;
|
||||
}
|
||||
} else{
|
||||
dlFailed = true;
|
||||
|
||||
return new DownloadResponse{
|
||||
Data = new List<DownloadedMedia>(),
|
||||
Error = dlFailed,
|
||||
FileName = "./unknown",
|
||||
ErrorText = "No Hardsubs available"
|
||||
};
|
||||
return new DownloadResponse{
|
||||
Data = new List<DownloadedMedia>(),
|
||||
Error = dlFailed,
|
||||
FileName = "./unknown",
|
||||
ErrorText = "No Hardsubs available"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1263,7 +1304,7 @@ public class CrunchyrollManager{
|
|||
var videoDownloadMedia = new DownloadedMedia(){ Lang = Languages.DEFAULT_lang };
|
||||
|
||||
if (!dlFailed && curStream != null && options is not{ Novids: true, Noaudio: true }){
|
||||
Dictionary<string, string> streamPlaylistsReqResponseList =[];
|
||||
Dictionary<string, StreamInfo> streamPlaylistsReqResponseList = [];
|
||||
|
||||
foreach (var streamUrl in curStream.Url){
|
||||
var streamPlaylistsReq = HttpClientReq.CreateRequestMessage(streamUrl.Url ?? string.Empty, HttpMethod.Get, true, streamUrl.CrAuth?.Token?.access_token);
|
||||
|
|
@ -1280,7 +1321,11 @@ public class CrunchyrollManager{
|
|||
}
|
||||
|
||||
if (streamPlaylistsReqResponse.ResponseContent.Contains("MPD")){
|
||||
streamPlaylistsReqResponseList[streamUrl.Url ?? ""] = streamPlaylistsReqResponse.ResponseContent;
|
||||
streamPlaylistsReqResponseList[streamUrl.Url ?? ""] = new StreamInfo(){
|
||||
Playlist = streamPlaylistsReqResponse.ResponseContent,
|
||||
Audio = streamUrl.Audio,
|
||||
Video = streamUrl.Video
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1315,7 +1360,7 @@ public class CrunchyrollManager{
|
|||
//
|
||||
// List<string> streamServers = new List<string>(streamPlaylists.Data.Keys);
|
||||
if (streamPlaylistsReqResponseList.Count > 0){
|
||||
HashSet<string> streamServers =[];
|
||||
HashSet<string> streamServers = [];
|
||||
Dictionary<string, ServerData> playListData = new Dictionary<string, ServerData>();
|
||||
|
||||
foreach (var curStreams in streamPlaylistsReqResponseList){
|
||||
|
|
@ -1328,9 +1373,10 @@ public class CrunchyrollManager{
|
|||
}
|
||||
|
||||
try{
|
||||
MPDParsed streamPlaylists = MPDParser.Parse(curStreams.Value, Languages.FindLang(crLocal), matchedUrl);
|
||||
var entry = curStreams.Value;
|
||||
MPDParsed streamPlaylists = MPDParser.Parse(entry.Playlist, Languages.FindLang(crLocal), matchedUrl);
|
||||
streamServers.UnionWith(streamPlaylists.Data.Keys);
|
||||
Helpers.MergePlaylistData(playListData, streamPlaylists.Data);
|
||||
Helpers.MergePlaylistData(playListData, streamPlaylists.Data, entry.Audio, entry.Video);
|
||||
} catch (Exception e){
|
||||
Console.Error.WriteLine(e);
|
||||
}
|
||||
|
|
@ -1543,9 +1589,10 @@ public class CrunchyrollManager{
|
|||
Console.WriteLine("Skipping video download...");
|
||||
} else{
|
||||
await CrAuthEndpoint1.RefreshToken(true);
|
||||
await CrAuthEndpoint2.RefreshToken(true);
|
||||
|
||||
Dictionary<string, string> authDataDict = new Dictionary<string, string>
|
||||
{ { "authorization", "Bearer " + CrAuthEndpoint1.Token?.access_token },{ "x-cr-content-id", mediaGuid },{ "x-cr-video-token", pbData.Meta?.Token ?? string.Empty } };
|
||||
{ { "authorization", "Bearer " + ((options.StreamEndpoint is { Audio: true } or{ Video: true } ) ? CrAuthEndpoint1.Token?.access_token : CrAuthEndpoint2.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);
|
||||
|
||||
|
|
@ -1576,10 +1623,11 @@ public class CrunchyrollManager{
|
|||
|
||||
if (chosenAudioSegments.segments.Count > 0 && !options.Noaudio && !dlFailed){
|
||||
await CrAuthEndpoint1.RefreshToken(true);
|
||||
await CrAuthEndpoint2.RefreshToken(true);
|
||||
|
||||
if (chosenVideoSegments.encryptionKeys.Count == 0){
|
||||
Dictionary<string, string> authDataDict = new Dictionary<string, string>
|
||||
{ { "authorization", "Bearer " + CrAuthEndpoint1.Token?.access_token },{ "x-cr-content-id", mediaGuid },{ "x-cr-video-token", pbData.Meta?.Token ?? string.Empty } };
|
||||
{ { "authorization", "Bearer " + ((options.StreamEndpoint is { Audio: true } or{ Video: true } ) ? CrAuthEndpoint1.Token?.access_token : CrAuthEndpoint2.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);
|
||||
|
||||
|
|
@ -1635,9 +1683,10 @@ public class CrunchyrollManager{
|
|||
}
|
||||
|
||||
await CrAuthEndpoint1.RefreshToken(true);
|
||||
await CrAuthEndpoint2.RefreshToken(true);
|
||||
|
||||
Dictionary<string, string> authDataDict = new Dictionary<string, string>
|
||||
{ { "authorization", "Bearer " + CrAuthEndpoint1.Token?.access_token },{ "x-cr-content-id", mediaGuid },{ "x-cr-video-token", pbData.Meta?.Token ?? string.Empty } };
|
||||
{ { "authorization", "Bearer " + ((options.StreamEndpoint is { Audio: true } or{ Video: true } ) ? CrAuthEndpoint1.Token?.access_token : CrAuthEndpoint2.Token?.access_token) },{ "x-cr-content-id", mediaGuid },{ "x-cr-video-token", pbData.Meta?.Token ?? string.Empty } };
|
||||
|
||||
var encryptionKeys = chosenVideoSegments.encryptionKeys;
|
||||
|
||||
|
|
@ -1895,7 +1944,7 @@ public class CrunchyrollManager{
|
|||
var isAbsolute = Path.IsPathRooted(outFile);
|
||||
|
||||
// Get all directory parts of the path except the last segment (assuming it's a file)
|
||||
var directories = Path.GetDirectoryName(outFile)?.Split(Path.DirectorySeparatorChar) ??[];
|
||||
var directories = Path.GetDirectoryName(outFile)?.Split(Path.DirectorySeparatorChar) ?? [];
|
||||
|
||||
// Initialize the cumulative path based on whether the original path is absolute or not
|
||||
var cumulativePath = isAbsolute ? "" : fileDir;
|
||||
|
|
@ -1990,7 +2039,7 @@ public class CrunchyrollManager{
|
|||
Console.WriteLine($"{fileName}.xml has been created with the description.");
|
||||
}
|
||||
|
||||
if (options.MuxCover){
|
||||
if (options is{ MuxCover: true, Noaudio: false, Novids: false }){
|
||||
if (!string.IsNullOrEmpty(data.ImageBig) && !File.Exists(fileDir + "cover.png")){
|
||||
var bitmap = await Helpers.LoadImage(data.ImageBig);
|
||||
if (bitmap != null){
|
||||
|
|
@ -2040,8 +2089,8 @@ public class CrunchyrollManager{
|
|||
videoDownloadMedia.Lang = pbData.Meta.AudioLocale;
|
||||
}
|
||||
|
||||
List<SubtitleInfo> subsData = pbData.Meta.Subtitles?.Values.ToList() ??[];
|
||||
List<Caption> capsData = pbData.Meta.Captions?.Values.ToList() ??[];
|
||||
List<SubtitleInfo> subsData = pbData.Meta.Subtitles?.Values.ToList() ?? [];
|
||||
List<Caption> capsData = pbData.Meta.Captions?.Values.ToList() ?? [];
|
||||
var subsDataMapped = subsData.Select(s => {
|
||||
var subLang = Languages.FixAndFindCrLc((s.Locale ?? Locale.DefaulT).GetEnumMemberValue());
|
||||
return new{
|
||||
|
|
@ -2348,7 +2397,8 @@ public class CrunchyrollManager{
|
|||
|
||||
#region Fetch Playback Data
|
||||
|
||||
private async Task<(bool IsOk, PlaybackData pbData, string error)> FetchPlaybackData(CrAuth authEndpoint, string mediaId, string mediaGuidId, bool music, bool auioRoleDesc){
|
||||
private async Task<(bool IsOk, PlaybackData pbData, string error)> FetchPlaybackData(CrAuth authEndpoint, string mediaId, string mediaGuidId, bool music, bool auioRoleDesc,
|
||||
CrAuthSettings optionsStreamEndpointSettings){
|
||||
var temppbData = new PlaybackData{
|
||||
Total = 0,
|
||||
Data = new Dictionary<string, StreamDetails>()
|
||||
|
|
@ -2364,7 +2414,7 @@ public class CrunchyrollManager{
|
|||
}
|
||||
|
||||
if (playbackRequestResponse.IsOk){
|
||||
temppbData = await ProcessPlaybackResponseAsync(playbackRequestResponse.ResponseContent, mediaId, mediaGuidId, authEndpoint);
|
||||
temppbData = await ProcessPlaybackResponseAsync(playbackRequestResponse.ResponseContent, mediaId, mediaGuidId, authEndpoint, optionsStreamEndpointSettings);
|
||||
} else{
|
||||
Console.WriteLine("Request Stream URLs FAILED! Attempting fallback");
|
||||
playbackEndpoint = $"{ApiUrls.Playback}/{(music ? "music/" : "")}{mediaGuidId}/web/firefox/play{(auioRoleDesc ? "?audioRole=description" : "")}";
|
||||
|
|
@ -2375,7 +2425,7 @@ public class CrunchyrollManager{
|
|||
}
|
||||
|
||||
if (playbackRequestResponse.IsOk){
|
||||
temppbData = await ProcessPlaybackResponseAsync(playbackRequestResponse.ResponseContent, mediaId, mediaGuidId, authEndpoint);
|
||||
temppbData = await ProcessPlaybackResponseAsync(playbackRequestResponse.ResponseContent, mediaId, mediaGuidId, authEndpoint, optionsStreamEndpointSettings);
|
||||
} else{
|
||||
Console.Error.WriteLine("Fallback Request Stream URLs FAILED!");
|
||||
}
|
||||
|
|
@ -2405,7 +2455,7 @@ public class CrunchyrollManager{
|
|||
return response;
|
||||
}
|
||||
|
||||
private async Task<PlaybackData> ProcessPlaybackResponseAsync(string responseContent, string mediaId, string mediaGuidId, CrAuth authEndpoint){
|
||||
private async Task<PlaybackData> ProcessPlaybackResponseAsync(string responseContent, string mediaId, string mediaGuidId, CrAuth authEndpoint, CrAuthSettings optionsStreamEndpointSettings){
|
||||
var temppbData = new PlaybackData{
|
||||
Total = 0,
|
||||
Data = new Dictionary<string, StreamDetails>()
|
||||
|
|
@ -2424,7 +2474,7 @@ public class CrunchyrollManager{
|
|||
foreach (var hardsub in playStream.HardSubs){
|
||||
var stream = hardsub.Value;
|
||||
derivedPlayCrunchyStreams[hardsub.Key] = new StreamDetails{
|
||||
Url =[new UrlWithAuth(){ Url = stream.Url, CrAuth = authEndpoint }],
|
||||
Url = [new UrlWithAuth(){ Url = stream.Url, CrAuth = authEndpoint, Audio = optionsStreamEndpointSettings.Audio, Video = optionsStreamEndpointSettings.Video }],
|
||||
IsHardsubbed = true,
|
||||
HardsubLocale = stream.Hlang,
|
||||
HardsubLang = Languages.FixAndFindCrLc((stream.Hlang ?? Locale.DefaulT).GetEnumMemberValue())
|
||||
|
|
@ -2433,7 +2483,7 @@ public class CrunchyrollManager{
|
|||
}
|
||||
|
||||
derivedPlayCrunchyStreams[""] = new StreamDetails{
|
||||
Url =[new UrlWithAuth(){ Url = playStream.Url, CrAuth = authEndpoint }],
|
||||
Url = [new UrlWithAuth(){ Url = playStream.Url, CrAuth = authEndpoint, Audio = optionsStreamEndpointSettings.Audio, Video = optionsStreamEndpointSettings.Video }],
|
||||
IsHardsubbed = false,
|
||||
HardsubLocale = Locale.DefaulT,
|
||||
HardsubLang = Languages.DEFAULT_lang
|
||||
|
|
|
|||
|
|
@ -139,6 +139,9 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
[ObservableProperty]
|
||||
private ComboBoxItem _selectedHSLang;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _hsRawFallback;
|
||||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem _selectedDescriptionLang;
|
||||
|
||||
|
|
@ -150,6 +153,12 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem _selectedStreamEndpoint;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _firstEndpointVideo;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _firstEndpointAudio;
|
||||
|
||||
[ObservableProperty]
|
||||
private ComboBoxItem _SelectedStreamEndpointSecondary;
|
||||
|
|
@ -169,6 +178,12 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
[ObservableProperty]
|
||||
private string _endpointDeviceType = "";
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _endpointVideo;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _endpointAudio;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isLoggingIn;
|
||||
|
||||
|
|
@ -188,7 +203,7 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
private ComboBoxItem? _selectedAudioQuality;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<ListBoxItem> _selectedSubLang =[];
|
||||
private ObservableCollection<ListBoxItem> _selectedSubLang = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private Color _listBoxColor;
|
||||
|
|
@ -231,12 +246,12 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
new(){ Content = "ar-SA" }
|
||||
];
|
||||
|
||||
public ObservableCollection<ListBoxItem> DubLangList{ get; } =[];
|
||||
public ObservableCollection<ListBoxItem> DubLangList{ get; } = [];
|
||||
|
||||
|
||||
public ObservableCollection<ComboBoxItem> DefaultDubLangList{ get; } =[];
|
||||
public ObservableCollection<ComboBoxItem> DefaultDubLangList{ get; } = [];
|
||||
|
||||
public ObservableCollection<ComboBoxItem> DefaultSubLangList{ get; } =[];
|
||||
public ObservableCollection<ComboBoxItem> DefaultSubLangList{ get; } = [];
|
||||
|
||||
|
||||
public ObservableCollection<ListBoxItem> SubLangList{ get; } =[
|
||||
|
|
@ -277,7 +292,7 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
new(){ Content = "tv/android_tv" },
|
||||
];
|
||||
|
||||
public ObservableCollection<StringItemWithDisplayName> FFmpegHWAccel{ get; } =[];
|
||||
public ObservableCollection<StringItemWithDisplayName> FFmpegHWAccel{ get; } = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private StringItemWithDisplayName _selectedFFmpegHWAccel;
|
||||
|
|
@ -345,7 +360,7 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
ComboBoxItem? defaultSubLang = DefaultSubLangList.FirstOrDefault(a => a.Content != null && (string)a.Content == (options.DefaultSub ?? "")) ?? null;
|
||||
SelectedDefaultSubLang = defaultSubLang ?? DefaultSubLangList[0];
|
||||
|
||||
ComboBoxItem? streamEndpoint = StreamEndpoints.FirstOrDefault(a => a.Content != null && (string)a.Content == (options.StreamEndpoint ?? "")) ?? null;
|
||||
ComboBoxItem? streamEndpoint = StreamEndpoints.FirstOrDefault(a => a.Content != null && (string)a.Content == (options.StreamEndpoint?.Endpoint ?? "")) ?? null;
|
||||
SelectedStreamEndpoint = streamEndpoint ?? StreamEndpoints[0];
|
||||
|
||||
ComboBoxItem? streamEndpointSecondar = StreamEndpointsSecondary.FirstOrDefault(a => a.Content != null && (string)a.Content == (options.StreamEndpointSecondSettings?.Endpoint ?? "")) ?? null;
|
||||
|
|
@ -356,6 +371,11 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
EndpointUserAgent = options.StreamEndpointSecondSettings?.UserAgent ?? string.Empty;
|
||||
EndpointDeviceName = options.StreamEndpointSecondSettings?.Device_name ?? string.Empty;
|
||||
EndpointDeviceType = options.StreamEndpointSecondSettings?.Device_type ?? string.Empty;
|
||||
EndpointVideo = options.StreamEndpointSecondSettings?.Video ?? true;
|
||||
EndpointAudio = options.StreamEndpointSecondSettings?.Audio ?? true;
|
||||
|
||||
FirstEndpointVideo = options.StreamEndpoint?.Video ?? true;
|
||||
FirstEndpointAudio = options.StreamEndpoint?.Audio ?? true;
|
||||
|
||||
if (CrunchyrollManager.Instance.CrAuthEndpoint2.Profile.Username == "???"){
|
||||
EndpointNotSignedWarning = true;
|
||||
|
|
@ -390,6 +410,7 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
AddScaledBorderAndShadow = options.SubsAddScaledBorder is ScaledBorderAndShadowSelection.ScaledBorderAndShadowNo or ScaledBorderAndShadowSelection.ScaledBorderAndShadowYes;
|
||||
SelectedScaledBorderAndShadow = GetScaledBorderAndShadowFromOptions(options);
|
||||
|
||||
HsRawFallback = options.HsRawFallback;
|
||||
FixCccSubtitles = options.FixCccSubtitles;
|
||||
ConvertVtt2Ass = options.ConvertVtt2Ass;
|
||||
SubsDownloadDuplicate = options.SubsDownloadDuplicate;
|
||||
|
|
@ -519,12 +540,16 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
CrunchyrollManager.Instance.CrunOptions.DescriptionLang = descLang != "default" ? descLang : CrunchyrollManager.Instance.DefaultLocale;
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.Hslang = SelectedHSLang.Content + "";
|
||||
CrunchyrollManager.Instance.CrunOptions.HsRawFallback = HsRawFallback;
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.DefaultAudio = SelectedDefaultDubLang.Content + "";
|
||||
CrunchyrollManager.Instance.CrunOptions.DefaultSub = SelectedDefaultSubLang.Content + "";
|
||||
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.StreamEndpoint = SelectedStreamEndpoint.Content + "";
|
||||
var endpointSettingsFirst = new CrAuthSettings();
|
||||
endpointSettingsFirst.Endpoint = SelectedStreamEndpoint.Content + "";
|
||||
endpointSettingsFirst.Video = FirstEndpointVideo;
|
||||
endpointSettingsFirst.Audio = FirstEndpointAudio;
|
||||
CrunchyrollManager.Instance.CrunOptions.StreamEndpoint = endpointSettingsFirst;
|
||||
|
||||
var endpointSettings = new CrAuthSettings();
|
||||
endpointSettings.Endpoint = SelectedStreamEndpointSecondary.Content + "";
|
||||
|
|
@ -533,6 +558,8 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
endpointSettings.UserAgent = EndpointUserAgent;
|
||||
endpointSettings.Device_name = EndpointDeviceName;
|
||||
endpointSettings.Device_type = EndpointDeviceType;
|
||||
endpointSettings.Video = EndpointVideo;
|
||||
endpointSettings.Audio = EndpointAudio;
|
||||
|
||||
|
||||
CrunchyrollManager.Instance.CrunOptions.StreamEndpointSecondSettings = endpointSettings;
|
||||
|
|
@ -657,13 +684,13 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
}
|
||||
}
|
||||
} else{
|
||||
CrunchyrollManager.Instance.HistoryList =[];
|
||||
CrunchyrollManager.Instance.HistoryList = [];
|
||||
}
|
||||
}
|
||||
|
||||
_ = SonarrClient.Instance.RefreshSonarrLite();
|
||||
} else{
|
||||
CrunchyrollManager.Instance.HistoryList =[];
|
||||
CrunchyrollManager.Instance.HistoryList = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -763,7 +790,7 @@ public partial class CrunchyrollSettingsViewModel : ViewModelBase{
|
|||
}
|
||||
|
||||
|
||||
return[];
|
||||
return [];
|
||||
}
|
||||
|
||||
private List<StringItemWithDisplayName> MapHWAccelOptions(List<string> accels){
|
||||
|
|
|
|||
|
|
@ -75,6 +75,12 @@
|
|||
</ComboBox>
|
||||
</controls:SettingsExpander.Footer>
|
||||
|
||||
<controls:SettingsExpanderItem Content="No hardsubs fallback" Description="If no hardsubs are available, automatically download the no-hardsub (raw) video.">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<CheckBox IsChecked="{Binding HsRawFallback}"> </CheckBox>
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
</controls:SettingsExpander>
|
||||
|
||||
|
||||
|
|
@ -249,12 +255,27 @@
|
|||
</controls:SettingsExpanderItem>
|
||||
|
||||
|
||||
<controls:SettingsExpanderItem Content="Stream Endpoint " IsEnabled="False">
|
||||
<controls:SettingsExpanderItem Content="Stream Endpoint ">
|
||||
<controls:SettingsExpanderItem.Footer>
|
||||
<ComboBox HorizontalContentAlignment="Center" MinWidth="210" MaxDropDownHeight="400"
|
||||
ItemsSource="{Binding StreamEndpoints}"
|
||||
SelectedItem="{Binding SelectedStreamEndpoint}">
|
||||
</ComboBox>
|
||||
<StackPanel>
|
||||
<ComboBox IsEnabled="False" HorizontalContentAlignment="Center" MinWidth="210" MaxDropDownHeight="400"
|
||||
ItemsSource="{Binding StreamEndpoints}"
|
||||
SelectedItem="{Binding SelectedStreamEndpoint}">
|
||||
</ComboBox>
|
||||
|
||||
<StackPanel Margin="0,5" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<TextBlock Text="Video" Margin="0 , 0 , 5 , 0" VerticalAlignment="Center"/>
|
||||
<CheckBox HorizontalAlignment="Left" MinWidth="250" VerticalAlignment="Center"
|
||||
IsChecked="{Binding FirstEndpointVideo}" />
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0,5" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<TextBlock Text="Audio" Margin="0 , 0 , 5 , 0" VerticalAlignment="Center"/>
|
||||
<CheckBox HorizontalAlignment="Left" MinWidth="250" VerticalAlignment="Center"
|
||||
IsChecked="{Binding FirstEndpointAudio}" />
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
</controls:SettingsExpanderItem.Footer>
|
||||
</controls:SettingsExpanderItem>
|
||||
|
||||
|
|
@ -266,33 +287,44 @@
|
|||
SelectedItem="{Binding SelectedStreamEndpointSecondary}">
|
||||
</ComboBox>
|
||||
|
||||
<StackPanel Margin="0,5" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<TextBlock Text="Video" Margin="0 , 0 , 5 , 0" VerticalAlignment="Center"/>
|
||||
<CheckBox HorizontalAlignment="Left" MinWidth="250" VerticalAlignment="Center"
|
||||
IsChecked="{Binding EndpointVideo}" />
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0,5" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<TextBlock Text="Audio" Margin="0 , 0 , 5 , 0" VerticalAlignment="Center"/>
|
||||
<CheckBox HorizontalAlignment="Left" MinWidth="250" VerticalAlignment="Center"
|
||||
IsChecked="{Binding EndpointAudio}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Margin="0,5">
|
||||
<TextBlock Text="Authorization" />
|
||||
<TextBox Name="AuthorizationTextBox" HorizontalAlignment="Left" MinWidth="250"
|
||||
<TextBox Name="AuthorizationTextBox" HorizontalAlignment="Left" MinWidth="250" MaxWidth="250" TextWrapping="Wrap"
|
||||
Text="{Binding EndpointAuthorization}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Margin="0,5">
|
||||
<TextBlock Text="Client Id" />
|
||||
<TextBox Name="ClientIdTextBox" HorizontalAlignment="Left" MinWidth="250"
|
||||
<TextBox Name="ClientIdTextBox" HorizontalAlignment="Left" MinWidth="250" MaxWidth="250" TextWrapping="Wrap"
|
||||
Text="{Binding EndpointClientId}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Margin="0,5">
|
||||
<TextBlock Text="User Agent" />
|
||||
<TextBox Name="UserAgentTextBox" HorizontalAlignment="Left" MinWidth="250"
|
||||
<TextBox Name="UserAgentTextBox" HorizontalAlignment="Left" MinWidth="250" MaxWidth="250" TextWrapping="Wrap"
|
||||
Text="{Binding EndpointUserAgent}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Margin="0,5">
|
||||
<TextBlock Text="Device Type" />
|
||||
<TextBox Name="DeviceTypeTextBox" HorizontalAlignment="Left" MinWidth="250"
|
||||
<TextBox Name="DeviceTypeTextBox" HorizontalAlignment="Left" MinWidth="250" MaxWidth="250" TextWrapping="Wrap"
|
||||
Text="{Binding EndpointDeviceType}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Margin="0,5">
|
||||
<TextBlock Text="Device Name" />
|
||||
<TextBox Name="DeviceNameTextBox" HorizontalAlignment="Left" MinWidth="250"
|
||||
<TextBox Name="DeviceNameTextBox" HorizontalAlignment="Left" MinWidth="250" MaxWidth="250" TextWrapping="Wrap"
|
||||
Text="{Binding EndpointDeviceName}" />
|
||||
</StackPanel>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using Avalonia;
|
||||
using System.Linq;
|
||||
using ReactiveUI.Avalonia;
|
||||
|
||||
namespace CRD;
|
||||
|
||||
|
|
@ -26,7 +27,8 @@ sealed class Program{
|
|||
var builder = AppBuilder.Configure<App>()
|
||||
.UsePlatformDetect()
|
||||
.WithInterFont()
|
||||
.LogToTrace();
|
||||
.LogToTrace()
|
||||
.UseReactiveUI() ;
|
||||
|
||||
if (isHeadless){
|
||||
Console.WriteLine("Running in headless mode...");
|
||||
|
|
|
|||
|
|
@ -73,10 +73,12 @@ public class Helpers{
|
|||
}
|
||||
|
||||
public static int ToKbps(int bps) => (int)Math.Round(bps / 1000.0);
|
||||
|
||||
public static int SnapToAudioBucket(int kbps){
|
||||
int[] buckets ={ 64, 96, 128, 192,256 };
|
||||
int[] buckets = { 64, 96, 128, 192, 256 };
|
||||
return buckets.OrderBy(b => Math.Abs(b - kbps)).First();
|
||||
}
|
||||
|
||||
public static int WidthBucket(int width, int height){
|
||||
int expected = (int)Math.Round(height * 16 / 9.0);
|
||||
int tol = Math.Max(8, (int)(expected * 0.02)); // ~2% or ≥8 px
|
||||
|
|
@ -555,7 +557,7 @@ public class Helpers{
|
|||
return CosineSimilarity(vector1, vector2);
|
||||
}
|
||||
|
||||
private static readonly char[] Delimiters ={ ' ', ',', '.', ';', ':', '-', '_', '\'' };
|
||||
private static readonly char[] Delimiters = { ' ', ',', '.', ';', ':', '-', '_', '\'' };
|
||||
|
||||
public static Dictionary<string, double> ComputeWordFrequency(string text){
|
||||
var wordFrequency = new Dictionary<string, double>(StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -718,7 +720,7 @@ public class Helpers{
|
|||
bool isValid = !folderName.Any(c => invalidChars.Contains(c));
|
||||
|
||||
// Check for reserved names on Windows
|
||||
string[] reservedNames =["CON", "PRN", "AUX", "NUL", "COM1", "LPT1"];
|
||||
string[] reservedNames = ["CON", "PRN", "AUX", "NUL", "COM1", "LPT1"];
|
||||
bool isReservedName = reservedNames.Contains(folderName.ToUpperInvariant());
|
||||
|
||||
if (isValid && !isReservedName && folderName.Length <= 255){
|
||||
|
|
@ -847,28 +849,39 @@ public class Helpers{
|
|||
|
||||
public static void MergePlaylistData(
|
||||
Dictionary<string, ServerData> target,
|
||||
Dictionary<string, ServerData> source){
|
||||
Dictionary<string, ServerData> source,
|
||||
bool mergeAudio,
|
||||
bool mergeVideo){
|
||||
foreach (var kvp in source){
|
||||
if (target.TryGetValue(kvp.Key, out var existing)){
|
||||
// Merge audio
|
||||
existing.audio ??=[];
|
||||
if (kvp.Value.audio != null)
|
||||
existing.audio.AddRange(kvp.Value.audio);
|
||||
var key = kvp.Key;
|
||||
var src = kvp.Value;
|
||||
|
||||
// Merge video
|
||||
existing.video ??=[];
|
||||
if (kvp.Value.video != null)
|
||||
existing.video.AddRange(kvp.Value.video);
|
||||
if (target.TryGetValue(key, out var existing)){
|
||||
if (mergeAudio){
|
||||
existing.audio ??= [];
|
||||
if (src.audio != null)
|
||||
existing.audio.AddRange(src.audio);
|
||||
}
|
||||
|
||||
if (mergeVideo){
|
||||
existing.video ??= [];
|
||||
if (src.video != null)
|
||||
existing.video.AddRange(src.video);
|
||||
}
|
||||
} else{
|
||||
// Add new entry (clone lists to avoid reference issues)
|
||||
target[kvp.Key] = new ServerData{
|
||||
audio = kvp.Value.audio != null ? new List<AudioPlaylist>(kvp.Value.audio) : new List<AudioPlaylist>(),
|
||||
video = kvp.Value.video != null ? new List<VideoPlaylist>(kvp.Value.video) : new List<VideoPlaylist>()
|
||||
target[key] = new ServerData{
|
||||
audio = (mergeAudio && src.audio != null)
|
||||
? new List<AudioPlaylist>(src.audio)
|
||||
: new List<AudioPlaylist>(),
|
||||
video = (mergeVideo && src.video != null)
|
||||
? new List<VideoPlaylist>(src.video)
|
||||
: new List<VideoPlaylist>()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static readonly SemaphoreSlim ShutdownLock = new(1, 1);
|
||||
|
||||
public static async Task ShutdownComputer(){
|
||||
|
|
|
|||
|
|
@ -270,6 +270,7 @@ public static class ApiUrls{
|
|||
|
||||
public static string Auth => (CrunchyrollManager.Instance.CrunOptions.UseCrBetaApi ? ApiBeta : ApiN) + "/auth/v1/token";
|
||||
public static string Profile => (CrunchyrollManager.Instance.CrunOptions.UseCrBetaApi ? ApiBeta : ApiN) + "/accounts/v1/me/profile";
|
||||
public static string Profiles => (CrunchyrollManager.Instance.CrunOptions.UseCrBetaApi ? ApiBeta : ApiN) + "/accounts/v1/me/multiprofile";
|
||||
public static string CmsToken => (CrunchyrollManager.Instance.CrunOptions.UseCrBetaApi ? ApiBeta : ApiN) + "/index/v2";
|
||||
public static string Search => (CrunchyrollManager.Instance.CrunOptions.UseCrBetaApi ? ApiBeta : ApiN) + "/content/v2/discover/search";
|
||||
public static string Browse => (CrunchyrollManager.Instance.CrunOptions.UseCrBetaApi ? ApiBeta : ApiN) + "/content/v2/discover/browse";
|
||||
|
|
|
|||
|
|
@ -16,9 +16,6 @@ public class Merger{
|
|||
|
||||
public Merger(MergerOptions options){
|
||||
this.options = options;
|
||||
if (this.options.SkipSubMux != null && this.options.SkipSubMux == true){
|
||||
this.options.Subtitles = new();
|
||||
}
|
||||
|
||||
if (this.options.VideoTitle != null && this.options.VideoTitle.Length > 0){
|
||||
this.options.VideoTitle = this.options.VideoTitle.Replace("\"", "'");
|
||||
|
|
@ -74,35 +71,39 @@ public class Merger{
|
|||
index++;
|
||||
}
|
||||
|
||||
bool hasSignsSub = options.Subtitles.Any(sub => sub.Signs && options.Defaults.Sub.Code == sub.Language.Code);
|
||||
if (!options.SkipSubMux){
|
||||
bool hasSignsSub = options.Subtitles.Any(sub => sub.Signs && options.Defaults.Sub.Code == sub.Language.Code);
|
||||
|
||||
foreach (var sub in options.Subtitles.Select((value, i) => new{ value, i })){
|
||||
if (sub.value.Delay != null && sub.value.Delay != 0){
|
||||
double delay = sub.value.Delay / 1000.0 ?? 0;
|
||||
args.Add($"-itsoffset {delay.ToString(CultureInfo.InvariantCulture)}");
|
||||
foreach (var sub in options.Subtitles.Select((value, i) => new{ value, i })){
|
||||
if (sub.value.Delay != null && sub.value.Delay != 0){
|
||||
double delay = sub.value.Delay / 1000.0 ?? 0;
|
||||
args.Add($"-itsoffset {delay.ToString(CultureInfo.InvariantCulture)}");
|
||||
}
|
||||
|
||||
args.Add($"-i \"{sub.value.File}\"");
|
||||
metaData.Add($"-map {index}:s");
|
||||
if (options.Defaults.Sub.Code == sub.value.Language.Code &&
|
||||
(options.DefaultSubSigns == sub.value.Signs || options.DefaultSubSigns && !hasSignsSub)
|
||||
&& sub.value.ClosedCaption == false){
|
||||
metaData.Add($"-disposition:s:{sub.i} default");
|
||||
} else{
|
||||
metaData.Add($"-disposition:s:{sub.i} 0");
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
args.Add($"-i \"{sub.value.File}\"");
|
||||
metaData.Add($"-map {index}:s");
|
||||
if (options.Defaults.Sub.Code == sub.value.Language.Code &&
|
||||
(options.DefaultSubSigns == sub.value.Signs || options.DefaultSubSigns && !hasSignsSub)
|
||||
&& sub.value.ClosedCaption == false){
|
||||
metaData.Add($"-disposition:s:{sub.i} default");
|
||||
} else{
|
||||
metaData.Add($"-disposition:s:{sub.i} 0");
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
|
||||
args.AddRange(metaData);
|
||||
// args.AddRange(options.Subtitles.Select((sub, subIndex) => $"-map {subIndex + index}"));
|
||||
args.Add("-c:v copy");
|
||||
args.Add("-c:a copy");
|
||||
args.Add(options.Output.EndsWith(".mp4", StringComparison.OrdinalIgnoreCase) ? "-c:s mov_text" : "-c:s ass");
|
||||
args.AddRange(options.Subtitles.Select((sub, subindex) =>
|
||||
$"-metadata:s:s:{subindex} title=\"{sub.Language.Language ?? sub.Language.Name}{(sub.ClosedCaption == true ? $" {options.CcTag}" : "")}{(sub.Signs == true ? " Signs" : "")}\" -metadata:s:s:{subindex} language={sub.Language.Code}"));
|
||||
|
||||
if (!options.SkipSubMux){
|
||||
args.AddRange(options.Subtitles.Select((sub, subindex) =>
|
||||
$"-metadata:s:s:{subindex} title=\"{sub.Language.Language ?? sub.Language.Name}{(sub.ClosedCaption == true ? $" {options.CcTag}" : "")}{(sub.Signs == true ? " Signs" : "")}\" -metadata:s:s:{subindex} language={sub.Language.Code}"));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(options.VideoTitle)){
|
||||
args.Add($"-metadata title=\"{options.VideoTitle}\"");
|
||||
|
|
@ -134,9 +135,9 @@ public class Merger{
|
|||
}
|
||||
|
||||
var audio = options.OnlyAudio.First();
|
||||
|
||||
|
||||
args.Add($"-i \"{audio.Path}\"");
|
||||
args.Add("-c:a libmp3lame" + (audio.Bitrate > 0 ? $" -b:a {audio.Bitrate}k" : "") );
|
||||
args.Add("-c:a libmp3lame" + (audio.Bitrate > 0 ? $" -b:a {audio.Bitrate}k" : ""));
|
||||
args.Add($"\"{options.Output}\"");
|
||||
return string.Join(" ", args);
|
||||
}
|
||||
|
|
@ -170,7 +171,7 @@ public class Merger{
|
|||
// var sortedAudio = options.OnlyAudio
|
||||
// .OrderBy(sub => options.DubLangList.IndexOf(sub.Language.CrLocale) != -1 ? options.DubLangList.IndexOf(sub.Language.CrLocale) : int.MaxValue)
|
||||
// .ToList();
|
||||
|
||||
|
||||
var rank = options.DubLangList
|
||||
.Select((val, i) => new{ val, i })
|
||||
.ToDictionary(x => x.val, x => x.i, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -204,7 +205,7 @@ public class Merger{
|
|||
args.Add($"\"{Helpers.AddUncPrefixIfNeeded(aud.Path)}\"");
|
||||
}
|
||||
|
||||
if (options.Subtitles.Count > 0){
|
||||
if (options.Subtitles.Count > 0 && !options.SkipSubMux){
|
||||
bool hasSignsSub = options.Subtitles.Any(sub => sub.Signs && options.Defaults.Sub.Code == sub.Language.Code);
|
||||
|
||||
var sortedSubtitles = options.Subtitles
|
||||
|
|
@ -274,7 +275,7 @@ 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}\"");
|
||||
|
|
@ -446,14 +447,16 @@ public class Merger{
|
|||
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));
|
||||
|
||||
// Delete subtitle files
|
||||
options.Subtitles.ForEach(subtitle => Helpers.DeleteFile(subtitle.File));
|
||||
if (!options.SkipSubMux){
|
||||
// Delete subtitle files
|
||||
options.Subtitles.ForEach(subtitle => Helpers.DeleteFile(subtitle.File));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -486,7 +489,7 @@ public class CrunchyMuxOptions{
|
|||
public List<string> DubLangList{ get; set; } = new List<string>();
|
||||
public List<string> SubLangList{ get; set; } = new List<string>();
|
||||
public string Output{ get; set; }
|
||||
public bool? SkipSubMux{ get; set; }
|
||||
public bool SkipSubMux{ get; set; }
|
||||
public bool? KeepAllVideos{ get; set; }
|
||||
public bool? Novids{ get; set; }
|
||||
public bool Mp4{ get; set; }
|
||||
|
|
@ -524,7 +527,7 @@ public class MergerOptions{
|
|||
public string VideoTitle{ get; set; }
|
||||
public bool? KeepAllVideos{ get; set; }
|
||||
public List<ParsedFont> Fonts{ get; set; } = new List<ParsedFont>();
|
||||
public bool? SkipSubMux{ get; set; }
|
||||
public bool SkipSubMux{ get; set; }
|
||||
public MuxOptions Options{ get; set; }
|
||||
public Defaults Defaults{ get; set; }
|
||||
public bool mp3{ get; set; }
|
||||
|
|
|
|||
|
|
@ -150,6 +150,9 @@ public class CrDownloadOptions{
|
|||
|
||||
[JsonProperty("hard_sub_lang")]
|
||||
public string Hslang{ get; set; } = "";
|
||||
|
||||
[JsonProperty("hard_sub_raw_fallback")]
|
||||
public bool HsRawFallback{ get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public int Kstream{ get; set; }
|
||||
|
|
@ -304,8 +307,8 @@ public class CrDownloadOptions{
|
|||
[JsonProperty("calendar_show_upcoming_episodes")]
|
||||
public bool CalendarShowUpcomingEpisodes{ get; set; }
|
||||
|
||||
[JsonProperty("stream_endpoint")]
|
||||
public string? StreamEndpoint{ get; set; }
|
||||
[JsonProperty("stream_endpoint_settings")]
|
||||
public CrAuthSettings? StreamEndpoint{ get; set; }
|
||||
|
||||
[JsonProperty("stream_endpoint_secondary_settings")]
|
||||
public CrAuthSettings? StreamEndpointSecondSettings{ get; set; }
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ public class CrProfile{
|
|||
[JsonProperty("profile_name")]
|
||||
public string? ProfileName{ get; set; }
|
||||
|
||||
[JsonProperty("profile_id")]
|
||||
public string? ProfileId{ get; set; }
|
||||
|
||||
[JsonProperty("preferred_content_audio_language")]
|
||||
public string? PreferredContentAudioLanguage{ get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ public class StreamDetails{
|
|||
public class UrlWithAuth{
|
||||
|
||||
public CrAuth? CrAuth{ get; set; }
|
||||
public bool Video{ get; set; }
|
||||
public bool Audio{ get; set; }
|
||||
|
||||
public string? Url{ get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,14 @@ public class CrAuthSettings{
|
|||
public string Device_type{ get; set; }
|
||||
public string Device_name{ get; set; }
|
||||
|
||||
public bool Video{ get; set; }
|
||||
public bool Audio{ get; set; }
|
||||
}
|
||||
|
||||
public class StreamInfo{
|
||||
public string Playlist { get; set; }
|
||||
public bool Audio { get; set; }
|
||||
public bool Video { get; set; }
|
||||
}
|
||||
|
||||
public class DrmAuthData{
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ A simple crunchyroll downloader that allows you to download your favorite series
|
|||
## 🛠️ System Requirements
|
||||
|
||||
- **Operating System:** Windows 10 or Windows 11
|
||||
- **.NET Desktop Runtime:** Version 8.0
|
||||
- **.NET Desktop Runtime:** Version 10.0
|
||||
- **Visual C++ Redistributable:** 2015–2022
|
||||
|
||||
## 🖥️ Features
|
||||
|
|
|
|||
Loading…
Reference in a new issue