Skip to content

Instantly share code, notes, and snippets.

@btastic
Created July 6, 2020 21:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save btastic/31bcefaac992f11a57537cd7d74e69e8 to your computer and use it in GitHub Desktop.
Save btastic/31bcefaac992f11a57537cd7d74e69e8 to your computer and use it in GitHub Desktop.
Get chapters from youtube explode
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using AngleSharp.Dom;
using AngleSharp.Html.Dom;
using YoutubeExplode;
using YoutubeExplode.Videos;
namespace YourProgram.YoutubeExplode
{
/// <summary>
/// YouTube video chapter.
/// </summary>
public class Chapter
{
/// <summary>
/// Chapter Title.
/// </summary>
public string Title { get; }
/// <summary>
/// Start of the chapter in milliseconds.
/// </summary>
public ulong TimeRangeStart { get; }
/// <summary>
/// Initializes an instance of <see cref="Chapter"/>.
/// </summary>
public Chapter(
string title,
ulong timeRangeStart)
{
Title = title;
TimeRangeStart = timeRangeStart;
}
}
public static class MethodInfoExtensions
{
public static async Task<object> InvokeAsync(this MethodInfo @this, object obj, params object[] parameters)
{
var task = (Task)@this.Invoke(obj, parameters);
await task.ConfigureAwait(false);
var resultProperty = task.GetType().GetProperty("Result");
return resultProperty.GetValue(task);
}
}
public static class VideoExtensions
{
public static async Task<IReadOnlyCollection<Chapter>> TryGetChaptersAsync(this Video video, YoutubeClient client)
{
// wooo, whacky hacky
try
{
var assembly = typeof(YoutubeClient).Assembly;
var httpClient = typeof(VideoClient).GetField("_httpClient",
BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(client.Videos);
var watchPageObj = assembly.GetType("YoutubeExplode.ReverseEngineering.Responses.WatchPage");
var methodInfo = watchPageObj.GetMethod("GetAsync");
var parameters = new object[] { httpClient, video.Id.ToString() };
var watchPage = await methodInfo.InvokeAsync(null, parameters);
var root = (IHtmlDocument)watchPage.GetType().GetField("_root", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(watchPage);
var ytInitialData = root
.GetElementsByTagName("script")
.Select(e => e.Text())
.FirstOrDefault(s => s.Contains("window[\"ytInitialData\"] ="));
if (string.IsNullOrWhiteSpace(ytInitialData))
{
return new List<Chapter>().AsReadOnly();
}
var json = Regex.Match(ytInitialData, "window\\[\"ytInitialData\"\\]\\s*=\\s*(.+?})(?:\"\\))?;", RegexOptions.Singleline).Groups[1].Value;
using var doc = JsonDocument.Parse(json);
var jsonDocument = doc.RootElement.Clone();
var chaptersArray = jsonDocument
.GetProperty("playerOverlays")
.GetProperty("playerOverlayRenderer")
.GetProperty("decoratedPlayerBarRenderer")
.GetProperty("decoratedPlayerBarRenderer")
.GetProperty("playerBar")
.GetProperty("chapteredPlayerBarRenderer")
.GetProperty("chapters")
.EnumerateArray()
.Select(j => new Chapter(
j.GetProperty("chapterRenderer").GetProperty("title").GetProperty("simpleText").GetString(),
j.GetProperty("chapterRenderer").GetProperty("timeRangeStartMillis").GetUInt64()));
return chaptersArray.ToList().AsReadOnly();
}
catch (Exception ex)
{
Console.WriteLine("Getting chapters failed");
Console.WriteLine(ex.Message);
}
return new List<Chapter>().AsReadOnly();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment