feat: resolve HTTP video URLs with bundled yt-dlp

This commit is contained in:
2026-10-22 17:05:29 +02:00
parent 6a4afc9f0e
commit b132bab393
14 changed files with 121 additions and 79 deletions
+32 -18
View File
@@ -70,20 +70,20 @@ struct YouTubeResolverChecks {
let url = try YouTubeResolver.validateURL(input)
try require(url == page, "supported video URLs identify the same video")
}
for input in ["https://vimeo.com/76979871", "https://example.com/watch?v=jNQXAC9IVRw",
"https://youtube.com.evil.example/watch?v=jNQXAC9IVRw",
"https://evil.youtube.com/watch?v=jNQXAC9IVRw",
"http://127.0.0.1:8080/video.mp4?token=kept#fragment",
"https://www.youtube.com/playlist?list=PLexample"] {
try require(try YouTubeResolver.validateURL(input).absoluteString == input,
"HTTP(S) URLs outside canonical YouTube video forms reach yt-dlp unchanged")
}
let hostile = [
"", "not a URL", "javascript:alert(1)", "file:///watch?v=jNQXAC9IVRw",
"https://example.com/watch?v=jNQXAC9IVRw",
"https://youtube.com.evil.example/watch?v=jNQXAC9IVRw",
"https://evil.youtube.com/watch?v=jNQXAC9IVRw",
"ftp://example.com/video", "https://", "https://example.com:0/video",
"https://example.com:65536/video", "https://example.com/video\ninjected",
"https://youtube.com@evil.example/watch?v=jNQXAC9IVRw",
"https://user:password@www.youtube.com/watch?v=jNQXAC9IVRw",
"https://www.youtube.com/playlist?list=PLexample",
"https://www.youtube.com/watch?list=PLexample",
"https://www.youtube.com/watch?v=short",
"https://youtu.be/jNQXAC9IVRwextra", "https://youtu.be/jNQXAC9IVR!",
"https://www.youtube.com/shorts/", "https://youtu.be/jNQXAC9IVRw/extra",
"https://www.youtube.com/watch?v=jNQXAC9IVR%0A",
"https://www.youtube.com/watch?v=jNQXAC9IVR%C3%A9"
"https://user:password@www.youtube.com/watch?v=jNQXAC9IVRw"
]
for input in hostile {
try reject("hostile or malformed video URL") { _ = try YouTubeResolver.validateURL(input) }
@@ -202,6 +202,9 @@ struct YouTubeResolverChecks {
}
static func malformedMetadata() throws {
var collection = try fixture(combinedJSON)
collection["_type"] = "playlist"
try reject("collection result instead of a single video") { _ = try decode(collection) }
try reject("non-JSON extractor output") { _ = try decode("not JSON") }
for json in ["null", "[]", "{}", #"{"requested_formats":[]}"#] {
try reject("missing playable stream metadata") { _ = try decode(json) }
@@ -210,10 +213,11 @@ struct YouTubeResolverChecks {
_ = try decode(#"{"url":"https://media.example/video","vcodec":"h264","acodec":"aac","duration":1e999}"#)
}
let combined = try fixture(combinedJSON)
var missingURL = combined
missingURL.removeValue(forKey: "url")
try reject("missing combined stream URL") { _ = try decode(missingURL) }
for key in ["url", "vcodec", "acodec"] {
var object = combined
object.removeValue(forKey: key)
try reject("missing required combined \(key)") { _ = try decode(object) }
object[key] = 17
try reject("wrong type for combined \(key)") { _ = try decode(object) }
}
@@ -236,11 +240,9 @@ struct YouTubeResolverChecks {
var hdr = combined
hdr["dynamic_range"] = "HDR10"
try reject("HDR input unsupported by the SDR renderer") { _ = try decode(hdr) }
for value in ["none", NSNull()] as [Any] {
var object = combined
object["vcodec"] = value
try reject("combined input without video codec") { _ = try decode(object) }
}
var audioOnly = combined
audioOnly["vcodec"] = "none"
try reject("explicitly audio-only result") { _ = try decode(audioOnly) }
for transport in ["http_dash_segments", "mhtml", "rtmp"] {
var object = combined
object["protocol"] = transport
@@ -444,6 +446,18 @@ struct YouTubeResolverChecks {
try JSONSerialization.data(withJSONObject: object).write(to: extractor.marker("response.json"))
let lower = try await YouTubeResolver.resolve(page, executableURL: extractor.executable, runtimeURL: runtime)
try require(lower.height == 720, "default falls downward when 1080p is absent, never upward to available 4K")
let genericPage = URL(string: "https://video.example/watch?id=123")!
let generic: [String: Any] = [
"id": "123", "title": "Generic HTML5 video", "webpage_url": genericPage.absoluteString,
"extractor": "generic", "extractor_key": "Generic",
"formats": [["format_id": "0", "url": "https://media.example/unknown.mp4",
"ext": "mp4", "protocol": "https"]]
]
try JSONSerialization.data(withJSONObject: generic).write(to: extractor.marker("response.json"))
let unknown = try await YouTubeResolver.resolve(genericPage, executableURL: extractor.executable, runtimeURL: runtime)
try require(unknown.video.url == "https://media.example/unknown.mp4" &&
unknown.audio == nil && unknown.height == nil && unknown.webpageURL == genericPage,
"generic HTML5 video without codec or height hints remains playable and retains page identity")
}
static func bundledHelpers(root: URL) throws {