youtube: replace /v/ URLs with /watch?v= URLs
- ID
c68d4a9- date
2026-08-06 06:10:33+00:00- author
Alex Chan <alex@alexwlchan.net>- parent
edd1044- message
youtube: replace /v/ URLs with /watch?v= URLs- changed files
2 files, 24 additions, 6 deletions
Changed files
test_yt-dlp_alexwlchan.py (4222 → 4891)
diff --git a/test_yt-dlp_alexwlchan.py b/test_yt-dlp_alexwlchan.py
index 643151f..90c7a8c 100644
--- a/test_yt-dlp_alexwlchan.py
+++ b/test_yt-dlp_alexwlchan.py
@@ -104,6 +104,7 @@ def test_instagram_video() -> None:
@pytest.mark.parametrize(
"url, expected",
[
+ # Instagram and YouTube URLs in the correct format are left as-is.
(
"https://www.instagram.com/reel/DMWY8KkOS0n/",
"https://www.instagram.com/reel/DMWY8KkOS0n/",
@@ -116,14 +117,29 @@ def test_instagram_video() -> None:
"https://www.youtube.com/watch?v=0N1_0SUGlDQ",
"https://www.youtube.com/watch?v=0N1_0SUGlDQ",
),
+ # Extra query parameters are stripped from YouTube URLs
(
"https://www.youtube.com/watch?v=0N1_0SUGlDQ&app=desktop&list=LL&index=43",
"https://www.youtube.com/watch?v=0N1_0SUGlDQ",
),
+ # YouTube /embed/ and /v/ links are replaced with /watch?v=
(
"https://www.youtube.com/embed/0N1_0SUGlDQ",
"https://www.youtube.com/watch?v=0N1_0SUGlDQ",
),
+ (
+ "https://www.youtube.com/v/uoLoyg3JKRQ",
+ "https://www.youtube.com/watch?v=uoLoyg3JKRQ",
+ ),
+ # YouTube URLs are upgraded from HTTP to HTTPS
+ (
+ "http://www.youtube.com/embed/0N1_0SUGlDQ",
+ "https://www.youtube.com/watch?v=0N1_0SUGlDQ",
+ ),
+ (
+ "http://www.youtube.com/watch?v=0N1_0SUGlDQ",
+ "https://www.youtube.com/watch?v=0N1_0SUGlDQ",
+ ),
],
)
def test_normalise_url(url: str, expected: str) -> None:
yt-dlp_alexwlchan.py (9463 → 9588)
diff --git a/yt-dlp_alexwlchan.py b/yt-dlp_alexwlchan.py
index 31d74a4..3f7d896 100755
--- a/yt-dlp_alexwlchan.py
+++ b/yt-dlp_alexwlchan.py
@@ -15,12 +15,11 @@ from typing import Any, TypedDict
import urllib.parse
from chives.fetch import download_image
-from chives.media import create_video_entity, VideoEntity
+from chives.media import VideoEntity, create_video_entity
from yt_dlp import YoutubeDL
from yt_dlp.networking.exceptions import HTTPError as YouTubeDLHTTPError
from yt_dlp.utils import DownloadError
-
ydl_opts: Any = {
# Print progress output to stderr, not stdout
"logtostderr": True,
@@ -59,23 +58,26 @@ def normalise_url(url: str) -> str:
"""
u = urllib.parse.urlsplit(url)
- # If it's a YouTube embed URL, change it to the watch?v= form
+ # If it's a YouTube /embed/ or /v/ URL, change it to the watch?v= form
+ #
+ # The former is quite common in embeds; I've seen the /v/ form used
+ # in posts on leancrew.com.
if (
u.netloc == "www.youtube.com"
- and u.path.startswith("/embed/")
+ and u.path.startswith(("/embed/", "/v/"))
and len(u.path.split("/")) == 3
):
video_id = u.path.split("/")[-1]
qs = [("v", video_id)]
query = urllib.parse.urlencode(qs)
- return urllib.parse.urlunsplit((u.scheme, u.netloc, "/watch", query, ""))
+ return urllib.parse.urlunsplit(("https", u.netloc, "/watch", query, ""))
# If it's a YouTube URL, remove all query parameters except video ID (v)
if u.netloc == "www.youtube.com":
qs = urllib.parse.parse_qsl(u.query)
qs = [(k, v) for k, v in qs if k == "v"]
query = urllib.parse.urlencode(qs)
- return urllib.parse.urlunsplit((u.scheme, u.netloc, u.path, query, ""))
+ return urllib.parse.urlunsplit(("https", u.netloc, u.path, query, ""))
return url