111 lines
5.8 KiB
Python
Executable File
111 lines
5.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Run adaptive PlayerModel playback against real loopback media and offline yt-dlp metadata."""
|
|
import argparse
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import shlex
|
|
import subprocess
|
|
import tempfile
|
|
from urllib.parse import urlsplit
|
|
|
|
from streaming import StreamingServer
|
|
|
|
|
|
class CaptionStreamingServer(StreamingServer):
|
|
def __init__(self, fixtures):
|
|
super().__init__(fixtures)
|
|
self.caption_requests = 0
|
|
|
|
def _get(self, request):
|
|
path = urlsplit(request.path).path
|
|
if path == "/caption-count":
|
|
self._reply(request, 200, str(self.caption_requests).encode())
|
|
return
|
|
if path == "/captions.vtt":
|
|
if request.headers.get("X-Subtitle") != "caption-private":
|
|
self._reply(request, 403, b"Missing caption credentials")
|
|
return
|
|
self.caption_requests += 1
|
|
self._reply(request, 200, b"WEBVTT\n\n00:00.000 --> 02:00.000\nAutomatic caption\n", "text/vtt")
|
|
return
|
|
super()._get(request)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("binary", type=Path)
|
|
parser.add_argument("fixtures", type=Path)
|
|
parser.add_argument("app", type=Path)
|
|
parser.add_argument("--cast-session", type=Path)
|
|
args = parser.parse_args()
|
|
if args.cast_session:
|
|
with CaptionStreamingServer(args.fixtures) as server:
|
|
result = subprocess.run([str(args.cast_session.resolve()), "--youtube-subtitles",
|
|
str(args.binary.resolve()), str(args.fixtures.resolve() / "cache.mp4")],
|
|
env=dict(os.environ, TEREBI_CAST_YOUTUBE_SOURCE=server.base_url), timeout=150, check=True)
|
|
assert server.caption_requests == 2, "only initial selection and local restoration fetch captions"
|
|
return result.returncode
|
|
macOS = args.app.resolve() / "Contents/MacOS"
|
|
extractor = str(macOS / "yt-dlp")
|
|
runtime = macOS / "deno"
|
|
if not os.access(extractor, os.X_OK) or not os.access(runtime, os.X_OK):
|
|
parser.error("Missing bundled YouTube helpers; build the app first")
|
|
required = ("quality-1080.mp4", "quality-720.mp4")
|
|
if any(not (args.fixtures / name).is_file() for name in required):
|
|
parser.error("Missing real quality playback fixtures")
|
|
with CaptionStreamingServer(args.fixtures) as server, tempfile.TemporaryDirectory(prefix="terebi-quality-check-") as directory:
|
|
root = Path(directory)
|
|
(root / "deno").symlink_to(runtime)
|
|
formats = []
|
|
for height, width in ((720, 1280), (1080, 1920)):
|
|
path = "/media/quality-720.mp4" if height == 720 else "/stall/quality-low/quality-1080.mp4?after_seconds=2"
|
|
formats.append({"format_id": str(height), "height": height, "width": width,
|
|
"url": server.base_url + path, "protocol": "http", "ext": "mp4",
|
|
"vcodec": "mpeg4", "acodec": "aac", "dynamic_range": "SDR",
|
|
"http_headers": {"X-Terebi-Fixture": "combined"}})
|
|
info = {"id": "aqz-KE-bpKQ", "title": "Automatic quality playback fixture", "duration": 120,
|
|
"is_live": False, "webpage_url": "https://www.youtube.com/watch?v=aqz-KE-bpKQ",
|
|
"extractor": "youtube", "extractor_key": "Youtube", "formats": formats,
|
|
"automatic_captions": {"en-orig": [{"ext": "vtt", "name": "English (Original)",
|
|
"url": server.base_url + "/captions.vtt", "http_headers": {"X-Subtitle": "caption-private"}}]}}
|
|
metadata = root / "info.json"
|
|
metadata.write_text(json.dumps(info))
|
|
wrapper = root / "yt-dlp"
|
|
control = root / "control"
|
|
control.mkdir()
|
|
wrapper.write_text("#!/bin/bash\nset -eu\nargs=(\"$@\")\n"
|
|
+ "control=" + shlex.quote(str(control)) + "\n"
|
|
+ 'if [[ -f "$control/gate-open" ]]; then\n'
|
|
+ ' : > "$control/entered-open"\n'
|
|
+ ' while [[ -f "$control/gate-open" && ! -f "$control/release-open" ]]; do sleep 0.01; done\n'
|
|
+ 'fi\n'
|
|
+ 'if [[ "$*" == *"[height=1080]"* ]]; then\n'
|
|
+ ' if [[ -f "$control/fail-1080" ]]; then\n'
|
|
+ ' echo "Fixture extraction deliberately failed" >&2; exit 1\n'
|
|
+ ' fi\n'
|
|
+ ' if [[ -f "$control/gate-1080" ]]; then\n'
|
|
+ " " + shlex.quote(extractor) + " --load-info-json " + shlex.quote(str(metadata))
|
|
+ ' "${args[@]:0:${#args[@]}-2}" > "$control/output-1080.json"\n'
|
|
+ ' trap \': > "$control/finished-1080"\' EXIT\n'
|
|
+ ' : > "$control/entered-1080"\n'
|
|
+ ' while [[ ! -f "$control/release-1080" ]]; do sleep 0.01; done\n'
|
|
+ ' cat "$control/output-1080.json" || true\n'
|
|
+ ' exit 0\n'
|
|
+ ' fi\n'
|
|
+ 'fi\nexec ' + shlex.quote(extractor)
|
|
+ " --load-info-json " + shlex.quote(str(metadata))
|
|
+ ' "${args[@]:0:${#args[@]}-2}"\n')
|
|
wrapper.chmod(0o755)
|
|
environment = dict(os.environ, PATH=str(root) + os.pathsep + os.environ.get("PATH", ""),
|
|
TEREBI_QUALITY_CONTROL=str(control), TEREBI_CAPTION_COUNT=server.base_url + "/caption-count")
|
|
try:
|
|
result = subprocess.run([str(args.binary.resolve())], env=environment, timeout=90, check=False)
|
|
except subprocess.TimeoutExpired:
|
|
raise SystemExit("FAIL: adaptive playback exceeded its infrastructure deadline")
|
|
return result.returncode
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|