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 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
- offset_time = int(p.playbackInfo[0].playbackMode.offsetTime / 1000000)
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
- length = int((end_time - start_time) / 1000000)
204
+ total_length = int((end_time - start_time) / 1000000)
204
205
 
205
- callback(f"{(datetime.timedelta(seconds=offset_time))} / {(datetime.timedelta(seconds=length))} ({get_mode(mode)})")
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
- Display status and progress of the recording beeing played
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: remotivelabs-cli
3
- Version: 0.0.13
3
+ Version: 0.0.15
4
4
  Summary: CLI for operating RemotiveCloud and RemotiveBroker
5
5
  Author: Johan Rask
6
6
  Author-email: johan.rask@remotivelabs.com
@@ -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=fmGsdxmdC3llrIvr0o3E2cpkGHU6mxrbZ7_ac2ZqVvU,20664
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=F42VXWuoIVKmjkSxr4fq0M9w1GwszNE1kUbkVzxEaf4,20334
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.13.dist-info/LICENSE,sha256=qDPP_yfuv1fF-u7EfexN-cN3M8aFgGVndGhGLovLKz0,608
36
- remotivelabs_cli-0.0.13.dist-info/METADATA,sha256=q4vUcxbJSS14XERa8zL9nWJpHA6bVeFSWnja5b75tEo,1224
37
- remotivelabs_cli-0.0.13.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
38
- remotivelabs_cli-0.0.13.dist-info/entry_points.txt,sha256=lvDhPgagLqW_KTnLPCwKSqfYlEp-1uYVosRiPjsVj10,45
39
- remotivelabs_cli-0.0.13.dist-info/RECORD,,
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,,