From 945a4ab3d5fbed98090fd5197173f55e3b72aed9 Mon Sep 17 00:00:00 2001 From: Troy Simeon Taylor <44444967+troystaylor@users.noreply.github.com> Date: Sun, 28 Sep 2025 16:47:59 -0400 Subject: [PATCH 1/2] Add files via upload --- .../YouTube Transcripts/ConnectorPackage.zip | Bin 0 -> 3631 bytes .../apiDefinition.swagger.json | 246 ++++++++ .../YouTube Transcripts/apiProperties.json | 12 + .../YouTube Transcripts/readme.md | 21 + .../YouTube Transcripts/script.csx | 585 ++++++++++++++++++ 5 files changed, 864 insertions(+) create mode 100644 independent-publisher-connectors/YouTube Transcripts/ConnectorPackage.zip create mode 100644 independent-publisher-connectors/YouTube Transcripts/apiDefinition.swagger.json create mode 100644 independent-publisher-connectors/YouTube Transcripts/apiProperties.json create mode 100644 independent-publisher-connectors/YouTube Transcripts/readme.md create mode 100644 independent-publisher-connectors/YouTube Transcripts/script.csx diff --git a/independent-publisher-connectors/YouTube Transcripts/ConnectorPackage.zip b/independent-publisher-connectors/YouTube Transcripts/ConnectorPackage.zip new file mode 100644 index 0000000000000000000000000000000000000000..a413c5d964dc8359402c1749ea193fe324e9d487 GIT binary patch literal 3631 zcmZ`+byO1$yB^&er6AIcbP5P4AxbGAp`#llHaaIEh>{W_IclT=8!0hRYDf(MfgvM? zbUIQ%`25{_?mhRO^L=le=e+NGp8wuIp2ygLjGPq!08j(wbXBazvc2MuNdSORvVS%J zfQL_jA4JOA+2)BsDu*n6r@%-gvbVV16vs36<2mTsb0OpR7MtIk=~)z6Do|uYU3Kyx zZuO|KnFI9^<`@3Bcj{lHoLJw(Ohdq!EM}ag*-RE}f86AS&-J>+1kn@T%*XRgqT0eE z@JGkzw7k%PW(9%52m=Y@UW|bIgT+$Q6!zMiEOQ18?$?J_l8K2`1iI~gW@T)TVb0}s zXoz1G>Hm|uQ(6+@B>CR$BE9V(1E#_!q@QOJOgo^0tb^W7a?kWjG4^ATEAlQgl#{eg zs_dFxJ!OuEmC)naoPA)ad=Z@0%8c{(hPGsQ?JG$vZn9-yjoLF!ZsTG%ysn}3djVhc z{4T8_JA904jw<(gUYW+T?G9Dbj~nb=4B<%>FKHyiWTwhb*q}LM&%Kq;FO@DuI9mej;+8ci&X7H6 z&MA8R9eqERQOTIziXw5s6bDm%W7&)l!@bT`jk8Z;coQK4Sx0KcACU3?{)SGhnicum zj|(nz0KoIV{kH&62PaPlHy5ce4``r;xiw1?8E3(`SY_kG+esW;M&#XxQiE&za+vV%6GMXmD46D&*t zX#5HQ^mkPiFW(*H=9YO3axiCeb7P;J=(C|-THHKJfB63RwPxi@wOU>JLLhpyq8xR=m z&zmIcF`G?XN`4D9NNs;l@X|6@T-@0Zl{mrAJA)@FY7~GOa!E7w=OEPhyZb6A5I^SU z`X_#3bXnv1Ctut^Kef#{rg^?k5MZlWKd@5vKP+DL@78&U3cic9LDDUG=#aj3zqPn> zbgH!cf@$m>>MKQDE#2+16QiK`T~0Rt@Q?a2gpYAt*QccMj1mcXSLC$uk@I_&H&YaA zhIPEIRn0rpVkoFNO_29z8QzdsxNAI7`i5EB_13<#R(xWcDBpqnmddB=BW^f0$q}vo z>x|3;pJR9MY1n2zX&=q*SZB>|?znHI^8R^^RBug(0)2AbdF(6Cq1lA>sdC9p{Q(Nt zgqbJqTe~lCzo6-I$qZ=*??cEws{w&t2U%ni>qIqrb-)&HIus)M1|0%=qJ23VL*ISS zUj;?foK$~TZN3|nc7Z>H1=_Aa2*c5R`_q?Y`Kxx-pC~)Bi{|vT@Q0$%iRoapj&$*C zC!#Cyb78mByatOtPo27}QFHJ{I9@*>eM@-MvqV!aLwXchx%;cqEI#RY6tZGA{$zA_ z5U1ZqC!(gDRz3r8S{-E|jsGU%T3ZW;Q8`GbH{{p;j`5CPf0)}icR#b!pZHO5HO?7* z$?zyEP(z0;@Sg5L_7^vJUB~aS%<}!=Zr~NEipJPeeWy{VJ;(c|>~fX5Kwi7b@Whgy z?2wzLQ4(!W%`j0O+rgY+45PXV&#JjEr#?Zdj)pA*RVpLW`e#OBkF8$StwiMAAMIY> z@aNZ4q=|hQ@yG3FXyYxs$h+!HI*Za|bb0S`0&-P5#W^FF!e#PZcLTyyP2y8`eETfB zji)_M73v%B~%O%oFSLeu4_-mz*?c|E^Ri~$Ru)G_Gq2vzz0+Q(rV?-qB2;ORI)&U z32TIxtkv$g1)lklAsDlk2|$R+S9ns3oy1&?>S9Qk=v_+EfxbH}vN_=I6bt?6Y~0Rc zZ~i;@>6GRl0V)uV1D5y_g2L6d!=5wB>-z)Lbm0fmdQm?MYPvA{8~3Gco^~OON12Ot zI=qB2lWsOYD3K(EA~o$hHrr zms6_+R2R$CBKEGO{J=q0V2Y?!Q1!jzaJh(%`BW=j#8~9wsj&Luy0=KoVQSPX)?Gm) zl%aD{`AMr*&dt{$f`%4>nCeGF|8i|p!J}M!IC|%LrNup;&mRu8p>Re6v5-$j0+5gG zmWo&9@7`v*40k4z@J>BXkVT?~iZ|g)j7TX$X~^)+fs1|C1NHEk;1}i#ZJVEYhIz(k z4NVDD1QeWb!AV%|+Be1-^VQ}SCV$C4jIqma-Im(!E+L|pV1 za)mHN_5q5$`NQSD=RwhI{4kgPIss0uJyhoMSH7Y&Qp;E>__F)6+xJ z`jV_2auS2VN2gp?in1TG6VtWWxZgnS#5`2)6bt(4xlt)94=Ah+hBnj|wwItO(U4q+ z&hAU=i*9?alSE$b<9PO9|xH-yqQ;m;r`5Px;7T?C5ek&=u#w# ze~5DB5c^z|+kZqaI3rYV5H!sba%W9iwFSj`lSW1Aj-jkUrmB_!$i>+{&|yvK1GNnn zz$!{8=(7wyoK+!I6fnPV+MA&wx;!E}?OcFgo_7jm6`twYWs*V-ik-3RZd=uk;DljS z|5r%@ddTgmCL%`iw)Eg@%As4*_R$xuvn=pd%;EiV)ic~+1A01N{OxLE3Ns%h9$GU3 zY#&;aGO5NBQNLaJg)G(Ru>1lBWnvtgWp8maj^S+|e{A7a7Gu(Ey$T9{+T0H^_jgJ) z4^>*$S*7E0pfEBzM|ggNMcVj_in!!!Dmvbi7_B$31LU1XxUe9;?-v)87?ie3xmINQ zv3>{oB%CmDMrq4B3#3{T8zKx72TIfVGR$uMT72Sb;&K1?$^D*=%exiw{Wpz$CX2#- zgZBD1y;;yU0)cgmt=4Pdg7s2V0%Z zI0EAG|FiF$eSa6#$}q&s8?-lz0r@dn|Xh%Gd-9Rw3FKsexce@+f_w#J8a!e zxTWPvh0~^rbkVg-+0kVGjPJ(1m>7s~Ojz$in^DNGn3(7qH%UjK&JWAq= zlY_IXdR$>7sZocnB$~*(FXmZ#GSPG-Ut`8mVKN|?!5sXi^W2^a21F+G|Ykqv~<2D$l`hYn2xF4&WZq5*WT2!>VU z`o+f$?(o*0KV&pysi-7LD8<);ZF{P?=I*h4li~tRm4rrIdR&cS2?$?X%2X9#RbCRL z0C^lJ69Y6xHOZ>sLF9365J3dYp-KgV9RDVznI)6_tSm>VEy5v@7!E0im)JtWw|LH6 zNtoCf=12JIvpEKRnC4TF_WE^Q<4Meb5Z@(fWl zk-k(;e>~~~G+uZKcf6MuW#?Nf(oCwTsn@11^g{#G9GPif#t;aFZUC-eudhVcbQ#%$aKDHyCN|*AR%Q1{AaKBSCao%G)Mmf|Kr^LFYDi_`2Xer0L{^y Yr2kLT8yiqi{&OV#n~=X)&G66kFC ExecuteAsync() + { + this.Context.Logger.LogInformation("YouTube Transcript: Starting processing"); + try + { + var modifiedRequest = await ProcessRequest(this.Context.Request); + var response = await this.Context.SendAsync(modifiedRequest, this.CancellationToken).ConfigureAwait(false); + + this.Context.Logger.LogInformation($"YouTube API response: {response.StatusCode}"); + + if (!response.IsSuccessStatusCode) + { + var errorContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + this.Context.Logger.LogWarning($"API error: {response.StatusCode} - {errorContent}"); + return CreateErrorResponse(response.StatusCode, "YouTube API request failed"); + } + + var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + var transformedResponse = TransformYouTubeResponse(responseString); + response.Content = CreateJsonContent(transformedResponse); + + this.Context.Logger.LogInformation("Response transformation completed successfully"); + return response; + } + catch (Exception ex) + { + this.Context.Logger.LogError($"Processing error: {ex.Message}"); + return CreateErrorResponse(HttpStatusCode.InternalServerError, $"Processing error: {ex.Message}"); + } + } + + private async Task ProcessRequest(HttpRequestMessage originalRequest) + { + if (originalRequest.Content == null) + return originalRequest; + + try + { + var contentString = await originalRequest.Content.ReadAsStringAsync().ConfigureAwait(false); + var requestJson = JObject.Parse(contentString); + + var videoId = requestJson["externalVideoId"]?.ToString(); + var existingParams = requestJson["params"]?.ToString(); + + // Generate params if needed + if (ShouldGenerateParams(existingParams) && !string.IsNullOrWhiteSpace(videoId)) + { + requestJson["params"] = GenerateYouTubeParams(videoId); + } + + // Ensure context is set + EnsureContext(requestJson); + + var newRequest = CreateRequestWithHeaders(originalRequest, requestJson.ToString(), videoId); + return newRequest; + } + catch (Exception ex) + { + this.Context.Logger.LogError($"Request processing error: {ex.Message}"); + return originalRequest; + } + } + + private bool ShouldGenerateParams(string existingParams) + { + return string.IsNullOrWhiteSpace(existingParams) || + existingParams == "AUTO_GENERATE" || + !IsValidBase64(existingParams); + } + + private void EnsureContext(JObject requestJson) + { + if (requestJson["context"] == null) + { + requestJson["context"] = new JObject + { + ["client"] = new JObject + { + ["clientName"] = "WEB", + ["clientVersion"] = "2.20250923.08.00" + } + }; + } + } + + private HttpRequestMessage CreateRequestWithHeaders(HttpRequestMessage originalRequest, string content, string videoId) + { + var newRequest = new HttpRequestMessage(originalRequest.Method, originalRequest.RequestUri); + + // Add YouTube-specific headers for bot detection avoidance + var headers = new Dictionary + { + ["User-Agent"] = DefaultUserAgent, + ["Accept"] = "application/json", + ["Accept-Language"] = "en-US,en;q=0.9", + ["Origin"] = "https://www.youtube.com", + ["Referer"] = $"https://www.youtube.com/watch?v={videoId}", + ["Sec-Ch-Ua"] = "\"Not_A Brand\";v=\"99\", \"Google Chrome\";v=\"109\", \"Chromium\";v=\"109\"", + ["Sec-Ch-Ua-Mobile"] = "?0", + ["Sec-Ch-Ua-Platform"] = "\"Windows\"", + ["Sec-Fetch-Dest"] = "empty", + ["Sec-Fetch-Mode"] = "cors", + ["Sec-Fetch-Site"] = "same-origin" + }; + + foreach (var header in headers) + { + newRequest.Headers.TryAddWithoutValidation(header.Key, header.Value); + } + + // Copy non-conflicting original headers + foreach (var header in originalRequest.Headers) + { + if (!headers.ContainsKey(header.Key)) + { + newRequest.Headers.TryAddWithoutValidation(header.Key, header.Value); + } + } + + newRequest.Content = new StringContent(content, Encoding.UTF8, "application/json"); + return newRequest; + } + + private string GenerateYouTubeParams(string videoId) + { + try + { + // Optimized protobuf generation using pre-calculated byte arrays + var inner = new List(20); // Pre-allocate capacity + + // Field 1: "asr" + inner.AddRange(new byte[] { 0x0A, 0x03 }); + inner.AddRange(Encoding.UTF8.GetBytes("asr")); + + // Field 2: "en" + inner.AddRange(new byte[] { 0x12, 0x02 }); + inner.AddRange(Encoding.UTF8.GetBytes("en")); + + // Field 3: empty bytes + inner.AddRange(new byte[] { 0x1A, 0x00 }); + + var innerB64 = ToBase64Url(inner.ToArray()); + var innerB64UrlEncoded = Uri.EscapeDataString(innerB64); + + var outer = new List(50); // Pre-allocate capacity + + // Field 1: videoId + outer.Add(0x0A); + outer.Add((byte)videoId.Length); + outer.AddRange(Encoding.UTF8.GetBytes(videoId)); + + // Field 2: encoded inner params + outer.Add(0x12); + var innerBytes = Encoding.UTF8.GetBytes(innerB64UrlEncoded); + outer.Add((byte)innerBytes.Length); + outer.AddRange(innerBytes); + + return ToBase64Url(outer.ToArray()); + } + catch (Exception ex) + { + this.Context.Logger.LogWarning($"Params generation failed, using fallback: {ex.Message}"); + return "CgtEQzJwM2tGamNLMBISQ2dOaGMzSVNBbVZ1R2dBJTNE"; // Fallback + } + } + + private bool IsValidBase64(string value) + { + if (string.IsNullOrWhiteSpace(value)) + return false; + + try + { + var base64 = value.Replace('-', '+').Replace('_', '/'); + while (base64.Length % 4 != 0) + base64 += "="; + + Convert.FromBase64String(base64); + return true; + } + catch + { + return false; + } + } + + private string ToBase64Url(byte[] bytes) => + Convert.ToBase64String(bytes).Replace('+', '-').Replace('/', '_').TrimEnd('='); + + private string TransformYouTubeResponse(string youtubeResponse) + { + try + { + var youtubeData = JObject.Parse(youtubeResponse); + var segments = ExtractTranscriptSegments(youtubeData); + + var result = new JObject + { + ["success"] = true, + ["segments"] = JArray.FromObject(segments), + ["totalSegments"] = segments.Count, + ["totalDurationMs"] = segments.Count > 0 ? segments.Last().EndMs : 0, + ["totalDurationFormatted"] = segments.Count > 0 ? FormatDuration(segments.Last().EndMs) : "0:00", + ["fullTranscript"] = string.Join(" ", segments.Select(s => s.Text)), + ["language"] = ExtractLanguageInfo(youtubeData), + ["processedAt"] = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ") + }; + + return result.ToString(); + } + catch (Exception ex) + { + this.Context.Logger.LogError($"Response transformation error: {ex.Message}"); + return CreateFallbackResponse(youtubeResponse, ex.Message).ToString(); + } + } + + private List ExtractTranscriptSegments(JObject youtubeData) + { + var segments = new List(); + + // Try primary navigation path + if (TryExtractFromActions(youtubeData, segments)) + { + this.Context.Logger.LogInformation($"Primary extraction successful: {segments.Count} segments found"); + return segments.OrderBy(s => s.StartMs).ToList(); + } + + // Fallback: deep search with early termination + this.Context.Logger.LogInformation("Primary extraction failed, attempting deep search"); + SearchForTranscriptContentOptimized(youtubeData, segments, 0, MaxSearchDepth); + this.Context.Logger.LogInformation($"Deep search completed: {segments.Count} segments found"); + + return segments.OrderBy(s => s.StartMs).ToList(); + } + + private bool TryExtractFromActions(JObject youtubeData, List segments) + { + try + { + // Log the top-level structure for debugging + this.Context.Logger.LogInformation($"YouTube response top-level keys: {string.Join(", ", youtubeData.Properties().Select(p => p.Name))}"); + + var actions = youtubeData["actions"]; + this.Context.Logger.LogInformation($"Actions found: {actions != null}, HasValues: {actions?.HasValues}"); + + if (actions?.HasValues == true) + { + this.Context.Logger.LogInformation($"Found {actions.Count()} actions"); + var firstAction = actions[0]; + this.Context.Logger.LogInformation($"First action keys: {string.Join(", ", ((JObject)firstAction).Properties().Select(p => p.Name))}"); + + var updateAction = firstAction["updateEngagementPanelAction"]; + this.Context.Logger.LogInformation($"UpdateEngagementPanelAction found: {updateAction != null}"); + + if (updateAction != null) + { + var content = updateAction["content"]; + this.Context.Logger.LogInformation($"Content found: {content != null}"); + + if (content != null) + { + this.Context.Logger.LogInformation($"Content keys: {string.Join(", ", ((JObject)content).Properties().Select(p => p.Name))}"); + var transcriptRenderer = content["transcriptRenderer"]; + this.Context.Logger.LogInformation($"TranscriptRenderer found: {transcriptRenderer != null}"); + + if (transcriptRenderer != null) + { + this.Context.Logger.LogInformation($"TranscriptRenderer keys: {string.Join(", ", ((JObject)transcriptRenderer).Properties().Select(p => p.Name))}"); + } + } + } + } + + var cueGroups = youtubeData["actions"]?[0]?["updateEngagementPanelAction"]?["content"]? + ["transcriptRenderer"]?["body"]?["transcriptBodyRenderer"]?["cueGroups"]; + + if (cueGroups?.HasValues == true) + { + this.Context.Logger.LogInformation($"Found {cueGroups.Count()} cue groups in primary path"); + foreach (var cueGroup in cueGroups) + { + var segment = CreateTranscriptSegment(cueGroup["transcriptSegmentRenderer"]); + if (segment != null) + segments.Add(segment); + } + return segments.Count > 0; + } + + // Try alternative structure + var altTranscriptRenderer = youtubeData["actions"]?[0]?["updateEngagementPanelAction"]?["content"]?["transcriptRenderer"]; + if (altTranscriptRenderer != null) + { + this.Context.Logger.LogInformation("Trying alternative structure extraction"); + return TryExtractFromAlternativeStructure(altTranscriptRenderer, segments); + } + } + catch (Exception ex) + { + this.Context.Logger.LogWarning($"Primary extraction failed: {ex.Message}"); + } + + return false; + } + + private bool TryExtractFromAlternativeStructure(JToken transcriptRenderer, List segments) + { + this.Context.Logger.LogInformation($"Alternative structure - TranscriptRenderer keys: {string.Join(", ", ((JObject)transcriptRenderer).Properties().Select(p => p.Name))}"); + + var content = transcriptRenderer["content"]; + if (content != null) + { + this.Context.Logger.LogInformation($"Content keys: {string.Join(", ", ((JObject)content).Properties().Select(p => p.Name))}"); + + var searchPanel = content["transcriptSearchPanelRenderer"]; + if (searchPanel != null) + { + this.Context.Logger.LogInformation("Found transcriptSearchPanelRenderer, attempting extraction"); + this.Context.Logger.LogInformation($"SearchPanel keys: {string.Join(", ", ((JObject)searchPanel).Properties().Select(p => p.Name))}"); + + // Look for body in search panel + var body = searchPanel["body"]; + if (body != null) + { + this.Context.Logger.LogInformation($"SearchPanel body keys: {string.Join(", ", ((JObject)body).Properties().Select(p => p.Name))}"); + + var bodyRenderer = body["transcriptSegmentListRenderer"]; + if (bodyRenderer != null) + { + this.Context.Logger.LogInformation($"TranscriptSegmentListRenderer keys: {string.Join(", ", ((JObject)bodyRenderer).Properties().Select(p => p.Name))}"); + + var initialSegments = bodyRenderer["initialSegments"]; + if (initialSegments?.HasValues == true) + { + this.Context.Logger.LogInformation($"Found {initialSegments.Count()} initial segments"); + + foreach (var segmentItem in initialSegments) + { + var renderer = segmentItem["transcriptSegmentRenderer"]; + if (renderer != null) + { + var segment = CreateTranscriptSegment(renderer); + if (segment != null) + segments.Add(segment); + } + } + + if (segments.Count > 0) + { + this.Context.Logger.LogInformation($"Extracted {segments.Count} segments from initialSegments"); + return true; + } + } + } + } + + // If no segments found in structured approach, try deep search + SearchForTranscriptContentOptimized(searchPanel, segments, 0, 3); + if (segments.Count > 0) + { + this.Context.Logger.LogInformation($"Extracted {segments.Count} segments from search panel deep search"); + return true; + } + } + + // Try direct content search as fallback + SearchForTranscriptContentOptimized(content, segments, 0, 3); + if (segments.Count > 0) + { + this.Context.Logger.LogInformation($"Extracted {segments.Count} segments from direct content"); + return true; + } + } + + return false; + } + + private void SearchForTranscriptContentOptimized(JToken token, List segments, int depth, int maxDepth) + { + if (depth > maxDepth || token == null || segments.Count > 1000) // Early termination + return; + + if (token.Type == JTokenType.Object) + { + var obj = (JObject)token; + + // Quick check for transcript patterns + if (obj.TryGetValue("startMs", out var startMsToken) && + (obj.TryGetValue("snippet", out _) || obj.TryGetValue("text", out _))) + { + var segment = CreateTranscriptSegment(obj); + if (segment != null) + segments.Add(segment); + } + + // Continue searching efficiently + foreach (var property in obj.Properties()) + { + if (segments.Count > 1000) break; // Prevent excessive processing + SearchForTranscriptContentOptimized(property.Value, segments, depth + 1, maxDepth); + } + } + else if (token.Type == JTokenType.Array) + { + foreach (var item in (JArray)token) + { + if (segments.Count > 1000) break; + SearchForTranscriptContentOptimized(item, segments, depth + 1, maxDepth); + } + } + } + + private TranscriptSegment CreateTranscriptSegment(JToken renderer) + { + if (renderer == null) + return null; + + try + { + var startMs = ParseMilliseconds(renderer["startMs"]?.ToString()); + var endMs = ParseMilliseconds(renderer["endMs"]?.ToString()); + var text = ExtractTextFromRuns(renderer["snippet"]?["runs"]) ?? + renderer["text"]?.ToString() ?? ""; + + if (string.IsNullOrWhiteSpace(text)) + return null; + + var cleanText = CleanTranscriptTextOptimized(text); + var durationMs = endMs - startMs; + + return new TranscriptSegment + { + Text = cleanText, + StartMs = startMs, + EndMs = endMs, + DurationMs = durationMs, + StartTime = renderer["startTimeText"]?["simpleText"]?.ToString() ?? "", + StartTimeFormatted = FormatDuration(startMs), + EndTimeFormatted = FormatDuration(endMs), + DurationFormatted = FormatDuration(durationMs), + WordCount = CountWordsOptimized(cleanText), + CharacterCount = cleanText.Length + }; + } + catch + { + return null; + } + } + + private string ExtractTextFromRuns(JToken runs) + { + if (runs?.HasValues != true) + return ""; + + var sb = new StringBuilder(); + foreach (var run in runs) + { + var text = run["text"]?.ToString(); + if (!string.IsNullOrEmpty(text)) + sb.Append(text); + } + + return sb.ToString(); + } + + private string CleanTranscriptTextOptimized(string text) + { + if (string.IsNullOrWhiteSpace(text)) + return ""; + + text = text.Trim() + .Replace("♪", ""); + + // Use compiled regexes for better performance + text = WhitespaceRegex.Replace(text, " "); + text = SoundEffectsRegex.Replace(text, ""); + text = BackgroundNoiseRegex.Replace(text, ""); + + return text.Trim(); + } + + private int CountWordsOptimized(string text) => + string.IsNullOrWhiteSpace(text) ? 0 : + text.Split(new[] { ' ', '\t', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries).Length; + + private int ParseMilliseconds(string msString) => + int.TryParse(msString, out int ms) ? ms : 0; + + private string FormatDuration(int milliseconds) + { + var timeSpan = TimeSpan.FromMilliseconds(milliseconds); + return timeSpan.TotalHours >= 1 ? + $"{(int)timeSpan.TotalHours}:{timeSpan.Minutes:D2}:{timeSpan.Seconds:D2}" : + $"{timeSpan.Minutes}:{timeSpan.Seconds:D2}"; + } + + private string ExtractLanguageInfo(JObject youtubeData) + { + try + { + var languageMenu = youtubeData["actions"]?[0]?["updateEngagementPanelAction"]?["content"]? + ["transcriptRenderer"]?["footer"]?["transcriptFooterRenderer"]?["languageMenu"]; + + if (languageMenu != null) + { + var selectedItem = languageMenu["sortFilterSubMenuRenderer"]?["subMenuItems"]? + .FirstOrDefault(item => item["selected"]?.Value() == true); + + return selectedItem?["title"]?.ToString() ?? DefaultLanguage; + } + } + catch + { + // Graceful fallback + } + + return DefaultLanguage; + } + + private JObject CreateFallbackResponse(string originalResponse, string errorMessage) => + new JObject + { + ["success"] = false, + ["error"] = $"Response transformation failed: {errorMessage}", + ["segments"] = new JArray(), + ["totalSegments"] = 0, + ["processedAt"] = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"), + ["rawResponseSample"] = originalResponse.Length > 2000 ? + originalResponse.Substring(0, 2000) + "..." : originalResponse + }; + + private HttpResponseMessage CreateErrorResponse(HttpStatusCode statusCode, string message) + { + var errorResponse = new HttpResponseMessage(statusCode); + var errorObject = new JObject + { + ["success"] = false, + ["error"] = message, + ["segments"] = new JArray(), + ["totalSegments"] = 0, + ["processedAt"] = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ") + }; + + errorResponse.Content = CreateJsonContent(errorObject.ToString()); + return errorResponse; + } + + private class TranscriptSegment + { + public string Text { get; set; } + public int StartMs { get; set; } + public int EndMs { get; set; } + public int DurationMs { get; set; } + public string StartTime { get; set; } + public string StartTimeFormatted { get; set; } + public string EndTimeFormatted { get; set; } + public string DurationFormatted { get; set; } + public int WordCount { get; set; } + public int CharacterCount { get; set; } + } +} \ No newline at end of file From a952dac5e6153c1de389e80da7ed79288e739f6b Mon Sep 17 00:00:00 2001 From: Troy Simeon Taylor <44444967+troystaylor@users.noreply.github.com> Date: Fri, 24 Oct 2025 14:58:29 -0400 Subject: [PATCH 2/2] Add files via upload --- .../YouTube Transcripts/ConnectorPackage.zip | Bin 3631 -> 26518 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/independent-publisher-connectors/YouTube Transcripts/ConnectorPackage.zip b/independent-publisher-connectors/YouTube Transcripts/ConnectorPackage.zip index a413c5d964dc8359402c1749ea193fe324e9d487..b2eee8d0a5b30a7b0412677ed00bdd6328e8a784 100644 GIT binary patch delta 26077 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 delta 3010 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