feat: resolve HTTP video URLs with bundled yt-dlp
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user