From dd2b7cd82ceda6204551c3de565458a5e4648f20 Mon Sep 17 00:00:00 2001 From: sim0n00ps Date: Tue, 6 May 2025 00:34:15 +0100 Subject: [PATCH] #4 WIP --- OF DL/Entities/Config.cs | 3 ++ OF DL/Entities/IDownloadConfig.cs | 2 + OF DL/Entities/Post/SinglePost.cs | 4 +- OF DL/Enumerations/VideoResolution.cs | 15 ++++++ OF DL/Helpers/APIHelper.cs | 73 ++++++++++++++++++++------- OF DL/Helpers/DownloadHelper.cs | 68 ++++++++++++++++++++++++- OF DL/Program.cs | 17 ++++++- 7 files changed, 158 insertions(+), 24 deletions(-) create mode 100644 OF DL/Enumerations/VideoResolution.cs diff --git a/OF DL/Entities/Config.cs b/OF DL/Entities/Config.cs index 3731b18..0d05fbf 100644 --- a/OF DL/Entities/Config.cs +++ b/OF DL/Entities/Config.cs @@ -100,6 +100,9 @@ namespace OF_DL.Entities [ToggleableConfig] public bool DisableBrowserAuth { get; set; } = false; + + [JsonConverter(typeof(StringEnumConverter))] + public VideoResolution DownloadVideoResolution { get; set; } = VideoResolution.source; } public class CreatorConfig : IFileNameFormatConfig diff --git a/OF DL/Entities/IDownloadConfig.cs b/OF DL/Entities/IDownloadConfig.cs index 2072220..21565d0 100644 --- a/OF DL/Entities/IDownloadConfig.cs +++ b/OF DL/Entities/IDownloadConfig.cs @@ -17,6 +17,8 @@ namespace OF_DL.Entities bool DownloadVideos { get; set; } bool DownloadAudios { get; set; } + VideoResolution DownloadVideoResolution { get; set; } + int? Timeout { get; set; } bool FolderPerPaidPost { get; set; } bool FolderPerPost { get; set; } diff --git a/OF DL/Entities/Post/SinglePost.cs b/OF DL/Entities/Post/SinglePost.cs index 98e68f0..f38795f 100644 --- a/OF DL/Entities/Post/SinglePost.cs +++ b/OF DL/Entities/Post/SinglePost.cs @@ -150,10 +150,10 @@ namespace OF_DL.Entities.Post public class VideoSources { [JsonProperty("720")] - public object _720 { get; set; } + public string _720 { get; set; } [JsonProperty("240")] - public object _240 { get; set; } + public string _240 { get; set; } } public class Dash { diff --git a/OF DL/Enumerations/VideoResolution.cs b/OF DL/Enumerations/VideoResolution.cs new file mode 100644 index 0000000..2514dee --- /dev/null +++ b/OF DL/Enumerations/VideoResolution.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OF_DL.Enumerations +{ + public enum VideoResolution + { + _240, + _720, + source + } +} diff --git a/OF DL/Helpers/APIHelper.cs b/OF DL/Helpers/APIHelper.cs index bef4db8..e3f73dd 100644 --- a/OF DL/Helpers/APIHelper.cs +++ b/OF DL/Helpers/APIHelper.cs @@ -9,6 +9,7 @@ using OF_DL.Entities.Post; using OF_DL.Entities.Purchased; using OF_DL.Entities.Stories; using OF_DL.Entities.Streams; +using OF_DL.Enumerations; using OF_DL.Enumurations; using Serilog; using Spectre.Console; @@ -26,6 +27,7 @@ public class APIHelper : IAPIHelper { private static readonly JsonSerializerSettings m_JsonSerializerSettings; private readonly IDBHelper m_DBHelper; + private readonly IDownloadConfig downloadConfig; private readonly Auth auth; private static DateTime? cachedDynamicRulesExpiration; private static DynamicRules? cachedDynamicRules; @@ -42,6 +44,7 @@ public class APIHelper : IAPIHelper { this.auth = auth; m_DBHelper = new DBHelper(downloadConfig); + this.downloadConfig = downloadConfig; } @@ -1130,29 +1133,51 @@ public class APIHelper : IAPIHelper } if (medium.canView && medium.files?.drm == null) { - if (medium.files!.full != null && !string.IsNullOrEmpty(medium.files!.full.url)) + switch (downloadConfig.DownloadVideoResolution) { - if (!medium.files!.full.url.Contains("upload")) - { - if (!singlePostCollection.SinglePosts.ContainsKey(medium.id)) + case VideoResolution.source: + if (medium.files!.full != null && !string.IsNullOrEmpty(medium.files!.full.url)) { - await m_DBHelper.AddMedia(folder, medium.id, singlePost.id, medium.files!.full.url, null, null, null, "Posts", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), postPreviewIds.Contains((long)medium.id) ? true : false, false, null); - singlePostCollection.SinglePosts.Add(medium.id, medium.files!.full.url); - singlePostCollection.SinglePostMedia.Add(medium); + if (!medium.files!.full.url.Contains("upload")) + { + if (!singlePostCollection.SinglePosts.ContainsKey(medium.id)) + { + await m_DBHelper.AddMedia(folder, medium.id, singlePost.id, medium.files!.full.url, null, null, null, "Posts", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), postPreviewIds.Contains((long)medium.id) ? true : false, false, null); + singlePostCollection.SinglePosts.Add(medium.id, medium.files!.full.url); + singlePostCollection.SinglePostMedia.Add(medium); + } + } } - } - } - else if (medium.files.preview != null && medium.files!.full == null) - { - if (!medium.files.preview.url.Contains("upload")) - { - if (!singlePostCollection.SinglePosts.ContainsKey(medium.id)) + break; + case VideoResolution._240: + if(medium.videoSources != null) { - await m_DBHelper.AddMedia(folder, medium.id, singlePost.id, medium.files.preview.url, null, null, null, "Posts", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), postPreviewIds.Contains((long)medium.id) ? true : false, false, null); - singlePostCollection.SinglePosts.Add(medium.id, medium.files.preview.url); - singlePostCollection.SinglePostMedia.Add(medium); + if (!string.IsNullOrEmpty(medium.videoSources._240)) + { + if (!singlePostCollection.SinglePosts.ContainsKey(medium.id)) + { + await m_DBHelper.AddMedia(folder, medium.id, singlePost.id, medium.videoSources._240, null, null, null, "Posts", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), postPreviewIds.Contains((long)medium.id) ? true : false, false, null); + singlePostCollection.SinglePosts.Add(medium.id, medium.videoSources._240); + singlePostCollection.SinglePostMedia.Add(medium); + } + } } - } + break; + case VideoResolution._720: + if (medium.videoSources != null) + { + if (!string.IsNullOrEmpty(medium.videoSources._720)) + { + if (!singlePostCollection.SinglePosts.ContainsKey(medium.id)) + { + await m_DBHelper.AddMedia(folder, medium.id, singlePost.id, medium.videoSources._720, null, null, null, "Posts", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), postPreviewIds.Contains((long)medium.id) ? true : false, false, null); + singlePostCollection.SinglePosts.Add(medium.id, medium.videoSources._720); + singlePostCollection.SinglePostMedia.Add(medium); + } + } + } + break; + } } else if (medium.canView && medium.files != null && medium.files.drm != null) @@ -1167,6 +1192,18 @@ public class APIHelper : IAPIHelper } } } + else if (medium.files.preview != null && medium.files!.full == null) + { + if (!medium.files.preview.url.Contains("upload")) + { + if (!singlePostCollection.SinglePosts.ContainsKey(medium.id)) + { + await m_DBHelper.AddMedia(folder, medium.id, singlePost.id, medium.files.preview.url, null, null, null, "Posts", medium.type == "photo" ? "Images" : (medium.type == "video" || medium.type == "gif" ? "Videos" : (medium.type == "audio" ? "Audios" : null)), postPreviewIds.Contains((long)medium.id) ? true : false, false, null); + singlePostCollection.SinglePosts.Add(medium.id, medium.files.preview.url); + singlePostCollection.SinglePostMedia.Add(medium); + } + } + } } } } diff --git a/OF DL/Helpers/DownloadHelper.cs b/OF DL/Helpers/DownloadHelper.cs index 1c2589e..cb91493 100644 --- a/OF DL/Helpers/DownloadHelper.cs +++ b/OF DL/Helpers/DownloadHelper.cs @@ -12,6 +12,7 @@ using OF_DL.Enumerations; using OF_DL.Utils; using Org.BouncyCastle.Asn1.Tsp; using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Tsp; using Serilog; using Spectre.Console; using System; @@ -25,6 +26,7 @@ using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; +using System.Xml.Linq; using static OF_DL.Entities.Lists.UserList; using static OF_DL.Entities.Messages.Messages; @@ -602,9 +604,30 @@ public class DownloadHelper : IDownloadHelper decKey = decryptionKey.Substring(pos1 + 1); } - string tempFilename = $"{folder}{path}/{filename}_source.mp4"; + int? streamIndex = await GetVideoStreamIndexFromMpd(url, policy, signature, kvp, downloadConfig.DownloadVideoResolution); - string parameters = $"-cenc_decryption_key {decKey} -headers \"Cookie:CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {sess} Origin: https://onlyfans.com Referer: https://onlyfans.com User-Agent: {user_agent}\" -y -i \"{url}\" -codec copy \"{tempFilename}\""; + if (streamIndex == null) + throw new Exception($"Could not find video stream for resolution {downloadConfig.DownloadVideoResolution}"); + + string tempFilename; + + switch (downloadConfig.DownloadVideoResolution) + { + case VideoResolution.source: + tempFilename = $"{folder}{path}/{filename}_source.mp4"; + break; + case VideoResolution._240: + tempFilename = $"{folder}{path}/{filename}_240.mp4"; + break; + case VideoResolution._720: + tempFilename = $"{folder}{path}/{filename}_720.mp4"; + break; + default: + tempFilename = $"{folder}{path}/{filename}_source.mp4"; + break; + } + + string parameters = $"-cenc_decryption_key {decKey} -headers \"Cookie:CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {sess} Origin: https://onlyfans.com Referer: https://onlyfans.com User-Agent: {user_agent}\" -y -i \"{url}\" -map 0:v:{streamIndex} -map 0:a? -codec copy \"{tempFilename}\""; Log.Debug($"Calling FFMPEG with Parameters: {parameters}"); @@ -1787,4 +1810,45 @@ public class DownloadHelper : IDownloadHelper return false; } #endregion + + private async Task GetVideoStreamIndexFromMpd(string mpdUrl, string policy, string signature, string kvp, VideoResolution resolution) + { + HttpClient client = new(); + HttpRequestMessage request = new(HttpMethod.Get, mpdUrl); + request.Headers.Add("user-agent", auth.USER_AGENT); + request.Headers.Add("Accept", "*/*"); + request.Headers.Add("Cookie", $"CloudFront-Policy={policy}; CloudFront-Signature={signature}; CloudFront-Key-Pair-Id={kvp}; {auth.COOKIE};"); + using (var response = await client.SendAsync(request)) + { + response.EnsureSuccessStatusCode(); + var body = await response.Content.ReadAsStringAsync(); + XDocument doc = XDocument.Parse(body); + XNamespace ns = "urn:mpeg:dash:schema:mpd:2011"; + XNamespace cenc = "urn:mpeg:cenc:2013"; + var videoAdaptationSet = doc + .Descendants(ns + "AdaptationSet") + .FirstOrDefault(e => (string)e.Attribute("mimeType") == "video/mp4"); + + if (videoAdaptationSet == null) + return null; + + string targetHeight = resolution switch + { + VideoResolution._240 => "240", + VideoResolution._720 => "720", + VideoResolution.source => "1280", + _ => throw new ArgumentOutOfRangeException(nameof(resolution)) + }; + + var representations = videoAdaptationSet.Elements(ns + "Representation").ToList(); + + for (int i = 0; i < representations.Count; i++) + { + if ((string)representations[i].Attribute("height") == targetHeight) + return i; // this is the index FFmpeg will use for `-map 0:v:{i}` + } + } + + return null; + } } diff --git a/OF DL/Program.cs b/OF DL/Program.cs index f6900c9..14b8820 100644 --- a/OF DL/Program.cs +++ b/OF DL/Program.cs @@ -161,6 +161,7 @@ public class Program hoconConfig.AppendLine($" DownloadDateSelection = \"{jsonConfig.DownloadDateSelection.ToString().ToLower()}\""); hoconConfig.AppendLine($" CustomDate = \"{jsonConfig.CustomDate?.ToString("yyyy-MM-dd")}\""); hoconConfig.AppendLine($" ShowScrapeSize = {jsonConfig.ShowScrapeSize.ToString().ToLower()}"); + hoconConfig.AppendLine($" DownloadVideoResolution = \"{(jsonConfig.DownloadVideoResolution == VideoResolution.source ? "source" : jsonConfig.DownloadVideoResolution.ToString().TrimStart('_'))}\""); hoconConfig.AppendLine("}"); hoconConfig.AppendLine("# File Settings"); @@ -280,9 +281,10 @@ public class Program DownloadDateSelection = Enum.Parse(hoconConfig.GetString("Download.DownloadDateSelection"), true), CustomDate = !string.IsNullOrWhiteSpace(hoconConfig.GetString("Download.CustomDate")) ? DateTime.Parse(hoconConfig.GetString("Download.CustomDate")) : null, ShowScrapeSize = hoconConfig.GetBoolean("Download.ShowScrapeSize"), + DownloadVideoResolution = ParseVideoResolution(hoconConfig.GetString("Download.DownloadVideoResolution")), - // File Settings - PaidPostFileNameFormat = hoconConfig.GetString("File.PaidPostFileNameFormat"), + // File Settings + PaidPostFileNameFormat = hoconConfig.GetString("File.PaidPostFileNameFormat"), PostFileNameFormat = hoconConfig.GetString("File.PostFileNameFormat"), PaidMessageFileNameFormat = hoconConfig.GetString("File.PaidMessageFileNameFormat"), MessageFileNameFormat = hoconConfig.GetString("File.MessageFileNameFormat"), @@ -399,6 +401,7 @@ public class Program hoconConfig.AppendLine($" DownloadDateSelection = \"{jsonConfig.DownloadDateSelection.ToString().ToLower()}\""); hoconConfig.AppendLine($" CustomDate = \"{jsonConfig.CustomDate?.ToString("yyyy-MM-dd")}\""); hoconConfig.AppendLine($" ShowScrapeSize = {jsonConfig.ShowScrapeSize.ToString().ToLower()}"); + hoconConfig.AppendLine($" DownloadVideoResolution = \"{(jsonConfig.DownloadVideoResolution == VideoResolution.source ? "source" : jsonConfig.DownloadVideoResolution.ToString().TrimStart('_'))}\""); hoconConfig.AppendLine("}"); hoconConfig.AppendLine("# File Settings"); @@ -2862,6 +2865,7 @@ public class Program hoconConfig.AppendLine($" DownloadDateSelection = \"{newConfig.DownloadDateSelection.ToString().ToLower()}\""); hoconConfig.AppendLine($" CustomDate = \"{newConfig.CustomDate?.ToString("yyyy-MM-dd")}\""); hoconConfig.AppendLine($" ShowScrapeSize = {newConfig.ShowScrapeSize.ToString().ToLower()}"); + hoconConfig.AppendLine($" DownloadVideoResolution = \"{(newConfig.DownloadVideoResolution == VideoResolution.source ? "source" : newConfig.DownloadVideoResolution.ToString().TrimStart('_'))}\""); hoconConfig.AppendLine("}"); hoconConfig.AppendLine("# File Settings"); @@ -3020,6 +3024,7 @@ public class Program hoconConfig.AppendLine($" DownloadDateSelection = \"{newConfig.DownloadDateSelection.ToString().ToLower()}\""); hoconConfig.AppendLine($" CustomDate = \"{newConfig.CustomDate?.ToString("yyyy-MM-dd")}\""); hoconConfig.AppendLine($" ShowScrapeSize = {newConfig.ShowScrapeSize.ToString().ToLower()}"); + hoconConfig.AppendLine($" DownloadVideoResolution = \"{(newConfig.DownloadVideoResolution == VideoResolution.source ? "source" : newConfig.DownloadVideoResolution.ToString().TrimStart('_'))}\""); hoconConfig.AppendLine("}"); hoconConfig.AppendLine("# File Settings"); @@ -3238,4 +3243,12 @@ public class Program Environment.Exit(2); } } + + public static VideoResolution ParseVideoResolution(string value) + { + if (value.Equals("source", StringComparison.OrdinalIgnoreCase)) + return VideoResolution.source; + + return Enum.Parse("_" + value, ignoreCase: true); + } }