remotivelabs-cli 0.0.13__py3-none-any.whl → 0.0.15__py3-none-any.whl
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.
- cli/broker/lib/broker.py +11 -5
- cli/cloud/recordings.py +20 -6
- cli/cloud/recordings_playback.py +178 -0
- {remotivelabs_cli-0.0.13.dist-info → remotivelabs_cli-0.0.15.dist-info}/METADATA +1 -1
- {remotivelabs_cli-0.0.13.dist-info → remotivelabs_cli-0.0.15.dist-info}/RECORD +8 -7
- {remotivelabs_cli-0.0.13.dist-info → remotivelabs_cli-0.0.15.dist-info}/LICENSE +0 -0
- {remotivelabs_cli-0.0.13.dist-info → remotivelabs_cli-0.0.15.dist-info}/WHEEL +0 -0
- {remotivelabs_cli-0.0.13.dist-info → remotivelabs_cli-0.0.15.dist-info}/entry_points.txt +0 -0
cli/broker/lib/broker.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import binascii
|
4
|
-
import datetime
|
5
4
|
import ntpath
|
6
5
|
import os
|
7
6
|
import posixpath
|
@@ -183,7 +182,9 @@ class Broker:
|
|
183
182
|
self.__check_playbackmode_result(status)
|
184
183
|
return status
|
185
184
|
|
186
|
-
def listen_on_playback(self, callback: Callable[[str], None]):
|
185
|
+
def listen_on_playback(self, repeat: bool, recording_and_namespace: List, callback: Callable[[int, int, str], None]):
|
186
|
+
# include recording_and_namespace if we want to loop the recording
|
187
|
+
# This can probably be improved
|
187
188
|
def get_mode(mode: int):
|
188
189
|
if mode == 0:
|
189
190
|
return "playing"
|
@@ -195,14 +196,19 @@ class Broker:
|
|
195
196
|
sub = self.traffic_stub.PlayTrafficStatus(br.common_pb2.Empty())
|
196
197
|
for playback_state in sub:
|
197
198
|
p = typing.cast(br.traffic_api_pb2.PlaybackInfos, playback_state)
|
198
|
-
|
199
|
+
offset_length = int(p.playbackInfo[0].playbackMode.offsetTime / 1000000)
|
199
200
|
start_time = p.playbackInfo[0].playbackMode.startTime
|
200
201
|
end_time = p.playbackInfo[0].playbackMode.endTime
|
201
202
|
mode = p.playbackInfo[0].playbackMode.mode
|
202
203
|
|
203
|
-
|
204
|
+
total_length = int((end_time - start_time) / 1000000)
|
204
205
|
|
205
|
-
|
206
|
+
if mode == 2 and repeat:
|
207
|
+
# If we get a stop and is fairly (this is mostly not 100%) close to the end
|
208
|
+
# we repeat the recording when files are included
|
209
|
+
if abs(total_length - offset_length) < 5:
|
210
|
+
self.play(recording_and_namespace)
|
211
|
+
callback(offset_length, total_length, get_mode(mode))
|
206
212
|
|
207
213
|
def pause(self, namespace: str, path: str, silent: bool = False):
|
208
214
|
playback_list = [
|
cli/cloud/recordings.py
CHANGED
@@ -17,8 +17,10 @@ from cli.errors import ErrorPrinter
|
|
17
17
|
|
18
18
|
from ..broker.lib.broker import Broker
|
19
19
|
from . import rest_helper as rest
|
20
|
+
from .recordings_playback import app as playback_app
|
20
21
|
|
21
22
|
app = typer.Typer()
|
23
|
+
app.add_typer(playback_app, name="playback")
|
22
24
|
|
23
25
|
|
24
26
|
def uid(p):
|
@@ -365,52 +367,64 @@ def copy(
|
|
365
367
|
)
|
366
368
|
|
367
369
|
|
368
|
-
@app.command()
|
370
|
+
@app.command(deprecated=True)
|
369
371
|
def play(
|
370
372
|
recording_session: str = typer.Argument(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
|
371
373
|
broker: str = typer.Option(None, help="Broker to use"),
|
372
374
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
373
375
|
):
|
376
|
+
"""
|
377
|
+
Plays a recording (Deprecated - Use recordings playback play)"
|
378
|
+
"""
|
374
379
|
_do_change_playback_mode("play", recording_session, broker, project)
|
375
380
|
|
376
381
|
|
377
|
-
@app.command()
|
382
|
+
@app.command(deprecated=True)
|
378
383
|
def pause(
|
379
384
|
recording_session: str = typer.Argument(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
|
380
385
|
broker: str = typer.Option(None, help="Broker to use"),
|
381
386
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
382
387
|
):
|
388
|
+
"""
|
389
|
+
Pause recording (Deprecated - Use recordings playback pause")
|
390
|
+
"""
|
383
391
|
_do_change_playback_mode("pause", recording_session, broker, project)
|
384
392
|
|
385
393
|
|
386
|
-
@app.command(name="playback-status")
|
394
|
+
@app.command(name="playback-status", deprecated=True)
|
387
395
|
def playback_status(
|
388
396
|
recording_session: str = typer.Argument(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
|
389
397
|
broker: str = typer.Option(None, help="Broker to use"),
|
390
398
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
391
399
|
):
|
392
400
|
"""
|
393
|
-
|
401
|
+
Playback progress (Deprecated - Use recordings playback progress)
|
394
402
|
"""
|
395
403
|
_do_change_playback_mode("status", recording_session, broker, project)
|
396
404
|
|
397
405
|
|
398
|
-
@app.command()
|
406
|
+
@app.command(deprecated=True)
|
399
407
|
def seek(
|
400
408
|
recording_session: str = typer.Argument(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
|
401
409
|
seconds: int = typer.Option(..., min=0, help="Target offset in seconds"),
|
402
410
|
broker: str = typer.Option(None, help="Broker to use"),
|
403
411
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
404
412
|
):
|
413
|
+
"""
|
414
|
+
Seek into recording (Deprecated - Use recordings playback seek)
|
415
|
+
"""
|
405
416
|
_do_change_playback_mode("seek", recording_session, broker, project, seconds)
|
406
417
|
|
407
418
|
|
408
|
-
@app.command()
|
419
|
+
@app.command(deprecated=True)
|
409
420
|
def stop(
|
410
421
|
recording_session: str = typer.Argument(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
|
411
422
|
broker: str = typer.Option(None, help="Broker to use"),
|
412
423
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
413
424
|
):
|
425
|
+
"""
|
426
|
+
Stop recording (Deprecated - Use recordings playback stop)
|
427
|
+
"""
|
414
428
|
_do_change_playback_mode("stop", recording_session, broker, project)
|
415
429
|
|
416
430
|
|
@@ -0,0 +1,178 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import datetime
|
4
|
+
import json
|
5
|
+
import tempfile
|
6
|
+
from typing import List
|
7
|
+
|
8
|
+
import grpc
|
9
|
+
import rich
|
10
|
+
import typer
|
11
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
12
|
+
|
13
|
+
from cli.errors import ErrorPrinter
|
14
|
+
|
15
|
+
from ..broker.lib.broker import Broker
|
16
|
+
from . import rest_helper as rest
|
17
|
+
|
18
|
+
app = typer.Typer(
|
19
|
+
help="""
|
20
|
+
Support for playback of a recording on a cloud broker, make sure to always mount a recording first
|
21
|
+
"""
|
22
|
+
)
|
23
|
+
|
24
|
+
|
25
|
+
@app.command()
|
26
|
+
def play(
|
27
|
+
recording_session: str = typer.Argument(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
|
28
|
+
broker: str = typer.Option(None, help="Broker to use"),
|
29
|
+
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
30
|
+
show_progress: bool = typer.Option(False, help="Show progress after started playing"),
|
31
|
+
repeat: bool = typer.Option(False, help="Repeat recording - must keep command running in terminal"),
|
32
|
+
):
|
33
|
+
"""
|
34
|
+
Start playing a recording.
|
35
|
+
There is no problem invoking play multiple times since if it is already playing the command will be ignored.
|
36
|
+
Use --repeat to have the recording replayed when it reaches the end.
|
37
|
+
"""
|
38
|
+
|
39
|
+
_do_change_playback_mode("play", recording_session, broker, project, progress_on_play=show_progress, repeat=repeat)
|
40
|
+
|
41
|
+
|
42
|
+
@app.command()
|
43
|
+
def pause(
|
44
|
+
recording_session: str = typer.Argument(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
|
45
|
+
broker: str = typer.Option(None, help="Broker to use"),
|
46
|
+
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
47
|
+
):
|
48
|
+
"""
|
49
|
+
Pause a recording
|
50
|
+
"""
|
51
|
+
_do_change_playback_mode("pause", recording_session, broker, project)
|
52
|
+
|
53
|
+
|
54
|
+
@app.command()
|
55
|
+
def progress(
|
56
|
+
recording_session: str = typer.Argument(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
|
57
|
+
broker: str = typer.Option(None, help="Broker to use"),
|
58
|
+
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
59
|
+
):
|
60
|
+
"""
|
61
|
+
Shows progress of the recording playing.
|
62
|
+
Use --repeat to have the recording replayed when it reaches the end.
|
63
|
+
"""
|
64
|
+
_do_change_playback_mode("status", recording_session, broker, project)
|
65
|
+
|
66
|
+
|
67
|
+
@app.command()
|
68
|
+
def seek(
|
69
|
+
recording_session: str = typer.Argument(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
|
70
|
+
seconds: int = typer.Option(..., min=0, help="Target offset in seconds"),
|
71
|
+
broker: str = typer.Option(None, help="Broker to use"),
|
72
|
+
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
73
|
+
):
|
74
|
+
"""
|
75
|
+
Seek seconds into a recording
|
76
|
+
"""
|
77
|
+
_do_change_playback_mode("seek", recording_session, broker, project, seconds)
|
78
|
+
|
79
|
+
|
80
|
+
@app.command()
|
81
|
+
def stop(
|
82
|
+
recording_session: str = typer.Argument(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
|
83
|
+
broker: str = typer.Option(None, help="Broker to use"),
|
84
|
+
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
85
|
+
):
|
86
|
+
"""
|
87
|
+
Stop playing
|
88
|
+
"""
|
89
|
+
_do_change_playback_mode("stop", recording_session, broker, project)
|
90
|
+
|
91
|
+
|
92
|
+
def _do_change_playback_mode(
|
93
|
+
mode: str,
|
94
|
+
recording_session: str,
|
95
|
+
broker: str,
|
96
|
+
project: str,
|
97
|
+
seconds: int | None = None,
|
98
|
+
progress_on_play: bool = False,
|
99
|
+
repeat: bool = False,
|
100
|
+
): # noqa: C901
|
101
|
+
response = rest.handle_get(f"/api/project/{project}/files/recording/{recording_session}", return_response=True)
|
102
|
+
r = json.loads(response.text)
|
103
|
+
recordings: list = r["recordings"]
|
104
|
+
files = list(map(lambda rec: {"recording": rec["fileName"], "namespace": rec["metadata"]["namespace"]}, recordings))
|
105
|
+
|
106
|
+
if broker is not None:
|
107
|
+
response = rest.handle_get(f"/api/project/{project}/brokers/{broker}", return_response=True, allow_status_codes=[404])
|
108
|
+
else:
|
109
|
+
response = rest.handle_get(f"/api/project/{project}/brokers/personal", return_response=True, allow_status_codes=[404])
|
110
|
+
if response.status_code == 404:
|
111
|
+
broker_arg = ""
|
112
|
+
if broker is not None:
|
113
|
+
broker_arg = f" --broker {broker} --ensure-broker-started"
|
114
|
+
ErrorPrinter.print_generic_error("You need to mount the recording before you play")
|
115
|
+
ErrorPrinter.print_hint(f"remotive cloud recordings mount {recording_session}{broker_arg} --project {project}")
|
116
|
+
exit(1)
|
117
|
+
|
118
|
+
broker_info = json.loads(response.text)
|
119
|
+
broker = Broker(broker_info["url"], None)
|
120
|
+
|
121
|
+
_verify_recording_on_broker(broker, recording_session, mode, project)
|
122
|
+
|
123
|
+
if mode == "pause":
|
124
|
+
broker.pause_play(files, True)
|
125
|
+
elif mode == "play":
|
126
|
+
broker.play(files, True)
|
127
|
+
if progress_on_play or repeat:
|
128
|
+
_track_progress(broker, repeat, files)
|
129
|
+
elif mode == "seek":
|
130
|
+
broker.seek(files, int(seconds * 1000000), True)
|
131
|
+
elif mode == "stop":
|
132
|
+
broker.seek(files, 0, True)
|
133
|
+
elif mode == "status":
|
134
|
+
_track_progress(broker, repeat, files)
|
135
|
+
else:
|
136
|
+
raise Exception(f"Illegal command {mode}")
|
137
|
+
|
138
|
+
|
139
|
+
def _track_progress(broker: Broker, repeat: bool, files: List):
|
140
|
+
p = Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}"), transient=True)
|
141
|
+
t = p.add_task("label", total=1)
|
142
|
+
if repeat:
|
143
|
+
rich.print(":point_right: Keep this command running in terminal to keep the recording play with repeat")
|
144
|
+
with p:
|
145
|
+
|
146
|
+
def print_progress(offset: int, total: int, current_mode: str):
|
147
|
+
p.update(
|
148
|
+
t,
|
149
|
+
description=f"{(datetime.timedelta(seconds=offset))} " f"/ {(datetime.timedelta(seconds=total))} ({current_mode})",
|
150
|
+
)
|
151
|
+
|
152
|
+
broker.listen_on_playback(repeat, files, print_progress)
|
153
|
+
|
154
|
+
|
155
|
+
def _verify_recording_on_broker(broker: Broker, recording_session: str, mode: str, project: str):
|
156
|
+
try:
|
157
|
+
# Here we try to verify that we are operating on a recording that is mounted on the
|
158
|
+
# broker so we can verify this before we try playback and can also present some good
|
159
|
+
# error messages
|
160
|
+
tmp = tempfile.NamedTemporaryFile()
|
161
|
+
broker.download(".cloud.context", tmp.name, True)
|
162
|
+
with open(tmp.name, "r") as f:
|
163
|
+
json_context = json.loads(f.read())
|
164
|
+
if json_context["recordingSessionId"] != recording_session:
|
165
|
+
ErrorPrinter.print_generic_error(
|
166
|
+
f"The recording id mounted is '{json_context['recordingSessionId']}' "
|
167
|
+
f"which not the same as you are trying to {mode}, "
|
168
|
+
"use cmd below to mount this recording"
|
169
|
+
)
|
170
|
+
ErrorPrinter.print_hint(f"remotive cloud recordings mount {recording_session} --project {project}")
|
171
|
+
exit(1)
|
172
|
+
except grpc.RpcError as rpc_error:
|
173
|
+
if rpc_error.code() == grpc.StatusCode.NOT_FOUND:
|
174
|
+
ErrorPrinter.print_generic_error(f"You must use mount to prepare a recording before you can use {mode}")
|
175
|
+
ErrorPrinter.print_hint(f"remotive cloud recordings mount {recording_session} --project {project}")
|
176
|
+
else:
|
177
|
+
ErrorPrinter.print_grpc_error(rpc_error)
|
178
|
+
exit(1)
|
@@ -4,7 +4,7 @@ cli/broker/brokers.py,sha256=sSX--mm5ln5RUFR60VFHQR6NWu4Qz7Jqi2Ws-4TJsDI,3052
|
|
4
4
|
cli/broker/export.py,sha256=GxwE9ufWRwfxVh3eTLkwuOqwUOtsvS9Wb7b-BooULC8,3734
|
5
5
|
cli/broker/files.py,sha256=_8elBjbmJ5MEEeFGJ7QYkXzyuLDCDpO6UvEVXHbRy7U,4133
|
6
6
|
cli/broker/lib/__about__.py,sha256=xnZ5V6ZcHW9dhWLWdMzVjYJbEnMKpeXm0_S_mbNzypE,141
|
7
|
-
cli/broker/lib/broker.py,sha256=
|
7
|
+
cli/broker/lib/broker.py,sha256=b4MO38_SamR2eE-ClDZMWSVpVRfQ6HQ27ALVpmmVGXc,21071
|
8
8
|
cli/broker/playback.py,sha256=oOfC8Jn4Ib-nc9T6ob_uNXZSeCWfft7MrMQPafH4U2I,4846
|
9
9
|
cli/broker/record.py,sha256=gEvo3myHbIl6UyXzhJE741NiwRrFf7doBg6HXzzp5z0,1382
|
10
10
|
cli/broker/scripting.py,sha256=sLDtuktWsVk0fJ3RW4kYyh-_YAVJP3VM0xFIQR499Oo,3392
|
@@ -16,7 +16,8 @@ cli/cloud/brokers.py,sha256=wa3uMg91IZdrP0tMpTdO9cBIkZHtMHxQ-zEXwFiye_I,4127
|
|
16
16
|
cli/cloud/cloud_cli.py,sha256=nRRXFF_IgUMayerxS-h7oqsNe6tt34Q5VeThq8gatEg,1443
|
17
17
|
cli/cloud/configs.py,sha256=2p1mCHf5BwYNtwbY0Cbed5t6-79WHGKWU4Fv6LuJ21o,4069
|
18
18
|
cli/cloud/projects.py,sha256=-uqltAOficwprOKaPd2R0Itm4sqTz3VJNs9Sc8jtO5k,1369
|
19
|
-
cli/cloud/recordings.py,sha256=
|
19
|
+
cli/cloud/recordings.py,sha256=0xQw8iv-NjUUqxQjq5O4WqNYsew2lNlQ9Z0Qevl2f9E,20844
|
20
|
+
cli/cloud/recordings_playback.py,sha256=ygJRBbJ_BYmrEZ7k0j2b11N3xrrgmOdL3jZckJ-f-uU,7120
|
20
21
|
cli/cloud/rest_helper.py,sha256=g7lmGosAS0IDlo9Aso0bH0tdlLTCz0IYx3R71DXKtkc,6491
|
21
22
|
cli/cloud/sample_recordings.py,sha256=g1X6JTxvzWInSP9R1BJsDmL4WqvpEKqjdJR_xT4bo1U,639
|
22
23
|
cli/cloud/service_account_tokens.py,sha256=7vjoMd6Xq7orWCUP7TVUVa86JA0OiX8O10NZcHUE6rM,2294
|
@@ -32,8 +33,8 @@ cli/tools/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
32
33
|
cli/tools/can/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
33
34
|
cli/tools/can/can.py,sha256=kSd1c-nxxXyeKkm19oDILiDBZsKOcpjsUT0T3xox5Qs,2172
|
34
35
|
cli/tools/tools.py,sha256=LwQdWMcJ19pCyKUsVfSB2B3R6ui61NxxFWP0Nrnd5Jk,198
|
35
|
-
remotivelabs_cli-0.0.
|
36
|
-
remotivelabs_cli-0.0.
|
37
|
-
remotivelabs_cli-0.0.
|
38
|
-
remotivelabs_cli-0.0.
|
39
|
-
remotivelabs_cli-0.0.
|
36
|
+
remotivelabs_cli-0.0.15.dist-info/LICENSE,sha256=qDPP_yfuv1fF-u7EfexN-cN3M8aFgGVndGhGLovLKz0,608
|
37
|
+
remotivelabs_cli-0.0.15.dist-info/METADATA,sha256=6WwZlFqtnnC892J5T6m_Zgs1XGz_wnyNzzihRRFDY_Q,1224
|
38
|
+
remotivelabs_cli-0.0.15.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
39
|
+
remotivelabs_cli-0.0.15.dist-info/entry_points.txt,sha256=lvDhPgagLqW_KTnLPCwKSqfYlEp-1uYVosRiPjsVj10,45
|
40
|
+
remotivelabs_cli-0.0.15.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|