rbx 3.18.3.dev156__tar.gz → 3.18.3.dev157__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 (53) hide show
  1. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/PKG-INFO +15 -16
  2. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/README.md +1 -1
  3. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/pyproject.toml +16 -17
  4. rbx-3.18.3.dev157/rbx/__init__.py +1 -0
  5. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/toolkit/__init__.py +2 -43
  6. rbx-3.18.3.dev157/rbx/toolkit/browser.py +195 -0
  7. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/toolkit/media.py +1 -9
  8. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx.egg-info/PKG-INFO +15 -16
  9. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx.egg-info/SOURCES.txt +0 -1
  10. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx.egg-info/requires.txt +14 -15
  11. rbx-3.18.3.dev156/rbx/__init__.py +0 -1
  12. rbx-3.18.3.dev156/rbx/toolkit/browser.py +0 -141
  13. rbx-3.18.3.dev156/rbx/toolkit/web.py +0 -8
  14. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/LICENSE +0 -0
  15. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/auth/__init__.py +0 -0
  16. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/auth/decorators.py +0 -0
  17. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/auth/keystore.py +0 -0
  18. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/auth/mock.py +0 -0
  19. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/aws/__init__.py +0 -0
  20. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/aws/s3.py +0 -0
  21. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/buildtools/__init__.py +0 -0
  22. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/buildtools/cli.py +0 -0
  23. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/buildtools/tasks/__init__.py +0 -0
  24. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/buildtools/tasks/apprunner.py +0 -0
  25. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/buildtools/tasks/ec2.py +0 -0
  26. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/buildtools/tasks/image.py +0 -0
  27. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/buildtools/tasks/misc.py +0 -0
  28. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/clients/__init__.py +0 -0
  29. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/clients/adsquare.py +0 -0
  30. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/clients/broadsign.py +0 -0
  31. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/clients/oxr.py +0 -0
  32. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/clients/panels.py +0 -0
  33. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/clients/reporting.py +0 -0
  34. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/clients/retry.py +0 -0
  35. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/exceptions.py +0 -0
  36. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/gcp/__init__.py +0 -0
  37. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/gcp/cloud_tasks.py +0 -0
  38. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/gcp/pubsub.py +0 -0
  39. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/gcp/storage.py +0 -0
  40. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/logging.py +0 -0
  41. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/settings.py +0 -0
  42. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/toolkit/cli.py +0 -0
  43. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/toolkit/exporter.py +0 -0
  44. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/toolkit/utils.py +0 -0
  45. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/utils/__init__.py +0 -0
  46. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/utils/mdm.py +0 -0
  47. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/utils/vast.py +0 -0
  48. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/web/__init__.py +0 -0
  49. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx/web/handlers.py +0 -0
  50. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx.egg-info/dependency_links.txt +0 -0
  51. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx.egg-info/entry_points.txt +0 -0
  52. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/rbx.egg-info/top_level.txt +0 -0
  53. {rbx-3.18.3.dev156 → rbx-3.18.3.dev157}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rbx
3
- Version: 3.18.3.dev156
3
+ Version: 3.18.3.dev157
4
4
  Summary: A collection of common tools for Scoota services.
5
5
  Author-email: The Scoota Engineering Team <engineering@scoota.com>
6
6
  License:
@@ -228,6 +228,19 @@ Requires-Dist: Click<9
228
228
  Requires-Dist: colorama
229
229
  Requires-Dist: lxml<6,>=5.3.0
230
230
  Requires-Dist: requests>=2.31.0
231
+ Provides-Extra: all
232
+ Requires-Dist: boto3; extra == "all"
233
+ Requires-Dist: fabric~=3.2.0; extra == "all"
234
+ Requires-Dist: google-cloud-error-reporting<2,>=1.9.2; extra == "all"
235
+ Requires-Dist: google-cloud-firestore<3,>=2.3; extra == "all"
236
+ Requires-Dist: google-cloud-pubsub<3,>=2.9; extra == "all"
237
+ Requires-Dist: google-cloud-storage<3,>=2.1; extra == "all"
238
+ Requires-Dist: google-cloud-tasks<3,>=2.7; extra == "all"
239
+ Requires-Dist: Jinja2==3.1.2; extra == "all"
240
+ Requires-Dist: playwright~=1.51.0; extra == "all"
241
+ Requires-Dist: sh~=2.0.6; extra == "all"
242
+ Requires-Dist: starlette==0.38.2; extra == "all"
243
+ Requires-Dist: twine; extra == "all"
231
244
  Provides-Extra: auth
232
245
  Requires-Dist: google-cloud-firestore<3,>=2.3; extra == "auth"
233
246
  Provides-Extra: buildtools
@@ -247,24 +260,10 @@ Requires-Dist: google-cloud-tasks<3,>=2.7; extra == "tasks"
247
260
  Provides-Extra: toolkit
248
261
  Requires-Dist: boto3; extra == "toolkit"
249
262
  Requires-Dist: google-cloud-storage<3,>=2.1; extra == "toolkit"
250
- Requires-Dist: mutagen~=1.47.0; extra == "toolkit"
251
263
  Requires-Dist: playwright~=1.51.0; extra == "toolkit"
252
264
  Requires-Dist: sh~=2.0.6; extra == "toolkit"
253
- Requires-Dist: tqdm~=4.66.1; extra == "toolkit"
254
265
  Provides-Extra: web
255
266
  Requires-Dist: google-cloud-error-reporting<2,>=1.9.2; extra == "web"
256
- Provides-Extra: test
257
- Requires-Dist: google-cloud-error-reporting<2,>=1.9.2; extra == "test"
258
- Requires-Dist: google-cloud-firestore<3,>=2.3; extra == "test"
259
- Requires-Dist: google-cloud-pubsub<3,>=2.9; extra == "test"
260
- Requires-Dist: google-cloud-storage<3,>=2.1; extra == "test"
261
- Requires-Dist: google-cloud-tasks<3,>=2.7; extra == "test"
262
- Requires-Dist: httpx; extra == "test"
263
- Requires-Dist: mutagen~=1.47.0; extra == "test"
264
- Requires-Dist: playwright~=1.51.0; extra == "test"
265
- Requires-Dist: sh~=2.0.6; extra == "test"
266
- Requires-Dist: starlette==0.38.2; extra == "test"
267
- Requires-Dist: tqdm~=4.66.1; extra == "test"
268
267
  Dynamic: license-file
269
268
 
270
269
  The library provides a collection of tools and functionality for the Scoota Platform services.
@@ -276,7 +275,7 @@ The library provides a collection of tools and functionality for the Scoota Plat
276
275
  Install this library in a virtualenv using pip.
277
276
 
278
277
  ```
279
- pip install rbx
278
+ pip install rbx[all]
280
279
  ```
281
280
 
282
281
  > See the [wiki](https://github.com/rockabox/rbx/wiki) for more details on the contents of this library.
@@ -7,7 +7,7 @@ The library provides a collection of tools and functionality for the Scoota Plat
7
7
  Install this library in a virtualenv using pip.
8
8
 
9
9
  ```
10
- pip install rbx
10
+ pip install rbx[all]
11
11
  ```
12
12
 
13
13
  > See the [wiki](https://github.com/rockabox/rbx/wiki) for more details on the contents of this library.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "rbx"
7
- version = "3.18.3.dev156"
7
+ version = "3.18.3.dev157"
8
8
  description = "A collection of common tools for Scoota services."
9
9
  authors = [
10
10
  { name = "The Scoota Engineering Team", email = "engineering@scoota.com" }
@@ -35,6 +35,20 @@ dependencies = [
35
35
  ]
36
36
 
37
37
  [project.optional-dependencies]
38
+ all = [
39
+ "boto3",
40
+ "fabric~=3.2.0",
41
+ "google-cloud-error-reporting>=1.9.2,<2",
42
+ "google-cloud-firestore>=2.3,<3",
43
+ "google-cloud-pubsub>=2.9,<3",
44
+ "google-cloud-storage>=2.1,<3",
45
+ "google-cloud-tasks>=2.7,<3",
46
+ "Jinja2==3.1.2",
47
+ "playwright~=1.51.0",
48
+ "sh~=2.0.6",
49
+ "starlette==0.38.2",
50
+ "twine",
51
+ ]
38
52
  auth = [
39
53
  "google-cloud-firestore>=2.3,<3"
40
54
  ]
@@ -60,27 +74,12 @@ tasks = [
60
74
  toolkit = [
61
75
  "boto3",
62
76
  "google-cloud-storage>=2.1,<3",
63
- "mutagen~=1.47.0",
64
77
  "playwright~=1.51.0",
65
78
  "sh~=2.0.6",
66
- "tqdm~=4.66.1",
67
79
  ]
68
80
  web = [
69
81
  "google-cloud-error-reporting>=1.9.2,<2"
70
82
  ]
71
- test = [
72
- "google-cloud-error-reporting>=1.9.2,<2",
73
- "google-cloud-firestore>=2.3,<3",
74
- "google-cloud-pubsub>=2.9,<3",
75
- "google-cloud-storage>=2.1,<3",
76
- "google-cloud-tasks>=2.7,<3",
77
- "httpx",
78
- "mutagen~=1.47.0",
79
- "playwright~=1.51.0",
80
- "sh~=2.0.6",
81
- "starlette==0.38.2",
82
- "tqdm~=4.66.1",
83
- ]
84
83
 
85
84
  [project.scripts]
86
85
  buildtools = "rbx.buildtools.cli:cli [buildtools]"
@@ -91,7 +90,7 @@ homepage = "https://github.com/rockabox/rbx"
91
90
  repository = "https://github.com/rockabox/rbx.git"
92
91
 
93
92
  [tool.bumpversion]
94
- current_version = "3.18.3.dev156"
93
+ current_version = "3.18.3.dev157"
95
94
  commit = true
96
95
  parse = """
97
96
  (?P<major>\\d+)\\.
@@ -0,0 +1 @@
1
+ __version__ = "3.18.3.dev157"
@@ -1,17 +1,12 @@
1
1
  __all__ = ["Options", "run"]
2
2
 
3
3
  import logging
4
- import shutil
5
4
  import tempfile
6
5
  from pathlib import Path
7
6
  from typing import NamedTuple, Optional
8
7
 
9
- import mutagen
10
-
11
8
  from .browser import record, screenshot
12
- from .media import convert, merge
13
9
  from .utils import upload
14
- from .web import download
15
10
 
16
11
  logger = logging.getLogger(__name__)
17
12
 
@@ -35,52 +30,16 @@ def filename(output, filename):
35
30
  return str(Path(output) / filename)
36
31
 
37
32
 
38
- def add_audio(filename: str, path: Path) -> None:
39
- """Add audio stream.
40
-
41
- The source of the audio is extracted from a "source.mp4" video file found in the `path`.
42
- The video file is first renamed because FFmpeg cannot edit existing files in-place.
43
- """
44
- source = str(path / "source.mp4")
45
- audio = str(path / "audio.mp3")
46
- convert(infile=source, outfile=audio)
47
-
48
- video = str(path / f"_{filename}")
49
- shutil.move(path / filename, video)
50
- merge(infiles=[video, audio], outfile=str(path / filename))
51
-
52
-
53
33
  async def capture(filename: str, options: Options, path: Path) -> None:
54
- recording = await record(
34
+ await record(
55
35
  dirname=str(path),
36
+ filename=str(path / filename),
56
37
  duration=1000 * options.duration,
57
38
  height=options.height,
58
39
  url=options.url,
59
40
  width=options.width,
60
41
  )
61
42
 
62
- audio = None
63
- if recording.source:
64
- logger.debug(f"Downloading video source from {recording.source}")
65
- audio = path / "source.mp4"
66
- download(url=recording.source, filename=str(audio))
67
- file = mutagen.File(audio)
68
- if file.info.channels == 0:
69
- logger.debug("Video source has no audio")
70
- audio = None
71
-
72
- convert(
73
- infile=recording.location,
74
- outfile=str(path / filename),
75
- delay=recording.delay,
76
- duration=1000 * options.duration,
77
- # use highest quality, and place MOOV atom at the beginning
78
- opts=["-crf", "1", "-movflags", "faststart"],
79
- )
80
- if audio:
81
- logger.info(f"Adding audio from {audio}")
82
- add_audio(filename=filename, path=path)
83
-
84
43
 
85
44
  async def screengrab(filename: str, options: Options, path: Path) -> None:
86
45
  await screenshot(
@@ -0,0 +1,195 @@
1
+ import datetime
2
+ import logging
3
+ import os
4
+ import signal
5
+ import subprocess
6
+ import time
7
+ import uuid
8
+
9
+ from playwright.async_api import async_playwright
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ def get_free_display(base=99, max_tries=100):
15
+ for i in range(max_tries):
16
+ display = f":{base + i}"
17
+ if not os.path.exists(f"/tmp/.X{base + i}-lock"):
18
+ return display
19
+
20
+ raise RuntimeError("No free display found")
21
+
22
+
23
+ class RecordingSession:
24
+ def __init__(self, dirname, width, height):
25
+ self.display = get_free_display()
26
+ self.dirname = dirname
27
+ self.width = width
28
+ self.height = height
29
+ self.resolution = f"{self.width}x{self.height}"
30
+ self.session_id = str(uuid.uuid4())[:8]
31
+ self.sink_name = f"rec_sink_{self.session_id}"
32
+ self.pulse_dir = f"{dirname}/pulseaudio"
33
+ self.pulse_socket = os.path.join(self.pulse_dir, "pulse-socket")
34
+ self.proc = []
35
+
36
+ async def __aenter__(self):
37
+ return self
38
+
39
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
40
+ logger.debug("Stopping processes...")
41
+
42
+ # PulseAudio needs to be terminated via the kill command.
43
+ subprocess.Popen(
44
+ ["pulseaudio", "--kill"],
45
+ env={
46
+ "PULSE_SERVER": f"unix:{self.pulse_socket}",
47
+ "XDG_RUNTIME_DIR": self.pulse_dir,
48
+ },
49
+ )
50
+
51
+ for proc in reversed(self.proc):
52
+ if proc and proc.poll() is None:
53
+ try:
54
+ proc.send_signal(signal.SIGINT)
55
+ proc.wait(timeout=3)
56
+ except subprocess.TimeoutExpired:
57
+ proc.kill()
58
+
59
+ def start_xvfb(self):
60
+ logger.debug(f"Starting Xvfb [{self.display}]...")
61
+ # fmt: off
62
+ self.proc.append(
63
+ subprocess.Popen(
64
+ [
65
+ "Xvfb", self.display, "-screen", "0", f"{self.resolution}x24",
66
+ # These options are needed when running in a container
67
+ "-ac", "-nolisten", "tcp", "-nolisten", "unix",
68
+ ]
69
+ )
70
+ )
71
+ # fmt: on
72
+ time.sleep(1)
73
+
74
+ logger.debug(f"Starting unclutter [{self.display}]...")
75
+ self.proc.append(
76
+ subprocess.Popen(
77
+ ["unclutter", "-idle", "0"],
78
+ env={"DISPLAY": self.display},
79
+ )
80
+ )
81
+
82
+ def start_pulseaudio(self):
83
+ logger.debug(f"Starting PulseAudio [{self.pulse_dir}.{self.sink_name}]...")
84
+ os.makedirs(self.pulse_dir, exist_ok=True)
85
+ os.chmod(self.pulse_dir, 0o700) # PulseAudio requires strict permissions
86
+
87
+ subprocess.Popen(
88
+ [
89
+ "pulseaudio",
90
+ "--start",
91
+ "--exit-idle-time=300",
92
+ f"--load=module-native-protocol-unix socket={self.pulse_socket}",
93
+ f"--load=module-null-sink sink_name={self.sink_name}"
94
+ f" sink_properties=device.description={self.sink_name}",
95
+ ],
96
+ env={"DISPLAY": self.display, "XDG_RUNTIME_DIR": self.pulse_dir},
97
+ )
98
+ time.sleep(2)
99
+
100
+ def start_ffmpeg(self, duration, output_path):
101
+ logger.debug(f"Starting FFmpeg recording [{self.display}:{self.sink_name}]...")
102
+ # fmt: off
103
+ self.proc.append(
104
+ subprocess.Popen(
105
+ [
106
+ "ffmpeg", "-y",
107
+ "-t", str(duration),
108
+ "-video_size", self.resolution,
109
+ "-f", "x11grab",
110
+ "-i", f"{self.display}.0",
111
+ "-f", "pulse",
112
+ "-i", f"{self.sink_name}.monitor",
113
+ "-c:v", "libx264",
114
+ "-preset", "slow",
115
+ "-crf", "8",
116
+ "-pix_fmt", "yuv420p",
117
+ "-c:a", "aac",
118
+ "-b:a", "256k",
119
+ "-movflags",
120
+ "+faststart",
121
+ output_path,
122
+ ],
123
+ stdout=subprocess.DEVNULL,
124
+ stderr=subprocess.STDOUT,
125
+ env={
126
+ "PULSE_SERVER": f"unix:{self.pulse_socket}",
127
+ "XDG_RUNTIME_DIR": self.pulse_dir,
128
+ },
129
+ )
130
+ )
131
+ # fmt: on
132
+
133
+ async def capture(self, url, duration, output_path):
134
+ self.start_xvfb()
135
+ self.start_pulseaudio()
136
+
137
+ async with async_playwright() as p:
138
+ logger.debug(
139
+ f"Capturing Chromium session [{self.display}:{self.sink_name}]..."
140
+ )
141
+ context = await p.chromium.launch_persistent_context(
142
+ user_data_dir=f"{self.dirname}/user-data",
143
+ headless=False,
144
+ channel="chrome",
145
+ args=[
146
+ "--no-sandbox",
147
+ "--disable-gpu",
148
+ "--disable-software-rasterizer",
149
+ f"--display={self.display}",
150
+ "--autoplay-policy=no-user-gesture-required",
151
+ "--use-fake-ui-for-media-stream",
152
+ f"--alsa-output-device={self.sink_name}",
153
+ "--start-fullscreen",
154
+ "--window-position=0,0",
155
+ f"--window-size={self.width},{self.height}",
156
+ ],
157
+ ignore_default_args=["--enable-automation"],
158
+ no_viewport=True,
159
+ env={
160
+ "PULSE_SERVER": f"unix:{self.pulse_socket}",
161
+ "XDG_RUNTIME_DIR": self.pulse_dir,
162
+ },
163
+ )
164
+ page = await context.new_page()
165
+ await page.goto(url, wait_until="commit")
166
+
167
+ logger.info(
168
+ f"Recording session [{self.display}:{self.sink_name}] for {duration}ms"
169
+ )
170
+
171
+ self.start_ffmpeg(duration, output_path)
172
+
173
+ await page.wait_for_timeout(duration) # Wait for the duration
174
+ await context.close()
175
+
176
+
177
+ async def record(
178
+ dirname: str, filename: str, duration: int, height: int, url: str, width: int
179
+ ) -> None:
180
+ async with RecordingSession(dirname, width, height) as session:
181
+ await session.capture(url, duration, filename)
182
+
183
+
184
+ async def screenshot(filename: str, height: int, url: str, width: int) -> None:
185
+ async with async_playwright() as p:
186
+ browser = await p.chromium.launch()
187
+ page = await browser.new_page()
188
+ await page.set_viewport_size({"width": width, "height": height})
189
+ await page.goto(url, wait_until="networkidle")
190
+ await page.screenshot(path=filename)
191
+ await browser.close()
192
+
193
+
194
+ def to_ms(timedelta: datetime.timedelta) -> int:
195
+ return round(1000 * timedelta.total_seconds())
@@ -23,21 +23,13 @@ def convert(
23
23
  if duration:
24
24
  args.extend(["-to", milliseconds_to_duration(delay + duration)])
25
25
  if delay or duration:
26
- args.extend(["-c:v", "libx264", "-preset", "slow", "-crf", "18"])
26
+ args.extend(["-c:v", "libx264", "-preset", "slow", "-crf", "8"])
27
27
  if opts:
28
28
  args.extend(opts)
29
29
  args.append(outfile)
30
30
  ffmpg(args)
31
31
 
32
32
 
33
- def merge(infiles: [str], outfile: str) -> None:
34
- args = ["-y"]
35
- for file in infiles:
36
- args.extend(["-i", file])
37
- args.extend(["-c", "copy", outfile])
38
- ffmpg(args)
39
-
40
-
41
33
  def ffmpg(args):
42
34
  try:
43
35
  sh.ffmpeg(*args)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rbx
3
- Version: 3.18.3.dev156
3
+ Version: 3.18.3.dev157
4
4
  Summary: A collection of common tools for Scoota services.
5
5
  Author-email: The Scoota Engineering Team <engineering@scoota.com>
6
6
  License:
@@ -228,6 +228,19 @@ Requires-Dist: Click<9
228
228
  Requires-Dist: colorama
229
229
  Requires-Dist: lxml<6,>=5.3.0
230
230
  Requires-Dist: requests>=2.31.0
231
+ Provides-Extra: all
232
+ Requires-Dist: boto3; extra == "all"
233
+ Requires-Dist: fabric~=3.2.0; extra == "all"
234
+ Requires-Dist: google-cloud-error-reporting<2,>=1.9.2; extra == "all"
235
+ Requires-Dist: google-cloud-firestore<3,>=2.3; extra == "all"
236
+ Requires-Dist: google-cloud-pubsub<3,>=2.9; extra == "all"
237
+ Requires-Dist: google-cloud-storage<3,>=2.1; extra == "all"
238
+ Requires-Dist: google-cloud-tasks<3,>=2.7; extra == "all"
239
+ Requires-Dist: Jinja2==3.1.2; extra == "all"
240
+ Requires-Dist: playwright~=1.51.0; extra == "all"
241
+ Requires-Dist: sh~=2.0.6; extra == "all"
242
+ Requires-Dist: starlette==0.38.2; extra == "all"
243
+ Requires-Dist: twine; extra == "all"
231
244
  Provides-Extra: auth
232
245
  Requires-Dist: google-cloud-firestore<3,>=2.3; extra == "auth"
233
246
  Provides-Extra: buildtools
@@ -247,24 +260,10 @@ Requires-Dist: google-cloud-tasks<3,>=2.7; extra == "tasks"
247
260
  Provides-Extra: toolkit
248
261
  Requires-Dist: boto3; extra == "toolkit"
249
262
  Requires-Dist: google-cloud-storage<3,>=2.1; extra == "toolkit"
250
- Requires-Dist: mutagen~=1.47.0; extra == "toolkit"
251
263
  Requires-Dist: playwright~=1.51.0; extra == "toolkit"
252
264
  Requires-Dist: sh~=2.0.6; extra == "toolkit"
253
- Requires-Dist: tqdm~=4.66.1; extra == "toolkit"
254
265
  Provides-Extra: web
255
266
  Requires-Dist: google-cloud-error-reporting<2,>=1.9.2; extra == "web"
256
- Provides-Extra: test
257
- Requires-Dist: google-cloud-error-reporting<2,>=1.9.2; extra == "test"
258
- Requires-Dist: google-cloud-firestore<3,>=2.3; extra == "test"
259
- Requires-Dist: google-cloud-pubsub<3,>=2.9; extra == "test"
260
- Requires-Dist: google-cloud-storage<3,>=2.1; extra == "test"
261
- Requires-Dist: google-cloud-tasks<3,>=2.7; extra == "test"
262
- Requires-Dist: httpx; extra == "test"
263
- Requires-Dist: mutagen~=1.47.0; extra == "test"
264
- Requires-Dist: playwright~=1.51.0; extra == "test"
265
- Requires-Dist: sh~=2.0.6; extra == "test"
266
- Requires-Dist: starlette==0.38.2; extra == "test"
267
- Requires-Dist: tqdm~=4.66.1; extra == "test"
268
267
  Dynamic: license-file
269
268
 
270
269
  The library provides a collection of tools and functionality for the Scoota Platform services.
@@ -276,7 +275,7 @@ The library provides a collection of tools and functionality for the Scoota Plat
276
275
  Install this library in a virtualenv using pip.
277
276
 
278
277
  ```
279
- pip install rbx
278
+ pip install rbx[all]
280
279
  ```
281
280
 
282
281
  > See the [wiki](https://github.com/rockabox/rbx/wiki) for more details on the contents of this library.
@@ -41,7 +41,6 @@ rbx/toolkit/cli.py
41
41
  rbx/toolkit/exporter.py
42
42
  rbx/toolkit/media.py
43
43
  rbx/toolkit/utils.py
44
- rbx/toolkit/web.py
45
44
  rbx/utils/__init__.py
46
45
  rbx/utils/mdm.py
47
46
  rbx/utils/vast.py
@@ -5,6 +5,20 @@ colorama
5
5
  lxml<6,>=5.3.0
6
6
  requests>=2.31.0
7
7
 
8
+ [all]
9
+ boto3
10
+ fabric~=3.2.0
11
+ google-cloud-error-reporting<2,>=1.9.2
12
+ google-cloud-firestore<3,>=2.3
13
+ google-cloud-pubsub<3,>=2.9
14
+ google-cloud-storage<3,>=2.1
15
+ google-cloud-tasks<3,>=2.7
16
+ Jinja2==3.1.2
17
+ playwright~=1.51.0
18
+ sh~=2.0.6
19
+ starlette==0.38.2
20
+ twine
21
+
8
22
  [auth]
9
23
  google-cloud-firestore<3,>=2.3
10
24
 
@@ -27,26 +41,11 @@ google-cloud-storage<3,>=2.1
27
41
  [tasks]
28
42
  google-cloud-tasks<3,>=2.7
29
43
 
30
- [test]
31
- google-cloud-error-reporting<2,>=1.9.2
32
- google-cloud-firestore<3,>=2.3
33
- google-cloud-pubsub<3,>=2.9
34
- google-cloud-storage<3,>=2.1
35
- google-cloud-tasks<3,>=2.7
36
- httpx
37
- mutagen~=1.47.0
38
- playwright~=1.51.0
39
- sh~=2.0.6
40
- starlette==0.38.2
41
- tqdm~=4.66.1
42
-
43
44
  [toolkit]
44
45
  boto3
45
46
  google-cloud-storage<3,>=2.1
46
- mutagen~=1.47.0
47
47
  playwright~=1.51.0
48
48
  sh~=2.0.6
49
- tqdm~=4.66.1
50
49
 
51
50
  [web]
52
51
  google-cloud-error-reporting<2,>=1.9.2
@@ -1 +0,0 @@
1
- __version__ = "3.18.3.dev156"
@@ -1,141 +0,0 @@
1
- import datetime
2
- import logging
3
- from typing import NamedTuple, Optional
4
-
5
- from playwright.async_api import async_playwright
6
-
7
- logger = logging.getLogger(__name__)
8
-
9
-
10
- class Continue(Exception):
11
- pass
12
-
13
-
14
- class Recording(NamedTuple):
15
- location: str
16
- delay: Optional[int] = 0
17
- source: Optional[str] = None
18
-
19
-
20
- async def record(
21
- dirname: str, duration: int, height: int, url: str, width: int
22
- ) -> Recording:
23
- async with async_playwright() as p:
24
- browser = await p.chromium.launch(
25
- args=["--autoplay-policy=no-user-gesture-required"]
26
- )
27
- context = await browser.new_context(
28
- record_video_dir=dirname,
29
- record_video_size={"width": width, "height": height},
30
- viewport={"width": width, "height": height},
31
- )
32
- page = await context.new_page()
33
- await page.set_viewport_size({"width": width, "height": height})
34
- await page.goto(url, wait_until="domcontentloaded")
35
- start = datetime.datetime.now(datetime.UTC)
36
-
37
- try:
38
- # Wait for the appropriate events to be seen in the console log.
39
- # These events are expected to occur within 5 seconds, so we never wait any longer
40
- # than that.
41
- has_video = False
42
- ready = None
43
- while True:
44
- async with page.expect_console_message(timeout=0) as msg_info:
45
- message = await msg_info.value
46
- if message.type == "log":
47
- for arg in message.args:
48
- line = await arg.json_value()
49
- if "/session" in line:
50
- ready = datetime.datetime.now(datetime.UTC)
51
- elif "action=p0" in line or "action=p10" in line:
52
- has_video = True
53
-
54
- if (ready and has_video) or (
55
- datetime.datetime.now(datetime.UTC) - start
56
- ).total_seconds() >= 5:
57
- ready = ready or start
58
- raise Continue
59
-
60
- except Continue:
61
- checkpoint = datetime.datetime.now(datetime.UTC)
62
- text = "with" if has_video else "without"
63
- logger.debug(f"Recording {text} video @ {to_ms(checkpoint - start)}ms")
64
- src = None
65
-
66
- if has_video:
67
- video = page.locator("video")
68
- await video.wait_for(timeout=2000)
69
- for source in await video.locator("source").all():
70
- if await source.get_attribute("type") == "video/mp4":
71
- value = await source.get_attribute("src")
72
- src, _, _ = value.partition("?")
73
-
74
- # Record until the end of the video, at which point the `video` element will be set
75
- # as hidden.
76
- await page.locator("video").first.wait_for(
77
- state="hidden", timeout=60000
78
- )
79
- checkpoint = datetime.datetime.now(datetime.UTC)
80
- finished = datetime.datetime.now(datetime.UTC)
81
-
82
- # Continue recording until the required duration.
83
- checkpoint = datetime.datetime.now(datetime.UTC)
84
- elapsed = int(1000 * (checkpoint - ready).total_seconds())
85
- remaining = duration - elapsed
86
- if remaining > 0:
87
- logger.debug(f"{remaining}ms left to record")
88
- expression = (
89
- "window.recording = 1; setTimeout(() => { window.recording = 0 }, "
90
- + str(remaining)
91
- + ");"
92
- )
93
- await page.evaluate(expression)
94
- await page.wait_for_function("() => window.recording == 0")
95
- else:
96
- logger.debug("Nothing left to record")
97
-
98
- finished = datetime.datetime.now(datetime.UTC)
99
-
100
- else:
101
- elapsed = int(1000 * (checkpoint - start).total_seconds())
102
- remaining = duration - elapsed
103
- if remaining > 0:
104
- logger.debug(f"{remaining}ms left to record")
105
- expression = (
106
- "window.recording = 1; setTimeout(() => { window.recording = 0 }, "
107
- + str(remaining)
108
- + ");"
109
- )
110
- await page.evaluate(expression)
111
- await page.wait_for_function("() => window.recording == 0")
112
- else:
113
- logger.debug("Nothing left to record")
114
-
115
- finished = datetime.datetime.now(datetime.UTC)
116
-
117
- logger.debug(f"Finished @ {to_ms(finished - ready)}ms")
118
- await context.close()
119
- location = await page.video.path()
120
- await browser.close()
121
-
122
- # The delay is the lag between the start of the recording session and the time the video
123
- # started playing.
124
- delay = to_ms(ready - start)
125
- logger.debug(f"Recorded with delay of {delay}ms")
126
-
127
- return Recording(delay=delay, location=location, source=src)
128
-
129
-
130
- async def screenshot(filename: str, height: int, url: str, width: int) -> None:
131
- async with async_playwright() as p:
132
- browser = await p.chromium.launch()
133
- page = await browser.new_page()
134
- await page.set_viewport_size({"width": width, "height": height})
135
- await page.goto(url, wait_until="networkidle")
136
- await page.screenshot(path=filename)
137
- await browser.close()
138
-
139
-
140
- def to_ms(timedelta: datetime.timedelta) -> int:
141
- return round(1000 * timedelta.total_seconds())
@@ -1,8 +0,0 @@
1
- import requests
2
-
3
-
4
- def download(url: str, filename: str):
5
- response = requests.get(url)
6
- if response.status_code == 200:
7
- with open(filename, "wb") as file:
8
- file.write(response.content)
File without changes
File without changes
File without changes
File without changes
File without changes