Skip to content

Commit

Permalink
feat: Reformat YouTube videos to the embeded URL
Browse files Browse the repository at this point in the history
Extract the YouTube video or list ID and generates the embed URL.
Simplifies embedding YouTube videos by allowing one to copy paste the direct YouTube URL rather than extracting the embed URL from the <iframe> source.
Also supports short youtu.be links.

Add "rel=0" (unless explicitly specified) to only show related videos from the same channel.
  • Loading branch information
hultqvist committed Nov 8, 2024
1 parent efc1c3d commit d31c3f7
Showing 1 changed file with 99 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Web;
using Markdig.Renderers;
using Markdig.Renderers.Html;

namespace Docfx.MarkdigEngine.Extensions;

public class QuoteSectionNoteRender : HtmlObjectRenderer<QuoteSectionNoteBlock>
public partial class QuoteSectionNoteRender : HtmlObjectRenderer<QuoteSectionNoteBlock>
{
private readonly MarkdownContext _context;
private readonly Dictionary<string, string> _notes;
Expand Down Expand Up @@ -107,6 +110,7 @@ public static string FixUpLink(string link)
if (Uri.TryCreate(link, UriKind.Absolute, out Uri videoLink))
{
var host = videoLink.Host;
var path = videoLink.LocalPath;
var query = videoLink.Query;
if (query.Length > 1)
{
Expand All @@ -125,16 +129,107 @@ public static string FixUpLink(string link)
query += "&nocookie=true";
}
}
else if (host.Equals("youtube.com", StringComparison.OrdinalIgnoreCase) || host.Equals("www.youtube.com", StringComparison.OrdinalIgnoreCase))
else if (hostsYouTube.Contains(host, StringComparer.OrdinalIgnoreCase))
{
// case 2, YouTube video
host = "www.youtube-nocookie.com";
var idYouTube = GetYouTubeId(host, path, ref query);
if (idYouTube != null)
{
host = "www.youtube-nocookie.com";
path = "/embed/" + idYouTube;
}
else
{
//YouTube Playlist
var listYouTube = GetYouTubeList(query);
if (listYouTube != null)
{
host = "www.youtube-nocookie.com";
path = "/embed/videoseries";
query = "list=" + listYouTube;
//No need to modify query parameters, "list=..." is the same
}
}

//Only related videos from the same channel
//https://developers.google.com/youtube/player_parameters
//Add rel=0 unless specified in the original link
if (query.Split('&').Any(q => q.StartsWith("rel=")) == false)
{
if (query.Length == 0)
query = "rel=0";
else
query += "&rel=0";
}

//Keep this to preserve previous behavior
if (host.Equals("youtube.com", StringComparison.OrdinalIgnoreCase) || host.Equals("www.youtube.com", StringComparison.OrdinalIgnoreCase))
{
host = "www.youtube-nocookie.com";
}
}

var builder = new UriBuilder(videoLink) { Host = host, Query = query };
var builder = new UriBuilder(videoLink) { Host = host, Path = path, Query = query };
link = builder.Uri.ToString();
}

return link;
}

static readonly ReadOnlyCollection<string> hostsYouTube = new string[] {
"youtube.com",
"www.youtube.com",
"youtu.be",
"www.youtube-nocookie.com",
}.AsReadOnly();

private static string GetYouTubeId(string host, string path, ref string query)
{
if (host == "youtu.be")
{
return path.Substring(1);
}

var match = ReYouTubeQueryVideo().Match(query);
if (match.Success)
{
//Remove from query
query = query.Replace(match.Groups[0].Value, "").Trim('&').Replace("&&", "&");
return match.Groups[2].Value;
}

match = ReYouTubePathId().Match(path);
if (match.Success)
{
var id = match.Groups[1].Value;

if (id == "videoseries")
return null;

return id;
}

return null;
}

[GeneratedRegex(@"(^|&)v=([^&]+)")]
private static partial Regex ReYouTubeQueryVideo();

[GeneratedRegex(@"(^|&)list=([^&]+)")]
private static partial Regex ReYouTubeQueryList();

[GeneratedRegex(@"/embed/([^/]+)$")]
private static partial Regex ReYouTubePathId();

private static string GetYouTubeList(string query)
{
var match = ReYouTubeQueryList().Match(query);
if (match.Success)
{
return match.Groups[2].Value;
}

return null;
}

}

0 comments on commit d31c3f7

Please sign in to comment.