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.
- gecko_web_runtime_client-1.4.2/PACKAGE_README.md +58 -0
- gecko_web_runtime_client-1.4.2/PKG-INFO +73 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/__init__.py +5 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/api/runtime.py +64 -3
- gecko_web_runtime_client-1.4.2/gecko_web_runtime/media_capture.py +778 -0
- gecko_web_runtime_client-1.4.2/gecko_web_runtime/media_downloader.py +310 -0
- gecko_web_runtime_client-1.4.2/gecko_web_runtime_client.egg-info/PKG-INFO +73 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime_client.egg-info/SOURCES.txt +2 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime_client.egg-info/requires.txt +1 -1
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/pyproject.toml +2 -2
- gecko_web_runtime_client-1.4.0/PACKAGE_README.md +0 -17
- gecko_web_runtime_client-1.4.0/PKG-INFO +0 -32
- gecko_web_runtime_client-1.4.0/gecko_web_runtime_client.egg-info/PKG-INFO +0 -32
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/README.md +0 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/__main__.py +0 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/_leases.py +0 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/api/__init__.py +0 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/api/context.py +0 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/api/page.py +0 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/api/pool.py +0 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/data_plane.py +0 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/diagnostics/__init__.py +0 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/diagnostics/gc_cc.py +0 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/diagnostics/logging.py +0 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/diagnostics/memory.py +0 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/diagnostics/metrics.py +0 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/diagnostics/resources.py +0 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/http.py +0 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/installer.py +0 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/iocp_pipe.py +0 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/models/__init__.py +0 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/models/navigation.py +0 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/network/__init__.py +0 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/network/_urls.py +0 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/network/client.py +0 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/network/exceptions.py +0 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/network/models.py +0 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/network/transport.py +0 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/pipe.py +0 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/session.py +0 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/workflows/__init__.py +0 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/workflows/turnstile.py +0 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/workflows/wechat.py +0 -0
- {gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/workflows/wechat_challenge.py +0 -0
- {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
- {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
- {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
|
+
|
{gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/__init__.py
RENAMED
|
@@ -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
|
]
|
{gecko_web_runtime_client-1.4.0 → gecko_web_runtime_client-1.4.2}/gecko_web_runtime/api/runtime.py
RENAMED
|
@@ -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 =
|
|
109
|
-
await self.
|
|
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
|