Created
July 6, 2020 21:49
-
-
Save btastic/31bcefaac992f11a57537cd7d74e69e8 to your computer and use it in GitHub Desktop.
Get chapters from youtube explode
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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