31c4759c1d
Build pinned LGPL playback libraries and package standalone YouTube helpers inside the signed native app. Keep the CLI inside the app and relocate private library links for installation without runtime Homebrew dependencies. Record adaptive fixture completion in its EXIT trap: cancellation can exit with status 143 before the TERM trap writes its marker, while native pause and quality preservation remain correct.
504 lines
28 KiB
Swift
504 lines
28 KiB
Swift
import Foundation
|
|
import Darwin
|
|
|
|
@main
|
|
struct YouTubeResolverChecks {
|
|
struct Failure: Error, CustomStringConvertible {
|
|
let description: String
|
|
}
|
|
|
|
static let page = URL(string: "https://www.youtube.com/watch?v=jNQXAC9IVRw")!
|
|
static let splitJSON = #"""
|
|
{
|
|
"title": "Separate streams", "duration": 19.25, "is_live": false,
|
|
"http_headers": {"Referer": "https://www.youtube.com/", "User-Agent": "metadata-agent"},
|
|
"requested_formats": [
|
|
{"url": "https://media.example/video?token=video", "vcodec": "av01.0.04M.08", "acodec": "none", "dynamic_range": "SDR", "http_headers": {"User-Agent": "video-agent", "X-Input": "video"}},
|
|
{"url": "https://media.example/audio?token=audio", "vcodec": "none", "acodec": "opus"}
|
|
]
|
|
}
|
|
"""#
|
|
static let combinedJSON = #"""
|
|
{"title": "Combined stream", "duration": 12, "is_live": false,
|
|
"url": "https://media.example/combined", "vcodec": "h264", "acodec": "aac",
|
|
"http_headers": {"User-Agent": "combined-agent"}}
|
|
"""#
|
|
|
|
static func require(_ condition: @autoclosure () throws -> Bool, _ message: String) throws {
|
|
if try condition() == false { throw Failure(description: "FAIL: \(message)") }
|
|
}
|
|
|
|
static func reject(_ message: String, _ operation: () throws -> Void) throws {
|
|
do { try operation() }
|
|
catch is Failure { throw Failure(description: "FAIL: \(message) assertion failed") }
|
|
catch { return }
|
|
throw Failure(description: "FAIL: \(message) was accepted")
|
|
}
|
|
|
|
static func decode(_ json: String) throws -> YouTubeVideo {
|
|
try YouTubeResolver.decode(Data(json.utf8), webpageURL: page)
|
|
}
|
|
|
|
static func decode(_ object: [String: Any]) throws -> YouTubeVideo {
|
|
try YouTubeResolver.decode(JSONSerialization.data(withJSONObject: object), webpageURL: page)
|
|
}
|
|
|
|
static func fixture(_ json: String) throws -> [String: Any] {
|
|
try JSONSerialization.jsonObject(with: Data(json.utf8)) as! [String: Any]
|
|
}
|
|
|
|
static func headerLines(_ text: String) throws -> Set<String> {
|
|
try require(text.isEmpty || text.hasSuffix("\r\n"), "headers terminate with CRLF")
|
|
let lines = text.components(separatedBy: "\r\n").filter { !$0.isEmpty }
|
|
try require(lines.allSatisfy { !$0.contains("\r") && !$0.contains("\n") },
|
|
"headers contain no bare CR or LF")
|
|
return Set(lines)
|
|
}
|
|
|
|
static func videoURLs() throws {
|
|
let valid = [
|
|
"https://youtube.com/watch?v=jNQXAC9IVRw",
|
|
"https://www.youtube.com/watch?v=jNQXAC9IVRw&list=PLignored&t=3",
|
|
"https://m.youtube.com/watch?v=jNQXAC9IVRw",
|
|
"https://music.youtube.com/watch?v=jNQXAC9IVRw",
|
|
"https://youtu.be/jNQXAC9IVRw?si=share",
|
|
"https://www.youtube.com/shorts/jNQXAC9IVRw",
|
|
"https://www.youtube.com/live/jNQXAC9IVRw",
|
|
"https://www.youtube.com/embed/jNQXAC9IVRw"
|
|
]
|
|
for input in valid {
|
|
let url = try YouTubeResolver.validateURL(input)
|
|
try require(url == page, "supported video URLs identify the same video")
|
|
}
|
|
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",
|
|
"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"
|
|
]
|
|
for input in hostile {
|
|
try reject("hostile or malformed video URL") { _ = try YouTubeResolver.validateURL(input) }
|
|
}
|
|
let asciiID = try YouTubeResolver.validateURL("https://youtu.be/Abc_123-XYZ")
|
|
try require(asciiID.absoluteString.hasSuffix("v=Abc_123-XYZ"), "video IDs retain case, underscore and hyphen")
|
|
}
|
|
|
|
static func metadata() throws {
|
|
let split = try decode(splitJSON)
|
|
try require(split.title == "Separate streams" && split.duration == 19.25 && !split.isLive,
|
|
"split streams preserve title, fractional duration and recorded status")
|
|
try require(split.webpageURL == page, "resolved media retains its webpage URL")
|
|
try require(split.video.url == "https://media.example/video?token=video"
|
|
&& split.audio?.url == "https://media.example/audio?token=audio",
|
|
"video and audio inputs remain distinct and preserve signed query strings")
|
|
let videoHeaders = try headerLines(split.video.headers)
|
|
let audioHeaders = try headerLines(split.audio!.headers)
|
|
try require(videoHeaders == ["User-Agent: video-agent", "X-Input: video"],
|
|
"selected video headers take precedence as a complete map")
|
|
try require(audioHeaders == ["Referer: https://www.youtube.com/", "User-Agent: metadata-agent"],
|
|
"audio without selected headers inherits metadata headers")
|
|
|
|
var splitObject = try fixture(splitJSON)
|
|
var formats = splitObject["requested_formats"] as! [[String: Any]]
|
|
formats.reverse()
|
|
formats[0]["http_headers"] = ["X-Input": "audio"]
|
|
formats[1]["http_headers"] = NSNull()
|
|
splitObject["requested_formats"] = formats
|
|
let reversed = try decode(splitObject)
|
|
let reversedVideoHeaders = try headerLines(reversed.video.headers)
|
|
let reversedAudioHeaders = try headerLines(reversed.audio!.headers)
|
|
try require(reversed.video.url == split.video.url && reversed.audio?.url == split.audio?.url,
|
|
"stream roles depend on codecs rather than array order")
|
|
try require(reversedVideoHeaders == audioHeaders && reversedAudioHeaders == ["X-Input: audio"],
|
|
"each input independently selects its own headers or metadata fallback")
|
|
|
|
let combined = try decode(combinedJSON)
|
|
try require(combined.video.url == "https://media.example/combined" && combined.audio == nil,
|
|
"combined fallback uses one input, not duplicated audio")
|
|
try require(combined.title == "Combined stream" && combined.duration == 12 && !combined.isLive,
|
|
"combined metadata is preserved")
|
|
let combinedHeaders = try headerLines(combined.video.headers)
|
|
try require(combinedHeaders == ["User-Agent: combined-agent"], "combined input carries headers")
|
|
|
|
var selectedCombined = try fixture(combinedJSON)
|
|
selectedCombined.removeValue(forKey: "url")
|
|
selectedCombined["requested_formats"] = [
|
|
["url": "https://media.example/selected-combined", "vcodec": "h264", "acodec": "aac",
|
|
"http_headers": ["X-Input": "combined"]]
|
|
]
|
|
let selected = try decode(selectedCombined)
|
|
let selectedHeaders = try headerLines(selected.video.headers)
|
|
try require(selected.audio == nil && selected.video.url == "https://media.example/selected-combined"
|
|
&& selectedHeaders == ["X-Input: combined"],
|
|
"a selected combined format is one playable input with its own headers")
|
|
|
|
var hls = try fixture(combinedJSON)
|
|
hls["url"] = "https://media.example/live.m3u8"
|
|
hls["protocol"] = "m3u8_native"
|
|
let playlist = try decode(hls)
|
|
try require(playlist.video.url == "https://media.example/live.m3u8" && playlist.audio == nil,
|
|
"native HLS remains a usable streaming playlist, not a downloaded file")
|
|
|
|
var live = try fixture(combinedJSON)
|
|
live["is_live"] = true
|
|
live["duration"] = NSNull()
|
|
let directLive = try decode(live)
|
|
try require(directLive.isLive && directLive.duration == 0, "live stream permits unknown duration")
|
|
live.removeValue(forKey: "is_live")
|
|
live.removeValue(forKey: "duration")
|
|
live["live_status"] = "is_live"
|
|
try require(try decode(live).isLive, "live_status identifies live broadcasts")
|
|
live["live_status"] = "was_live"
|
|
try require(!(try decode(live)).isLive, "completed live broadcasts are recorded media")
|
|
live["live_status"] = "is_upcoming"
|
|
try reject("upcoming broadcast with no playable live content") { _ = try decode(live) }
|
|
|
|
var emptyHeaders = try fixture(combinedJSON)
|
|
emptyHeaders.removeValue(forKey: "http_headers")
|
|
try require(try decode(emptyHeaders).video.headers.isEmpty, "absent headers serialize to an empty block")
|
|
}
|
|
|
|
static func qualities() throws {
|
|
func video(_ height: Int, audio: Bool = false, range: String = "SDR", transport: String = "https") -> [String: Any] {
|
|
["height": height, "vcodec": "h264", "acodec": audio ? "aac" : "none",
|
|
"dynamic_range": range, "protocol": transport, "url": "https://media.example/\(height)p"]
|
|
}
|
|
let audio: [String: Any] = ["vcodec": "none", "acodec": "aac", "url": "https://media.example/audio"]
|
|
var object: [String: Any] = [
|
|
"requested_formats": [video(1080), audio],
|
|
"formats": [video(2160), video(1440), video(1080), video(1080, audio: true), video(720),
|
|
video(480), video(4320, range: "HDR10"), video(360, transport: "http_dash_segments"),
|
|
video(0), audio]
|
|
]
|
|
let normal = try decode(object)
|
|
try require(normal.height == 1080 && normal.availableHeights == [2160, 1440, 1080, 720, 480],
|
|
"quality choices are unique playable SDR resolutions, including manual choices above the default cap")
|
|
object["requested_formats"] = [video(720), audio]
|
|
try require(try decode(object).height == 720, "a lower available resolution is accepted by the default policy")
|
|
object["requested_formats"] = [video(2160), audio]
|
|
try reject("4K stream selected by the default policy") { _ = try decode(object) }
|
|
let data = try JSONSerialization.data(withJSONObject: object)
|
|
let high = try YouTubeResolver.decode(data, webpageURL: page, height: 2160)
|
|
try require(high.height == 2160 && high.video.url == "https://media.example/2160p",
|
|
"explicit selection permits supported 4K video")
|
|
try reject("requested resolution silently replaced by another") {
|
|
_ = try YouTubeResolver.decode(data, webpageURL: page, height: 1440)
|
|
}
|
|
object["requested_formats"] = [video(720, audio: true)]
|
|
object["formats"] = [video(2160), video(720, audio: true)]
|
|
try require(try decode(object).availableHeights == [720],
|
|
"video-only resolutions without a playable separate audio stream are not offered")
|
|
object["requested_formats"] = [video(-720, audio: true)]
|
|
try reject("invalid selected video dimensions") { _ = try decode(object) }
|
|
}
|
|
|
|
static func malformedMetadata() throws {
|
|
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) }
|
|
}
|
|
try reject("nonfinite duration") {
|
|
_ = try decode(#"{"url":"https://media.example/video","vcodec":"h264","acodec":"aac","duration":1e999}"#)
|
|
}
|
|
let combined = try fixture(combinedJSON)
|
|
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) }
|
|
}
|
|
for url in ["", "https://", "file:///tmp/video", "ftp://media.example/video",
|
|
"data:video/mp4;base64,AA==", "https://media.example/video\r\nX-Injected: yes"] {
|
|
var object = combined
|
|
object["url"] = url
|
|
try reject("invalid or unsafe stream URL") { _ = try decode(object) }
|
|
}
|
|
for duration in [-1, "19", true] as [Any] {
|
|
var object = combined
|
|
object["duration"] = duration
|
|
try reject("invalid duration metadata") { _ = try decode(object) }
|
|
}
|
|
for key in ["is_live", "title"] {
|
|
var object = combined
|
|
object[key] = ["malformed"]
|
|
try reject("malformed \(key) metadata") { _ = try decode(object) }
|
|
}
|
|
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) }
|
|
}
|
|
for transport in ["http_dash_segments", "mhtml", "rtmp"] {
|
|
var object = combined
|
|
object["protocol"] = transport
|
|
try reject("fragment assembly or unsupported transport is not a whole playable input") {
|
|
_ = try decode(object)
|
|
}
|
|
}
|
|
|
|
let split = try fixture(splitJSON)
|
|
let formats = split["requested_formats"] as! [[String: Any]]
|
|
for malformed in ["bad", [formats[0]], [formats[1]], [formats[0], formats[0]],
|
|
[formats[0], formats[1], formats[1]]] as [Any] {
|
|
var object = split
|
|
object["requested_formats"] = malformed
|
|
try reject("malformed or ambiguous split stream selection") { _ = try decode(object) }
|
|
}
|
|
var missingAudio = split
|
|
var badFormats = formats
|
|
badFormats[1].removeValue(forKey: "url")
|
|
missingAudio["requested_formats"] = badFormats
|
|
try reject("missing separate audio URL is not silently downgraded") { _ = try decode(missingAudio) }
|
|
|
|
let badHeaders: [Any] = [
|
|
"not a header map", ["User-Agent": 5], ["Bad Name": "value"], ["Bad:Name": "value"],
|
|
["Bad\r\nName": "value"], ["": "value"], ["X-Header": "a\r\nInjected: yes"],
|
|
["X-Header": "a\nb"], ["X-Header": "a\rb"], ["X-Header": "a\u{0}b"],
|
|
["X-Header": "a\t b"], ["X-Header": "a\u{7f}b"]
|
|
]
|
|
for headers in badHeaders {
|
|
var object = combined
|
|
object["http_headers"] = headers
|
|
try reject("unsafe or malformed metadata headers") { _ = try decode(object) }
|
|
var perInput = split
|
|
var selected = formats
|
|
selected[1]["http_headers"] = headers
|
|
perInput["requested_formats"] = selected
|
|
try reject("unsafe or malformed selected audio headers") { _ = try decode(perInput) }
|
|
}
|
|
}
|
|
|
|
struct Extractor: Sendable {
|
|
let executable: URL
|
|
let directory: URL
|
|
|
|
init(root: URL, name: String, json: String = YouTubeResolverChecks.splitJSON,
|
|
body: String = #"/bin/cat "$base/response.json""#) throws {
|
|
directory = root.appendingPathComponent(name, isDirectory: true)
|
|
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: false)
|
|
executable = directory.appendingPathComponent("fake-extractor")
|
|
let script = "#!/bin/sh\nset -eu\nbase=${0%/*}\n: > \"$base/started\"\n" + body + "\n"
|
|
try Data(script.utf8).write(to: executable)
|
|
try FileManager.default.setAttributes([.posixPermissions: 0o700], ofItemAtPath: executable.path)
|
|
try Data(json.utf8).write(to: directory.appendingPathComponent("response.json"))
|
|
}
|
|
|
|
func marker(_ name: String) -> URL { directory.appendingPathComponent(name) }
|
|
}
|
|
|
|
static func awaitMarker(_ extractor: Extractor, _ name: String = "started") async throws {
|
|
for _ in 0..<400 {
|
|
if FileManager.default.fileExists(atPath: extractor.marker(name).path) { return }
|
|
try await Task.sleep(for: .milliseconds(5))
|
|
}
|
|
throw Failure(description: "FAIL: extractor did not reach \(name) within the isolated check deadline")
|
|
}
|
|
|
|
static func cancelled(_ task: Task<YouTubeVideo, Error>, _ message: String) async throws {
|
|
do { _ = try await task.value }
|
|
catch is CancellationError { return }
|
|
catch { throw Failure(description: "FAIL: \(message) returned an extraction error instead of cancellation") }
|
|
throw Failure(description: "FAIL: \(message) returned media instead of cancellation")
|
|
}
|
|
|
|
static func subprocesses(root: URL, runtime: URL) async throws {
|
|
let successful = try Extractor(root: root, name: "successful split")
|
|
let result = try await YouTubeResolver.resolve(page, executableURL: successful.executable, runtimeURL: runtime)
|
|
try require(result.video.url == "https://media.example/video?token=video"
|
|
&& result.audio?.url == "https://media.example/audio?token=audio"
|
|
&& result.title == "Separate streams" && result.duration == 19.25,
|
|
"actual subprocess output becomes usable separate media inputs")
|
|
|
|
let denied = try Extractor(root: root, name: "denied", body: #"""
|
|
/bin/cat "$base/response.json"
|
|
printf '%s\n' 'ERROR: Sign in to confirm your age: https://media.example/private?secret=not-for-errors' >&2
|
|
exit 17
|
|
"""#)
|
|
do {
|
|
_ = try await YouTubeResolver.resolve(page, executableURL: denied.executable, runtimeURL: runtime)
|
|
throw Failure(description: "FAIL: failed extractor returned media despite its exit status")
|
|
} catch let failure as Failure { throw failure }
|
|
catch {
|
|
let text = String(describing: error) + error.localizedDescription
|
|
try require(!text.contains("not-for-errors") && !text.contains("media.example"),
|
|
"extractor error does not leak signed media URLs or credentials")
|
|
}
|
|
|
|
let noisy = try Extractor(root: root, name: "pipe boundaries", body: #"""
|
|
/bin/dd if=/dev/zero bs=1024 count=512 2>/dev/null | /usr/bin/tr '\000' x >&2
|
|
/bin/dd if=/dev/zero bs=1024 count=512 2>/dev/null | /usr/bin/tr '\000' ' '
|
|
/bin/cat "$base/response.json"
|
|
"""#)
|
|
let drained = try await YouTubeResolver.resolve(page, executableURL: noisy.executable, runtimeURL: runtime)
|
|
try require(drained.audio?.url == result.audio?.url && drained.video.url == result.video.url,
|
|
"stderr beyond its capture boundary and stdout beyond pipe capacity drain without deadlock")
|
|
|
|
let oversized = try Extractor(root: root, name: "oversized stdout", body: #"""
|
|
/bin/dd if=/dev/zero bs=1024 count=17408 2>/dev/null | /usr/bin/tr '\000' ' '
|
|
/bin/cat "$base/response.json"
|
|
"""#)
|
|
do {
|
|
_ = try await YouTubeResolver.resolve(page, executableURL: oversized.executable, runtimeURL: runtime)
|
|
throw Failure(description: "FAIL: oversized extractor output was accepted")
|
|
} catch let failure as Failure { throw failure }
|
|
catch {}
|
|
|
|
let beforeLaunch = try Extractor(root: root, name: "already cancelled")
|
|
let preCancelled = Task {
|
|
withUnsafeCurrentTask { $0?.cancel() }
|
|
return try await YouTubeResolver.resolve(page, executableURL: beforeLaunch.executable, runtimeURL: runtime)
|
|
}
|
|
try await cancelled(preCancelled, "cancellation before launch")
|
|
try require(!FileManager.default.fileExists(atPath: beforeLaunch.marker("started").path),
|
|
"a pre-cancelled request never starts the dependency")
|
|
|
|
let waitingScript = #"""
|
|
trap '' TERM INT
|
|
printf '%s\n' "$$" > "$base/pid"
|
|
: > "$base/ready"
|
|
while [ ! -f "$base/release" ]; do /bin/sleep 0.01; done
|
|
/bin/cat "$base/response.json"
|
|
"""#
|
|
let old = try Extractor(root: root, name: "old request", body: waitingScript)
|
|
let newer = try Extractor(root: root, name: "new request", json: combinedJSON, body: waitingScript)
|
|
let oldTask = Task { try await YouTubeResolver.resolve(page, executableURL: old.executable, runtimeURL: runtime) }
|
|
defer { oldTask.cancel() }
|
|
try await awaitMarker(old, "ready")
|
|
let newTask = Task { try await YouTubeResolver.resolve(page, executableURL: newer.executable, runtimeURL: runtime) }
|
|
defer { newTask.cancel() }
|
|
try await awaitMarker(newer, "ready")
|
|
oldTask.cancel()
|
|
try await cancelled(oldTask, "in-flight cancellation")
|
|
let oldPID = try String(contentsOf: old.marker("pid"), encoding: .utf8)
|
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
try require(pid_t(oldPID).map { Darwin.kill($0, 0) == -1 && errno == ESRCH } == true,
|
|
"cancelled extractor is reaped even when it ignores termination")
|
|
oldTask.cancel()
|
|
try Data().write(to: newer.marker("release"))
|
|
let newResult = try await newTask.value
|
|
try require(newResult.title == "Combined stream" && newResult.audio == nil
|
|
&& newResult.video.url == "https://media.example/combined",
|
|
"cancelling an old request cannot terminate or replace a newer resolution")
|
|
}
|
|
|
|
static func actualQualitySelection(root: URL, runtime: URL) async throws {
|
|
let executable: URL
|
|
if CommandLine.arguments.count > 1 {
|
|
let app = URL(fileURLWithPath: CommandLine.arguments[1], isDirectory: true)
|
|
guard let bundled = try YouTubeResolver.bundledExecutable("yt-dlp", bundleURL: app) else {
|
|
throw Failure(description: "FAIL: supplied quality-selection app is not an app bundle")
|
|
}
|
|
executable = bundled
|
|
} else {
|
|
let directories = (ProcessInfo.processInfo.environment["PATH"] ?? "").split(separator: ":").map(String.init)
|
|
guard let found = directories.map({ URL(fileURLWithPath: $0).appendingPathComponent("yt-dlp") })
|
|
.first(where: { FileManager.default.isExecutableFile(atPath: $0.path) }) else {
|
|
print("Skipping actual yt-dlp quality selection: no bundled app supplied and yt-dlp is not on PATH")
|
|
return
|
|
}
|
|
executable = found
|
|
}
|
|
func video(_ height: Int, combined: Bool = false) -> [String: Any] {
|
|
["format_id": "\(height)", "height": height, "width": height * 16 / 9, "ext": "mp4",
|
|
"vcodec": "h264", "acodec": combined ? "aac" : "none", "dynamic_range": "SDR",
|
|
"protocol": "https", "url": "https://media.example/\(height).mp4", "preference": height == 720 ? 100 : 0]
|
|
}
|
|
var object: [String: Any] = [
|
|
"id": "jNQXAC9IVRw", "title": "Quality ordering", "duration": 12,
|
|
"webpage_url": page.absoluteString, "extractor": "youtube", "extractor_key": "Youtube",
|
|
"formats": [video(2160), video(1080, combined: true), video(720),
|
|
["format_id": "audio", "ext": "m4a", "vcodec": "none", "acodec": "aac",
|
|
"protocol": "https", "url": "https://media.example/audio.m4a"]]
|
|
]
|
|
let extractor = try Extractor(root: root, name: "actual quality selection")
|
|
let quotedExecutable = "'" + executable.path.replacingOccurrences(of: "'", with: "'\\''") + "'"
|
|
let script = """
|
|
#!/bin/bash
|
|
set -eu
|
|
base=${0%/*}
|
|
args=("$@")
|
|
exec \(quotedExecutable) --load-info-json "$base/response.json" "${args[@]:0:${#args[@]}-2}"
|
|
"""
|
|
try Data(script.utf8).write(to: extractor.executable)
|
|
try JSONSerialization.data(withJSONObject: object).write(to: extractor.marker("response.json"))
|
|
let normal = try await YouTubeResolver.resolve(page, executableURL: extractor.executable, runtimeURL: runtime)
|
|
try require(normal.height == 1080 && normal.audio == nil, "1080p combined video wins over preferred 720p and capped-out 4K")
|
|
let high = try await YouTubeResolver.resolve(page, height: 2160, executableURL: extractor.executable, runtimeURL: runtime)
|
|
try require(high.height == 2160 && high.audio != nil, "explicit 4K selects the higher video with separate audio")
|
|
var formats = object["formats"] as! [[String: Any]]
|
|
formats.removeAll { ($0["height"] as? Int) == 1080 }
|
|
object["formats"] = formats
|
|
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")
|
|
}
|
|
|
|
static func bundledHelpers(root: URL) throws {
|
|
let app = root.appendingPathComponent("relocated player.app", isDirectory: true)
|
|
let macOS = app.appendingPathComponent("Contents/MacOS", isDirectory: true)
|
|
try FileManager.default.createDirectory(at: macOS, withIntermediateDirectories: true)
|
|
let external = root.appendingPathComponent("yt-dlp")
|
|
try Data("#!/bin/sh\nexit 99\n".utf8).write(to: external)
|
|
try FileManager.default.setAttributes([.posixPermissions: 0o700], ofItemAtPath: external.path)
|
|
let oldPath = ProcessInfo.processInfo.environment["PATH"]
|
|
defer {
|
|
if let oldPath { setenv("PATH", oldPath, 1) } else { unsetenv("PATH") }
|
|
}
|
|
for name in ["yt-dlp", "deno"] {
|
|
for path in [root.path, ""] {
|
|
setenv("PATH", path, 1)
|
|
try reject("missing bundled helper cannot fall back to hostile or missing PATH") {
|
|
_ = try YouTubeResolver.bundledExecutable(name, bundleURL: app)
|
|
}
|
|
}
|
|
let helper = macOS.appendingPathComponent(name)
|
|
try Data("#!/bin/sh\nexit 0\n".utf8).write(to: helper)
|
|
try FileManager.default.setAttributes([.posixPermissions: 0o600], ofItemAtPath: helper.path)
|
|
try reject("nonexecutable bundled helper") {
|
|
_ = try YouTubeResolver.bundledExecutable(name, bundleURL: app)
|
|
}
|
|
try FileManager.default.setAttributes([.posixPermissions: 0o700], ofItemAtPath: helper.path)
|
|
try require(try YouTubeResolver.bundledExecutable(name, bundleURL: app) == helper,
|
|
"bundled helper location survives relocation independently of PATH")
|
|
}
|
|
try require(try YouTubeResolver.bundledExecutable("yt-dlp", bundleURL: root) == nil,
|
|
"standalone development executables can use PATH discovery")
|
|
}
|
|
|
|
static func main() async throws {
|
|
// A broken pipe/cancellation implementation must fail, not leave this check hanging.
|
|
let watchdog = Task {
|
|
try await Task.sleep(for: .seconds(30))
|
|
fatalError("FAIL: resolver checks exceeded their bounded subprocess deadline")
|
|
}
|
|
defer { watchdog.cancel() }
|
|
let root = FileManager.default.temporaryDirectory
|
|
.appendingPathComponent("terebi resolver checks '\(UUID().uuidString)", isDirectory: true)
|
|
try FileManager.default.createDirectory(at: root, withIntermediateDirectories: false)
|
|
defer { try? FileManager.default.removeItem(at: root) }
|
|
let runtime = root.appendingPathComponent("deno")
|
|
try FileManager.default.createSymbolicLink(at: runtime, withDestinationURL: URL(fileURLWithPath: "/usr/bin/true"))
|
|
try videoURLs()
|
|
try metadata()
|
|
try malformedMetadata()
|
|
try qualities()
|
|
try bundledHelpers(root: root)
|
|
try await subprocesses(root: root, runtime: runtime)
|
|
try await actualQualitySelection(root: root, runtime: runtime)
|
|
print("YouTube resolver checks passed")
|
|
}
|
|
}
|