From 9e975062dc2c03866b8ae3523e4ef929bbc19e5e Mon Sep 17 00:00:00 2001 From: Elwador <75888166+Elwador@users.noreply.github.com> Date: Thu, 6 Jun 2024 03:08:13 +0200 Subject: [PATCH] Chg - Muxing now doesn't overwrite existing files Chg - A bit more information why the download failed Fix - Hotfix DRM downloads --- CRD/Downloader/Crunchyroll.cs | 172 +++++++++++++----- CRD/Utils/Http/HttpClientReq.cs | 15 +- ...chyNoDRMStream.cs => CrunchyStreamData.cs} | 13 +- CRD/Utils/Structs/Structs.cs | 1 + 4 files changed, 142 insertions(+), 59 deletions(-) rename CRD/Utils/Structs/{CrunchyNoDRMStream.cs => CrunchyStreamData.cs} (85%) diff --git a/CRD/Downloader/Crunchyroll.cs b/CRD/Downloader/Crunchyroll.cs index 10ccb7d..6a0d6d7 100644 --- a/CRD/Downloader/Crunchyroll.cs +++ b/CRD/Downloader/Crunchyroll.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; @@ -187,7 +187,7 @@ public class Crunchyroll{ CrHistory.MatchHistorySeriesWithSonarr(true); } } - + public async Task GetCalendarForDate(string weeksMondayDate, bool forceUpdate){ if (!forceUpdate && calendar.TryGetValue(weeksMondayDate, out var forDate)){ @@ -346,7 +346,7 @@ public class Crunchyroll{ Percent = 100, Time = 0, DownloadSpeed = 0, - Doing = "Download Error" + Doing = "Download Error" + (!string.IsNullOrEmpty(res.ErrorText) ? " - " + res.ErrorText : ""), }; Queue.Refresh(); return false; @@ -362,7 +362,7 @@ public class Crunchyroll{ }; Queue.Refresh(); - + await MuxStreams(res.Data, new CrunchyMuxOptions{ FfmpegOptions = options.FfmpegOptions, @@ -434,6 +434,19 @@ public class Crunchyroll{ subt.Fonts = downloadedMedia.Fonts; subsList.Add(subt); } + + if (File.Exists($"{filename}.{(muxToMp3 ? "mp3" : options.Mp4 ? "mp4" : "mkv")}") && !string.IsNullOrEmpty(filename)){ + string newFilePath = filename; + int counter = 1; + + while (File.Exists($"{newFilePath}.{(muxToMp3 ? "mp3" : options.Mp4 ? "mp4" : "mkv")}")){ + newFilePath = filename + $"({counter})"; + counter++; + } + + filename = newFilePath; + } + var merger = new Merger(new MergerOptions{ OnlyVid = hasAudioStreams ? data.Where(a => a.Type == DownloadMediaType.Video).Select(a => new MergerInput{ Language = a.Lang, Path = a.Path ?? string.Empty }).ToList() : new List(), @@ -493,7 +506,8 @@ public class Crunchyroll{ return new DownloadResponse{ Data = new List(), Error = true, - FileName = "./unknown" + FileName = "./unknown", + ErrorText = "Login problem" }; } @@ -502,7 +516,8 @@ public class Crunchyroll{ return new DownloadResponse{ Data = new List(), Error = true, - FileName = "./unknown" + FileName = "./unknown", + ErrorText = "Login problem" }; } @@ -512,7 +527,8 @@ public class Crunchyroll{ return new DownloadResponse{ Data = new List(), Error = true, - FileName = "./unknown" + FileName = "./unknown", + ErrorText = "Missing ffmpeg" }; } @@ -538,7 +554,8 @@ public class Crunchyroll{ return new DownloadResponse{ Data = files, Error = true, - FileName = fileName.Length > 0 ? (Path.IsPathRooted(fileName) ? fileName : Path.Combine(CfgManager.PathVIDEOS_DIR, fileName)) : "./unknown" + FileName = fileName.Length > 0 ? (Path.IsPathRooted(fileName) ? fileName : Path.Combine(CfgManager.PathVIDEOS_DIR, fileName)) : "./unknown", + ErrorText = "Video Data not found" }; } @@ -608,14 +625,15 @@ public class Crunchyroll{ #endregion - var fetchPlaybackData = await FetchPlaybackData(mediaId, epMeta); + var fetchPlaybackData = await FetchPlaybackData(mediaId, mediaGuid, epMeta); if (!fetchPlaybackData.IsOk){ MainWindow.Instance.ShowError("Couldn't get Playback Data"); return new DownloadResponse{ Data = new List(), Error = true, - FileName = "./unknown" + FileName = "./unknown", + ErrorText = "Playback data not found" }; } @@ -640,7 +658,7 @@ public class Crunchyroll{ variables.Add(new Variable("seriesTitle", data.SeriesTitle ?? string.Empty, true)); variables.Add(new Variable("showTitle", data.SeasonTitle ?? string.Empty, true)); variables.Add(new Variable("season", data.Season != null ? Math.Round(double.Parse(data.Season, CultureInfo.InvariantCulture), 1) : 0, false)); - + if (pbStreams?.Keys != null){ foreach (var key in pbStreams.Keys){ if ((key.Contains("hls") || key.Contains("dash")) && @@ -675,7 +693,8 @@ public class Crunchyroll{ return new DownloadResponse{ Data = new List(), Error = true, - FileName = "./unknown" + FileName = "./unknown", + ErrorText = "Streams not found" }; } @@ -754,6 +773,12 @@ public class Crunchyroll{ if (!streamPlaylistsReqResponse.IsOk){ dlFailed = true; + return new DownloadResponse{ + Data = new List(), + Error = true, + FileName = "./unknown", + ErrorText = "Playlist fetch problem" + }; } if (dlFailed){ @@ -777,7 +802,8 @@ public class Crunchyroll{ return new DownloadResponse{ Data = new List(), Error = true, - FileName = "./unknown" + FileName = "./unknown", + ErrorText = "No stream servers found" }; } @@ -880,7 +906,8 @@ public class Crunchyroll{ return new DownloadResponse{ Data = new List(), Error = true, - FileName = "./unknown" + FileName = "./unknown", + ErrorText = "Language not found" }; } @@ -934,7 +961,8 @@ public class Crunchyroll{ return new DownloadResponse{ Data = files, Error = dlFailed, - FileName = fileName.Length > 0 ? (Path.IsPathRooted(fileName) ? fileName : Path.Combine(CfgManager.PathVIDEOS_DIR, fileName)) : "./unknown" + FileName = fileName.Length > 0 ? (Path.IsPathRooted(fileName) ? fileName : Path.Combine(CfgManager.PathVIDEOS_DIR, fileName)) : "./unknown", + ErrorText = "" }; } @@ -974,7 +1002,8 @@ public class Crunchyroll{ return new DownloadResponse{ Data = files, Error = dlFailed, - FileName = fileName.Length > 0 ? (Path.IsPathRooted(fileName) ? fileName : Path.Combine(CfgManager.PathVIDEOS_DIR, fileName)) : "./unknown" + FileName = fileName.Length > 0 ? (Path.IsPathRooted(fileName) ? fileName : Path.Combine(CfgManager.PathVIDEOS_DIR, fileName)) : "./unknown", + ErrorText = "DRM Authentication failed" }; } @@ -991,7 +1020,8 @@ public class Crunchyroll{ return new DownloadResponse{ Data = files, Error = dlFailed, - FileName = fileName.Length > 0 ? (Path.IsPathRooted(fileName) ? fileName : Path.Combine(CfgManager.PathVIDEOS_DIR, fileName)) : "./unknown" + FileName = fileName.Length > 0 ? (Path.IsPathRooted(fileName) ? fileName : Path.Combine(CfgManager.PathVIDEOS_DIR, fileName)) : "./unknown", + ErrorText = "Couldn't get DRM encryption keys" }; } @@ -1029,20 +1059,23 @@ public class Crunchyroll{ Console.WriteLine($"Failed to delete file {tempTsFile}.video.enc.m4s. Error: {ex.Message}"); // Handle exceptions if you need to log them or throw } - - try{ - File.Move($"{tempTsFile}.video.m4s", $"{tsFile}.video.m4s"); - } catch (IOException ex){ - Console.WriteLine($"An error occurred: {ex.Message}"); - } - - files.Add(new DownloadedMedia{ - Type = DownloadMediaType.Video, - Path = $"{tsFile}.video.m4s", - Lang = lang.Value, - IsPrimary = isPrimary - }); } + + try{ + if (File.Exists($"{tsFile}.video.m4s")){ + File.Delete($"{tsFile}.video.m4s"); + } + File.Move($"{tempTsFile}.video.m4s", $"{tsFile}.video.m4s"); + } catch (IOException ex){ + Console.WriteLine($"An error occurred: {ex.Message}"); + } + + files.Add(new DownloadedMedia{ + Type = DownloadMediaType.Video, + Path = $"{tsFile}.video.m4s", + Lang = lang.Value, + IsPrimary = isPrimary + }); } } @@ -1072,20 +1105,23 @@ public class Crunchyroll{ Console.WriteLine($"Failed to delete file {tempTsFile}.audio.enc.m4s. Error: {ex.Message}"); // Handle exceptions if you need to log them or throw } - - try{ - File.Move($"{tempTsFile}.audio.m4s", $"{tsFile}.audio.m4s"); - } catch (IOException ex){ - Console.WriteLine($"An error occurred: {ex.Message}"); - } - - files.Add(new DownloadedMedia{ - Type = DownloadMediaType.Audio, - Path = $"{tsFile}.audio.m4s", - Lang = lang.Value, - IsPrimary = isPrimary - }); } + + try{ + if (File.Exists($"{tsFile}.audio.m4s")){ + File.Delete($"{tsFile}.audio.m4s"); + } + File.Move($"{tempTsFile}.audio.m4s", $"{tsFile}.audio.m4s"); + } catch (IOException ex){ + Console.WriteLine($"An error occurred: {ex.Message}"); + } + + files.Add(new DownloadedMedia{ + Type = DownloadMediaType.Audio, + Path = $"{tsFile}.audio.m4s", + Lang = lang.Value, + IsPrimary = isPrimary + }); } } } else{ @@ -1198,7 +1234,8 @@ public class Crunchyroll{ return new DownloadResponse{ Data = files, Error = dlFailed, - FileName = fileName.Length > 0 ? (Path.IsPathRooted(fileName) ? fileName : Path.Combine(CfgManager.PathVIDEOS_DIR, fileName)) : "./unknown" + FileName = fileName.Length > 0 ? (Path.IsPathRooted(fileName) ? fileName : Path.Combine(CfgManager.PathVIDEOS_DIR, fileName)) : "./unknown", + ErrorText = "" }; } @@ -1428,7 +1465,7 @@ public class Crunchyroll{ var playbackRequestNonDrmResponse = await HttpClientReq.Instance.SendHttpRequest(playbackRequestNonDrm); if (playbackRequestNonDrmResponse.IsOk && playbackRequestNonDrmResponse.ResponseContent != string.Empty){ - CrunchyNoDrmStream? playStream = JsonConvert.DeserializeObject(playbackRequestNonDrmResponse.ResponseContent, SettingsJsonSerializerSettings); + CrunchyStreamData? playStream = JsonConvert.DeserializeObject(playbackRequestNonDrmResponse.ResponseContent, SettingsJsonSerializerSettings); CrunchyStreams derivedPlayCrunchyStreams = new CrunchyStreams(); if (playStream != null){ var deauthVideoToken = HttpClientReq.CreateRequestMessage($"https://cr-play-service.prd.crunchyrollsvc.com/v1/token/{currentMediaId}/{playStream.Token}/inactive", HttpMethod.Patch, true, false, null); @@ -1455,7 +1492,7 @@ public class Crunchyroll{ } } - private async Task<(bool IsOk, PlaybackData pbData)> FetchPlaybackData(string mediaId, CrunchyEpMetaData epMeta){ + private async Task<(bool IsOk, PlaybackData pbData)> FetchPlaybackData(string mediaId, string mediaGuidId, CrunchyEpMetaData epMeta){ PlaybackData temppbData = new PlaybackData{ Total = 0, Data = new List>>() }; bool ok = true; @@ -1506,13 +1543,50 @@ public class Crunchyroll{ } } } else{ - playbackRequest = HttpClientReq.CreateRequestMessage($"{Api.Cms}/videos/{mediaId}/streams", HttpMethod.Get, true, false, null); + // var playbackRequest22 = HttpClientReq.CreateRequestMessage($"{Api.Cms}/videos/{mediaId}/streams", HttpMethod.Get, true, false, null); + // + // var playbackRequestResponse22 = await HttpClientReq.Instance.SendHttpRequest(playbackRequest22); + + playbackRequest = HttpClientReq.CreateRequestMessage($"https://cr-play-service.prd.crunchyrollsvc.com/v1/{mediaGuidId}/web/firefox/play", HttpMethod.Get, true, false, null); playbackRequestResponse = await HttpClientReq.Instance.SendHttpRequest(playbackRequest); if (playbackRequestResponse.IsOk){ - temppbData = Helpers.Deserialize(playbackRequestResponse.ResponseContent, SettingsJsonSerializerSettings) ?? - new PlaybackData{ Total = 0, Data = new List>>() }; + // temppbData = Helpers.Deserialize(playbackRequestResponse22.ResponseContent, SettingsJsonSerializerSettings) ?? + // new PlaybackData{ Total = 0, Data = new List>>() }; + + temppbData = new PlaybackData{ Total = 0, Data = new List>>() }; + temppbData.Data.Add(new Dictionary>()); + + CrunchyStreamData? playStream = JsonConvert.DeserializeObject(playbackRequestResponse.ResponseContent, SettingsJsonSerializerSettings); + CrunchyStreams derivedPlayCrunchyStreams = new CrunchyStreams(); + if (playStream != null){ + var deauthVideoToken = HttpClientReq.CreateRequestMessage($"https://cr-play-service.prd.crunchyrollsvc.com/v1/token/{mediaGuidId}/{playStream.Token}/inactive", HttpMethod.Patch, true, false, null); + var deauthVideoTokenResponse = await HttpClientReq.Instance.SendHttpRequest(deauthVideoToken); + + if (playStream.HardSubs != null) + foreach (var hardsub in playStream.HardSubs){ + var stream = hardsub.Value; + derivedPlayCrunchyStreams[hardsub.Key] = new StreamDetails{ + Url = stream.Url, + HardsubLocale = Helpers.ConvertStringToLocale(stream.Hlang) + }; + } + + derivedPlayCrunchyStreams[""] = new StreamDetails{ + Url = playStream.Url, + HardsubLocale = Locale.DefaulT + }; + + if (temppbData.Data != null) temppbData.Data[0]["drm_adaptive_dash"] = derivedPlayCrunchyStreams; + + temppbData.Meta = new PlaybackMeta(){ AudioLocale = playStream.AudioLocale, Versions = playStream.Versions, Bifs = new List{ playStream.Bifs }, MediaId = mediaId }; + temppbData.Meta.Subtitles = new Subtitles(); + foreach (var playStreamSubtitle in playStream.Subtitles){ + Subtitle sub = playStreamSubtitle.Value; + temppbData.Meta.Subtitles.Add(playStreamSubtitle.Key, new SubtitleInfo(){ Format = sub.Format, Locale = sub.Locale, Url = sub.Url }); + } + } } else{ Console.WriteLine("Request Stream URLs FAILED! Attempting fallback"); diff --git a/CRD/Utils/Http/HttpClientReq.cs b/CRD/Utils/Http/HttpClientReq.cs index 6eb46cd..26ec3bd 100644 --- a/CRD/Utils/Http/HttpClientReq.cs +++ b/CRD/Utils/Http/HttpClientReq.cs @@ -43,8 +43,9 @@ public class HttpClientReq{ // Initialize the HttpClient with the handler client = new HttpClient(handler); - - client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0"); + + // client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0"); + client.DefaultRequestHeaders.UserAgent.ParseAdd("Crunchyroll/1.8.0 Nintendo Switch/12.3.12.0 UE4/4.27"); // // Set Accept headers // client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/html")); @@ -85,16 +86,19 @@ public class HttpClientReq{ } public async Task<(bool IsOk, string ResponseContent)> SendHttpRequest(HttpRequestMessage request){ + + string content = string.Empty; try{ HttpResponseMessage response = await client.SendAsync(request); + content = await response.Content.ReadAsStringAsync(); + response.EnsureSuccessStatusCode(); - - string content = await response.Content.ReadAsStringAsync(); + return (IsOk: true, ResponseContent: content); } catch (Exception e){ Console.WriteLine(e); - return (IsOk: false, ResponseContent: String.Empty); + return (IsOk: false, ResponseContent: content); } } @@ -113,6 +117,7 @@ public class HttpClientReq{ if (disableDrmHeader){ request.Headers.Add("X-Cr-Disable-Drm", "true"); + request.Headers.Add("x-cr-stream-limits", "false"); } diff --git a/CRD/Utils/Structs/CrunchyNoDRMStream.cs b/CRD/Utils/Structs/CrunchyStreamData.cs similarity index 85% rename from CRD/Utils/Structs/CrunchyNoDRMStream.cs rename to CRD/Utils/Structs/CrunchyStreamData.cs index cf4fe93..335faa6 100644 --- a/CRD/Utils/Structs/CrunchyNoDRMStream.cs +++ b/CRD/Utils/Structs/CrunchyStreamData.cs @@ -1,10 +1,11 @@ using System.Collections.Generic; +using Newtonsoft.Json; namespace CRD.Utils.Structs; -public class CrunchyNoDrmStream{ +public class CrunchyStreamData{ public string? AssetId{ get; set; } - public string? AudioLocale{ get; set; } + public Locale? AudioLocale{ get; set; } public string? Bifs{ get; set; } public string? BurnedInLocale{ get; set; } public Dictionary? Captions{ get; set; } @@ -14,7 +15,7 @@ public class CrunchyNoDrmStream{ public Dictionary? Subtitles{ get; set; } public string? Token{ get; set; } public string? Url{ get; set; } - public List? Versions{ get; set; } // Use a more specific type if known + public List? Versions{ get; set; } } public class Caption{ @@ -41,6 +42,8 @@ public class Session{ public class Subtitle{ public string? Format{ get; set; } - public string? Language{ get; set; } + [JsonProperty("language")] + public Locale? Locale{ get; set; } public string? Url{ get; set; } -} \ No newline at end of file +} + diff --git a/CRD/Utils/Structs/Structs.cs b/CRD/Utils/Structs/Structs.cs index 85c6dc9..bd957a7 100644 --- a/CRD/Utils/Structs/Structs.cs +++ b/CRD/Utils/Structs/Structs.cs @@ -63,6 +63,7 @@ public struct DownloadResponse{ public List Data{ get; set; } public string FileName{ get; set; } public bool Error{ get; set; } + public string ErrorText{ get; set; } } public class DownloadedMedia : SxItem{