Skip to content

Commit d31c3f7

Browse files
committed
feat: Reformat YouTube videos to the embeded URL
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.
1 parent efc1c3d commit d31c3f7

File tree

1 file changed

+99
-4
lines changed

1 file changed

+99
-4
lines changed

src/Docfx.MarkdigEngine.Extensions/QuoteSectionNote/QuoteSectionNoteRender.cs

Lines changed: 99 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Collections.ObjectModel;
5+
using System.Diagnostics;
6+
using System.Text.RegularExpressions;
47
using System.Web;
58
using Markdig.Renderers;
69
using Markdig.Renderers.Html;
710

811
namespace Docfx.MarkdigEngine.Extensions;
912

10-
public class QuoteSectionNoteRender : HtmlObjectRenderer<QuoteSectionNoteBlock>
13+
public partial class QuoteSectionNoteRender : HtmlObjectRenderer<QuoteSectionNoteBlock>
1114
{
1215
private readonly MarkdownContext _context;
1316
private readonly Dictionary<string, string> _notes;
@@ -107,6 +110,7 @@ public static string FixUpLink(string link)
107110
if (Uri.TryCreate(link, UriKind.Absolute, out Uri videoLink))
108111
{
109112
var host = videoLink.Host;
113+
var path = videoLink.LocalPath;
110114
var query = videoLink.Query;
111115
if (query.Length > 1)
112116
{
@@ -125,16 +129,107 @@ public static string FixUpLink(string link)
125129
query += "&nocookie=true";
126130
}
127131
}
128-
else if (host.Equals("youtube.com", StringComparison.OrdinalIgnoreCase) || host.Equals("www.youtube.com", StringComparison.OrdinalIgnoreCase))
132+
else if (hostsYouTube.Contains(host, StringComparer.OrdinalIgnoreCase))
129133
{
130134
// case 2, YouTube video
131-
host = "www.youtube-nocookie.com";
135+
var idYouTube = GetYouTubeId(host, path, ref query);
136+
if (idYouTube != null)
137+
{
138+
host = "www.youtube-nocookie.com";
139+
path = "/embed/" + idYouTube;
140+
}
141+
else
142+
{
143+
//YouTube Playlist
144+
var listYouTube = GetYouTubeList(query);
145+
if (listYouTube != null)
146+
{
147+
host = "www.youtube-nocookie.com";
148+
path = "/embed/videoseries";
149+
query = "list=" + listYouTube;
150+
//No need to modify query parameters, "list=..." is the same
151+
}
152+
}
153+
154+
//Only related videos from the same channel
155+
//https://developers.google.com/youtube/player_parameters
156+
//Add rel=0 unless specified in the original link
157+
if (query.Split('&').Any(q => q.StartsWith("rel=")) == false)
158+
{
159+
if (query.Length == 0)
160+
query = "rel=0";
161+
else
162+
query += "&rel=0";
163+
}
164+
165+
//Keep this to preserve previous behavior
166+
if (host.Equals("youtube.com", StringComparison.OrdinalIgnoreCase) || host.Equals("www.youtube.com", StringComparison.OrdinalIgnoreCase))
167+
{
168+
host = "www.youtube-nocookie.com";
169+
}
132170
}
133171

134-
var builder = new UriBuilder(videoLink) { Host = host, Query = query };
172+
var builder = new UriBuilder(videoLink) { Host = host, Path = path, Query = query };
135173
link = builder.Uri.ToString();
136174
}
137175

138176
return link;
139177
}
178+
179+
static readonly ReadOnlyCollection<string> hostsYouTube = new string[] {
180+
"youtube.com",
181+
"www.youtube.com",
182+
"youtu.be",
183+
"www.youtube-nocookie.com",
184+
}.AsReadOnly();
185+
186+
private static string GetYouTubeId(string host, string path, ref string query)
187+
{
188+
if (host == "youtu.be")
189+
{
190+
return path.Substring(1);
191+
}
192+
193+
var match = ReYouTubeQueryVideo().Match(query);
194+
if (match.Success)
195+
{
196+
//Remove from query
197+
query = query.Replace(match.Groups[0].Value, "").Trim('&').Replace("&&", "&");
198+
return match.Groups[2].Value;
199+
}
200+
201+
match = ReYouTubePathId().Match(path);
202+
if (match.Success)
203+
{
204+
var id = match.Groups[1].Value;
205+
206+
if (id == "videoseries")
207+
return null;
208+
209+
return id;
210+
}
211+
212+
return null;
213+
}
214+
215+
[GeneratedRegex(@"(^|&)v=([^&]+)")]
216+
private static partial Regex ReYouTubeQueryVideo();
217+
218+
[GeneratedRegex(@"(^|&)list=([^&]+)")]
219+
private static partial Regex ReYouTubeQueryList();
220+
221+
[GeneratedRegex(@"/embed/([^/]+)$")]
222+
private static partial Regex ReYouTubePathId();
223+
224+
private static string GetYouTubeList(string query)
225+
{
226+
var match = ReYouTubeQueryList().Match(query);
227+
if (match.Success)
228+
{
229+
return match.Groups[2].Value;
230+
}
231+
232+
return null;
233+
}
234+
140235
}

0 commit comments

Comments
 (0)