Skip to content

Instantly share code, notes, and snippets.

@gtk2k
Last active October 22, 2019 07:02
Show Gist options
  • Save gtk2k/4de29765b3f08bba21b8b39be7e9e980 to your computer and use it in GitHub Desktop.
Save gtk2k/4de29765b3f08bba21b8b39be7e9e980 to your computer and use it in GitHub Desktop.
Youtubeの動画URLを取得する(Live対応)
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
class YoutubeURL
{
public static async Task<URLs> Get(string videoId)
{
var raw = await GetWebStringAsync($"https://www.youtube.com/get_video_info?&video_id={videoId}");
var html = await GetWebStringAsync($"https://www.youtube.com/watch?v={videoId}");
var jsURL = new Regex(@"/yts/jsbin/player_ias-.+\.js").Match(html);
if (string.IsNullOrEmpty(raw) || jsURL == null) throw new Exception("Get error.");
var js = await GetWebStringAsync($"https://www.youtube.com{jsURL}");
var data = parse(raw);
dynamic res = data["player_response"];
dynamic streamingData = res["streamingData"];
return new URLs
{
Formats = data.ContainsKey("url_encoded_fmt_stream_map") ? parse2(js, data["url_encoded_fmt_stream_map"]) : null,
AdaptiveFormats = data.ContainsKey("adaptive_fmts") ? parse2(js, data["adaptive_fmts"]) : null,
DashURL = streamingData.dashManifestUrl,
HlsURL = streamingData.hlsManifestUrl
};
}
private static Dictionary<string, object> parse(string s)
{
return s.Split("&".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)
.Select(x => Uri.UnescapeDataString(x).Split("=".ToCharArray(), 2, StringSplitOptions.RemoveEmptyEntries))
.Select(x => (k: x[0], v: x.Length < 2 ? null : x[1][0] == '{' ? JObject.Parse(x[1]) : float.TryParse(x[1], out float n) ? (object)n : x[1]))
.ToDictionary(x => x.k, x => x.v);
}
private static Dictionary<string, object>[] parse2(string js, object o)
{
if (string.IsNullOrEmpty((string)o)) return null;
return ((string)o).Split(',').Select(x => execDecipher(js, parse(x))).ToArray();
}
private static Dictionary<string, object> execDecipher(string js, Dictionary<string, object> x)
{
if (x.ContainsKey("s") && x.ContainsKey("url")) x["url"] += $"&sig={Decipher(js, (string)x["s"])}"; // キー名をsignatureからsigに変更
return x;
}
private static string Decipher(string js, string signature)
{
var g = new Regex(@"\.split\(""""\);(.+?);return.+\.join\(""""\)").Match(js).Groups[1].Value;
var funcName = g.Split('.')[0];
var ds = new Regex(funcName + @"=\{(.+?)\}\};", RegexOptions.Singleline).Match(js).ToString();
var arr = signature.ToCharArray();
foreach (var x in g.Split(new[] { funcName + "." }, StringSplitOptions.RemoveEmptyEntries))
{
var m = new Regex(@"(.+?)\(a,(\d+?)\)").Match(x);
var n = int.Parse(m.Groups[2].Value);
switch (new Regex(m.Groups[1].Value + @":function\(.+?\)\{(.+?)\}").Match(ds).Groups[1].Value)
{
case "a.splice(0,b)": arr = arr.Skip(n).Take(arr.Length - n).ToArray(); break;
case "a.reverse()": arr = arr.Reverse().ToArray(); break;
case "var c=a[0];a[0]=a[b%a.length];a[b%a.length]=c":
{
var c = arr[0];
arr[0] = arr[n % arr.Length];
arr[n % arr.Length] = c;
break;
}
}
}
return new string(arr);
}
private static async Task<string> GetWebStringAsync(string url)
{
var webReq = (HttpWebRequest)WebRequest.Create(url);
using (var response = await webReq.GetResponseAsync())
{
if (((HttpWebResponse)response).StatusCode != HttpStatusCode.OK) return string.Empty;
using (var stream = response.GetResponseStream())
{
var reader = new StreamReader(stream, Encoding.UTF8);
return await reader.ReadToEndAsync();
}
}
}
}
class URLs
{
public bool IsLive { get { return DashURL != null || HlsURL != null; } }
public Dictionary<string, object>[] Formats;
public Dictionary<string, object>[] AdaptiveFormats;
public string DashURL;
public string HlsURL;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment