gecko-web-runtime-client 1.4.0__tar.gz → 1.4.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. gecko_web_runtime_client-1.4.2/PACKAGE_README.md +58 -0
  2. gecko_web_runtime_client-1.4.2/PKG-INFO +73 -0
  3. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/__init__.py +5 -0
  4. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/api/runtime.py +64 -3
  5. gecko_web_runtime_client-1.4.2/gecko_web_runtime/media_capture.py +778 -0
  6. gecko_web_runtime_client-1.4.2/gecko_web_runtime/media_downloader.py +310 -0
  7. gecko_web_runtime_client-1.4.2/gecko_web_runtime_client.egg-info/PKG-INFO +73 -0
  8. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime_client.egg-info/SOURCES.txt +2 -0
  9. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime_client.egg-info/requires.txt +1 -1
  10. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/pyproject.toml +2 -2
  11. gecko_web_runtime_client-1.4.0/PACKAGE_README.md +0 -17
  12. gecko_web_runtime_client-1.4.0/PKG-INFO +0 -32
  13. gecko_web_runtime_client-1.4.0/gecko_web_runtime_client.egg-info/PKG-INFO +0 -32
  14. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/README.md +0 -0
  15. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/__main__.py +0 -0
  16. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/_leases.py +0 -0
  17. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/api/__init__.py +0 -0
  18. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/api/context.py +0 -0
  19. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/api/page.py +0 -0
  20. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/api/pool.py +0 -0
  21. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/data_plane.py +0 -0
  22. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/diagnostics/__init__.py +0 -0
  23. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/diagnostics/gc_cc.py +0 -0
  24. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/diagnostics/logging.py +0 -0
  25. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/diagnostics/memory.py +0 -0
  26. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/diagnostics/metrics.py +0 -0
  27. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/diagnostics/resources.py +0 -0
  28. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/http.py +0 -0
  29. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/installer.py +0 -0
  30. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/iocp_pipe.py +0 -0
  31. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/models/__init__.py +0 -0
  32. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/models/navigation.py +0 -0
  33. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/network/__init__.py +0 -0
  34. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/network/_urls.py +0 -0
  35. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/network/client.py +0 -0
  36. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/network/exceptions.py +0 -0
  37. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/network/models.py +0 -0
  38. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/network/transport.py +0 -0
  39. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/pipe.py +0 -0
  40. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/session.py +0 -0
  41. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/workflows/__init__.py +0 -0
  42. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/workflows/turnstile.py +0 -0
  43. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/workflows/wechat.py +0 -0
  44. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/workflows/wechat_challenge.py +0 -0
  45. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime_client.egg-info/dependency_links.txt +0 -0
  46. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime_client.egg-info/top_level.txt +0 -0
  47. {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/setup.cfg +0 -0
@@ -0,0 +1,58 @@
1
+ # Gecko Web Runtime Client
2
+
3
+ Python page and browser client for a versioned Firefox-derived WPR runtime.
4
+ It exposes pages, contexts, navigation, DOM access, screenshots, diagnostics,
5
+ Turnstile and WeChat workflow adapters.
6
+
7
+ The native runtime is installed separately:
8
+
9
+ ```powershell
10
+ python -m gecko_web_runtime install-runtime --version 155
11
+ ```
12
+
13
+ The runtime is held in a private GitHub Release. Automatic download needs
14
+ access to `childclm/gecko-web-runtime`. The Python client depends on
15
+ `wpr-runtime-core` for Worker lifecycle and protocol compatibility. Historical
16
+ HTTP imports are retained through `wpr-http`.
17
+
18
+ ## Media capture
19
+
20
+ For a browser-played Widevine page, use `AsyncMediaDownloader`. It starts the
21
+ Worker, provisions the caller's Widevine profile, enables bounded native H.264
22
+ capture, waits for playback progress, writes a checkpoint, and verifies the
23
+ final MP4 with FFmpeg.
24
+
25
+ ```python
26
+ import asyncio
27
+ from pathlib import Path
28
+
29
+ from gecko_web_runtime import AsyncMediaDownloader
30
+
31
+
32
+ async def main() -> None:
33
+ result = await AsyncMediaDownloader(
34
+ "https://example.test/video",
35
+ output_dir=Path("./media-output"),
36
+ ffmpeg=Path(r"C:\\ffmpeg\\bin\\ffmpeg.exe"),
37
+ expected_duration=2575.32,
38
+ max_retries=1,
39
+ ).download()
40
+ if not result["completed"]:
41
+ raise RuntimeError(result["attempts"])
42
+ print(result["package"]["output"])
43
+
44
+
45
+ asyncio.run(main())
46
+ ```
47
+
48
+ Set `WPR_RUNTIME_DIR`, `WPR_WORKER_EXE`, `WPR_PERSISTENT_PROFILE_DIR`, and
49
+ `WPR_ENABLE_CDM=1` before creating the downloader. Widevine is installed by
50
+ Firefox's official GMP installer into the persistent WPR profile; no existing
51
+ Firefox profile or component is imported. This requires the Worker build with
52
+ `runtime.widevine_start/status` support (Runtime 155.0.8 or later).
53
+ For a bounded
54
+ smoke test of a longer page, use `sample_seconds=120` instead of
55
+ `expected_duration`. The downloader keeps a `checkpoint.json`; rerunning with
56
+ the same output directory returns a completed result without starting another
57
+ Worker.
58
+
@@ -0,0 +1,73 @@
1
+ Metadata-Version: 2.4
2
+ Name: gecko-web-runtime-client
3
+ Version: 1.4.2
4
+ Summary: Thin Python client for the Firefox-derived WPR Gecko Worker protocol
5
+ Author: WPR project
6
+ License: MPL-2.0-compatible client; see the runtime bundle notices
7
+ Project-URL: Repository, https://github.com/childclm/gecko-web-runtime-client
8
+ Project-URL: Documentation, https://github.com/childclm/gecko-web-runtime-client/blob/main/README.md
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: wpr-runtime-core<0.3,>=0.2.2
12
+ Requires-Dist: wpr-http<0.3,>=0.2.0
13
+ Provides-Extra: diagnostics
14
+ Requires-Dist: loguru>=0.7.2; extra == "diagnostics"
15
+
16
+ # Gecko Web Runtime Client
17
+
18
+ Python page and browser client for a versioned Firefox-derived WPR runtime.
19
+ It exposes pages, contexts, navigation, DOM access, screenshots, diagnostics,
20
+ Turnstile and WeChat workflow adapters.
21
+
22
+ The native runtime is installed separately:
23
+
24
+ ```powershell
25
+ python -m gecko_web_runtime install-runtime --version 155
26
+ ```
27
+
28
+ The runtime is held in a private GitHub Release. Automatic download needs
29
+ access to `childclm/gecko-web-runtime`. The Python client depends on
30
+ `wpr-runtime-core` for Worker lifecycle and protocol compatibility. Historical
31
+ HTTP imports are retained through `wpr-http`.
32
+
33
+ ## Media capture
34
+
35
+ For a browser-played Widevine page, use `AsyncMediaDownloader`. It starts the
36
+ Worker, provisions the caller's Widevine profile, enables bounded native H.264
37
+ capture, waits for playback progress, writes a checkpoint, and verifies the
38
+ final MP4 with FFmpeg.
39
+
40
+ ```python
41
+ import asyncio
42
+ from pathlib import Path
43
+
44
+ from gecko_web_runtime import AsyncMediaDownloader
45
+
46
+
47
+ async def main() -> None:
48
+ result = await AsyncMediaDownloader(
49
+ "https://example.test/video",
50
+ output_dir=Path("./media-output"),
51
+ ffmpeg=Path(r"C:\\ffmpeg\\bin\\ffmpeg.exe"),
52
+ expected_duration=2575.32,
53
+ max_retries=1,
54
+ ).download()
55
+ if not result["completed"]:
56
+ raise RuntimeError(result["attempts"])
57
+ print(result["package"]["output"])
58
+
59
+
60
+ asyncio.run(main())
61
+ ```
62
+
63
+ Set `WPR_RUNTIME_DIR`, `WPR_WORKER_EXE`, `WPR_PERSISTENT_PROFILE_DIR`, and
64
+ `WPR_ENABLE_CDM=1` before creating the downloader. Widevine is installed by
65
+ Firefox's official GMP installer into the persistent WPR profile; no existing
66
+ Firefox profile or component is imported. This requires the Worker build with
67
+ `runtime.widevine_start/status` support (Runtime 155.0.8 or later).
68
+ For a bounded
69
+ smoke test of a longer page, use `sample_seconds=120` instead of
70
+ `expected_duration`. The downloader keeps a `checkpoint.json`; rerunning with
71
+ the same output directory returns a completed result without starting another
72
+ Worker.
73
+
@@ -24,6 +24,8 @@ from .http import (
24
24
  WprTransportError,
25
25
  )
26
26
  from .installer import install_runtime
27
+ from .media_capture import package_media_capture
28
+ from .media_downloader import AsyncMediaDownloader, MediaDownloadError
27
29
  from .models.navigation import TurnstileResult, WechatArticleResult
28
30
  from .session import (
29
31
  AsyncGeckoWorkerSession,
@@ -38,12 +40,14 @@ __all__ = [
38
40
  "AsyncGeckoRuntime",
39
41
  "AsyncGeckoWorkerPool",
40
42
  "AsyncGeckoWorkerSession",
43
+ "AsyncMediaDownloader",
41
44
  "AsyncWprHttpClient",
42
45
  "BrowserContext",
43
46
  "GeckoWorkerSession",
44
47
  "Headers",
45
48
  "HttpRequest",
46
49
  "HttpResponse",
50
+ "MediaDownloadError",
47
51
  "MetricsOptions",
48
52
  "NetworkTiming",
49
53
  "Page",
@@ -68,4 +72,5 @@ __all__ = [
68
72
  "WprTimeoutError",
69
73
  "WprTransportError",
70
74
  "install_runtime",
75
+ "package_media_capture",
71
76
  ]
@@ -2,12 +2,13 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import asyncio
5
6
  from dataclasses import dataclass
6
7
  from pathlib import Path
7
8
  from typing import Any
8
9
 
9
- from ..session import AsyncGeckoWorkerSession, RuntimeConfig
10
10
  from ..diagnostics import worker_resource_snapshot
11
+ from ..session import AsyncGeckoWorkerSession, RuntimeConfig
11
12
  from .context import BrowserContext
12
13
  from .page import Page
13
14
 
@@ -64,8 +65,50 @@ class AsyncGeckoRuntime:
64
65
  async def __aenter__(self) -> "AsyncGeckoRuntime":
65
66
  self._session = AsyncGeckoWorkerSession(self.config, self.fpfile, self.proxy)
66
67
  await self._session.__aenter__()
68
+ worker_config = getattr(getattr(self._session, "_sync", None), "config", self.config)
69
+ if getattr(worker_config, "enable_widevine", False):
70
+ try:
71
+ await self.ensure_widevine()
72
+ except BaseException:
73
+ await self._session.__aexit__(None, None, None)
74
+ self._session = None
75
+ raise
67
76
  return self
68
77
 
78
+ async def ensure_widevine(self, *, timeout: float = 120) -> dict[str, Any]:
79
+ """Download, verify and register Widevine using Firefox's GMP installer.
80
+
81
+ Requires enable_widevine=True and a persistent WPR profile. A ready
82
+ result confirms registration; EME/CDM process readiness is a separate
83
+ playback capability check.
84
+ """
85
+ from wpr_runtime_core.session import require_ok
86
+ if self._session is None:
87
+ raise RuntimeError("AsyncGeckoRuntime is not open")
88
+ if not self._session._sync.config.enable_widevine:
89
+ raise ValueError("Set RuntimeConfig(enable_widevine=True) before starting the Worker")
90
+ if timeout <= 0:
91
+ raise ValueError("timeout must be positive")
92
+ last_state: dict[str, Any] = {}
93
+ async def wait():
94
+ nonlocal last_state
95
+ value = require_ok("runtime.widevine_start", await self._session.request(
96
+ {"method": "runtime.widevine_start"}))
97
+ while True:
98
+ last_state = value
99
+ if value.get("status") == "ready":
100
+ return value
101
+ if value.get("status") == "failed":
102
+ error = value.get("error", {})
103
+ raise RuntimeError(f"Widevine [{error.get('code')}] stage={value.get('stage')}: {error.get('message')}")
104
+ await asyncio.sleep(0.2)
105
+ value = require_ok("runtime.widevine_status", await self._session.request(
106
+ {"method": "runtime.widevine_status"}))
107
+ try:
108
+ return await asyncio.wait_for(wait(), timeout)
109
+ except asyncio.TimeoutError as exc:
110
+ raise TimeoutError(f"Widevine installation did not finish within {timeout}s; last_state={last_state}") from exc
111
+
69
112
  async def new_page(self) -> Page:
70
113
  if self._session is None:
71
114
  raise RuntimeError("AsyncGeckoRuntime is not open")
@@ -78,6 +121,24 @@ class AsyncGeckoRuntime:
78
121
  self._pages.add(page)
79
122
  return page
80
123
 
124
+ async def media_capture_snapshot(self) -> dict[str, Any]:
125
+ """Read opt-in CDM output metadata for the current Worker.
126
+
127
+ Configure ``RuntimeConfig(media_capture=True)`` before startup. The
128
+ result contains path, track, timing and size summaries, never media
129
+ bytes or license material.
130
+ """
131
+ if self._session is None:
132
+ raise RuntimeError("AsyncGeckoRuntime is not open")
133
+ return await self._session.media_capture_snapshot()
134
+
135
+ @property
136
+ def media_output_dir(self) -> Path | None:
137
+ """Per-session media output directory, when file capture is enabled."""
138
+ if self._session is None:
139
+ raise RuntimeError("AsyncGeckoRuntime is not open")
140
+ return self._session._sync.media_output_dir
141
+
81
142
  def _forget_page(self, page: Page) -> None:
82
143
  self._pages.discard(page)
83
144
 
@@ -105,8 +166,8 @@ class AsyncGeckoRuntime:
105
166
  old_pid = getattr(getattr(self._session, "_sync", None), "process", None)
106
167
  old_pid = getattr(old_pid, "pid", None)
107
168
  await self._session.__aexit__(None, None, None)
108
- self._session = AsyncGeckoWorkerSession(self.config, self.fpfile, self.proxy)
109
- await self._session.__aenter__()
169
+ self._session = None
170
+ await self.__aenter__()
110
171
  new_pid = getattr(getattr(self._session, "_sync", None), "process", None)
111
172
  new_pid = getattr(new_pid, "pid", None)
112
173
  self._completed_jobs = 0