motion-intelligence 0.2.0__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.
- motion/__init__.py +10 -0
- motion/batch.py +153 -0
- motion/categories.py +46 -0
- motion/cli.py +503 -0
- motion/config.py +148 -0
- motion/dockerfile.py +110 -0
- motion/predictor.py +62 -0
- motion/profile.py +345 -0
- motion/server.py +138 -0
- motion/weight_upload.py +324 -0
- motion_intelligence-0.2.0.dist-info/METADATA +134 -0
- motion_intelligence-0.2.0.dist-info/RECORD +15 -0
- motion_intelligence-0.2.0.dist-info/WHEEL +5 -0
- motion_intelligence-0.2.0.dist-info/entry_points.txt +2 -0
- motion_intelligence-0.2.0.dist-info/top_level.txt +1 -0
motion/weight_upload.py
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"""Upload model weights to S3 via Motion backend presigned URLs.
|
|
2
|
+
|
|
3
|
+
Developers only need MOTION_API_KEY — never AWS credentials on their machine.
|
|
4
|
+
Small files use a single presigned PUT; large files use S3 multipart upload
|
|
5
|
+
(init → per-part presigned PUT → complete) so multi-GB checkpoints can resume
|
|
6
|
+
per-part instead of restarting the whole file.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import mimetypes
|
|
11
|
+
import os
|
|
12
|
+
import time
|
|
13
|
+
from typing import Callable, Optional
|
|
14
|
+
|
|
15
|
+
# Use multipart for files >= 64 MiB (S3 minimum part size is 5 MiB).
|
|
16
|
+
MULTIPART_THRESHOLD_BYTES = 64 * 1024 * 1024
|
|
17
|
+
DEFAULT_PART_SIZE_BYTES = 64 * 1024 * 1024
|
|
18
|
+
MAX_PART_RETRIES = 5
|
|
19
|
+
API_TIMEOUT = (30, 120)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def upload_weight_via_backend(
|
|
23
|
+
local_path: str,
|
|
24
|
+
model_name: str,
|
|
25
|
+
api_key: str,
|
|
26
|
+
*,
|
|
27
|
+
api_url: str,
|
|
28
|
+
log_ok: Callable[[str], str],
|
|
29
|
+
log_warn: Callable[[str], str],
|
|
30
|
+
log_err: Callable[[str], str],
|
|
31
|
+
) -> Optional[str]:
|
|
32
|
+
"""Upload a local weight file; return final s3:// URL or None on failure."""
|
|
33
|
+
size_bytes = os.path.getsize(local_path)
|
|
34
|
+
if size_bytes >= MULTIPART_THRESHOLD_BYTES:
|
|
35
|
+
return _upload_multipart(
|
|
36
|
+
local_path,
|
|
37
|
+
model_name,
|
|
38
|
+
api_key,
|
|
39
|
+
api_url=api_url,
|
|
40
|
+
log_ok=log_ok,
|
|
41
|
+
log_warn=log_warn,
|
|
42
|
+
log_err=log_err,
|
|
43
|
+
)
|
|
44
|
+
return _upload_single_put(
|
|
45
|
+
local_path,
|
|
46
|
+
model_name,
|
|
47
|
+
api_key,
|
|
48
|
+
api_url=api_url,
|
|
49
|
+
log_ok=log_ok,
|
|
50
|
+
log_warn=log_warn,
|
|
51
|
+
log_err=log_err,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _api_post(session, api_url: str, path: str, api_key: str, payload: dict) -> dict:
|
|
56
|
+
r = session.post(
|
|
57
|
+
f"{api_url}{path}",
|
|
58
|
+
json=payload,
|
|
59
|
+
headers={"X-API-Key": api_key},
|
|
60
|
+
timeout=API_TIMEOUT,
|
|
61
|
+
)
|
|
62
|
+
r.raise_for_status()
|
|
63
|
+
return r.json()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _api_post_with_retries(
|
|
67
|
+
session,
|
|
68
|
+
api_url: str,
|
|
69
|
+
path: str,
|
|
70
|
+
api_key: str,
|
|
71
|
+
payload: dict,
|
|
72
|
+
*,
|
|
73
|
+
log_warn: Callable[[str], str],
|
|
74
|
+
max_attempts: int = 5,
|
|
75
|
+
) -> Optional[dict]:
|
|
76
|
+
for attempt in range(1, max_attempts + 1):
|
|
77
|
+
try:
|
|
78
|
+
return _api_post(session, api_url, path, api_key, payload)
|
|
79
|
+
except Exception as e:
|
|
80
|
+
if attempt < max_attempts:
|
|
81
|
+
wait = min(30, 2 ** attempt)
|
|
82
|
+
print(log_warn(f" ⚠ API {path} failed (attempt {attempt}): {e}"))
|
|
83
|
+
print(f" retrying in {wait}s …")
|
|
84
|
+
time.sleep(wait)
|
|
85
|
+
else:
|
|
86
|
+
print(log_err(f" ✗ API {path} failed after {max_attempts} attempts: {e}"))
|
|
87
|
+
return None
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _upload_single_put(
|
|
92
|
+
local_path: str,
|
|
93
|
+
model_name: str,
|
|
94
|
+
api_key: str,
|
|
95
|
+
*,
|
|
96
|
+
api_url: str,
|
|
97
|
+
log_ok: Callable[[str], str],
|
|
98
|
+
log_warn: Callable[[str], str],
|
|
99
|
+
log_err: Callable[[str], str],
|
|
100
|
+
) -> Optional[str]:
|
|
101
|
+
import requests
|
|
102
|
+
|
|
103
|
+
filename = os.path.basename(local_path)
|
|
104
|
+
size_bytes = os.path.getsize(local_path)
|
|
105
|
+
size_mb = size_bytes / 1e6
|
|
106
|
+
content_type = mimetypes.guess_type(local_path)[0] or "application/octet-stream"
|
|
107
|
+
read_timeout = max(3600, int(size_bytes / (512 * 1024)) + 300)
|
|
108
|
+
|
|
109
|
+
session = requests.Session()
|
|
110
|
+
print(f" ↑ {filename} ({size_mb:.1f} MB) — single PUT upload …")
|
|
111
|
+
|
|
112
|
+
for attempt in range(1, MAX_PART_RETRIES + 1):
|
|
113
|
+
data = _api_post_with_retries(
|
|
114
|
+
session,
|
|
115
|
+
api_url,
|
|
116
|
+
"/api/v1/motion/weights/upload-url",
|
|
117
|
+
api_key,
|
|
118
|
+
{
|
|
119
|
+
"filename": filename,
|
|
120
|
+
"model_name": model_name,
|
|
121
|
+
"content_type": content_type,
|
|
122
|
+
},
|
|
123
|
+
log_warn=log_warn,
|
|
124
|
+
)
|
|
125
|
+
if data is None:
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
s3_url = data["s3_url"]
|
|
129
|
+
label = "uploading" if attempt == 1 else f"retry {attempt}/{MAX_PART_RETRIES}"
|
|
130
|
+
print(f" ↑ {label} → {s3_url}")
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
with open(local_path, "rb") as f:
|
|
134
|
+
put = session.put(
|
|
135
|
+
data["upload_url"],
|
|
136
|
+
data=f,
|
|
137
|
+
headers={"Content-Type": content_type},
|
|
138
|
+
timeout=(30, read_timeout),
|
|
139
|
+
)
|
|
140
|
+
put.raise_for_status()
|
|
141
|
+
print(log_ok(f" ✓ uploaded {filename} → {s3_url}"))
|
|
142
|
+
return s3_url
|
|
143
|
+
except Exception as e:
|
|
144
|
+
if attempt < MAX_PART_RETRIES:
|
|
145
|
+
wait = min(30, 2 ** attempt)
|
|
146
|
+
print(log_warn(f" ⚠ upload failed (attempt {attempt}): {e}"))
|
|
147
|
+
print(f" retrying in {wait}s …")
|
|
148
|
+
time.sleep(wait)
|
|
149
|
+
else:
|
|
150
|
+
print(log_err(f" ✗ upload failed after {MAX_PART_RETRIES} attempts: {e}"))
|
|
151
|
+
return None
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _upload_multipart(
|
|
156
|
+
local_path: str,
|
|
157
|
+
model_name: str,
|
|
158
|
+
api_key: str,
|
|
159
|
+
*,
|
|
160
|
+
api_url: str,
|
|
161
|
+
log_ok: Callable[[str], str],
|
|
162
|
+
log_warn: Callable[[str], str],
|
|
163
|
+
log_err: Callable[[str], str],
|
|
164
|
+
) -> Optional[str]:
|
|
165
|
+
import math
|
|
166
|
+
|
|
167
|
+
import requests
|
|
168
|
+
|
|
169
|
+
filename = os.path.basename(local_path)
|
|
170
|
+
size_bytes = os.path.getsize(local_path)
|
|
171
|
+
size_mb = size_bytes / 1e6
|
|
172
|
+
content_type = mimetypes.guess_type(local_path)[0] or "application/octet-stream"
|
|
173
|
+
part_size = DEFAULT_PART_SIZE_BYTES
|
|
174
|
+
part_count = max(1, math.ceil(size_bytes / part_size))
|
|
175
|
+
|
|
176
|
+
session = requests.Session()
|
|
177
|
+
print(
|
|
178
|
+
f" ↑ {filename} ({size_mb:.1f} MB) — multipart upload "
|
|
179
|
+
f"({part_count} parts × {part_size / (1024 * 1024):.0f} MiB) …"
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
init = _api_post_with_retries(
|
|
183
|
+
session,
|
|
184
|
+
api_url,
|
|
185
|
+
"/api/v1/motion/weights/multipart/init",
|
|
186
|
+
api_key,
|
|
187
|
+
{
|
|
188
|
+
"filename": filename,
|
|
189
|
+
"model_name": model_name,
|
|
190
|
+
"content_type": content_type,
|
|
191
|
+
"file_size": size_bytes,
|
|
192
|
+
"part_size": part_size,
|
|
193
|
+
},
|
|
194
|
+
log_warn=log_warn,
|
|
195
|
+
)
|
|
196
|
+
if init is None:
|
|
197
|
+
return None
|
|
198
|
+
|
|
199
|
+
upload_id = init["upload_id"]
|
|
200
|
+
s3_key = init["s3_key"]
|
|
201
|
+
s3_url = init["s3_url"]
|
|
202
|
+
print(f" ↑ destination → {s3_url}")
|
|
203
|
+
|
|
204
|
+
completed_parts: list[dict] = []
|
|
205
|
+
upload_failed = False
|
|
206
|
+
|
|
207
|
+
try:
|
|
208
|
+
with open(local_path, "rb") as f:
|
|
209
|
+
for part_number in range(1, part_count + 1):
|
|
210
|
+
chunk = f.read(part_size)
|
|
211
|
+
if not chunk:
|
|
212
|
+
break
|
|
213
|
+
|
|
214
|
+
uploaded = False
|
|
215
|
+
for attempt in range(1, MAX_PART_RETRIES + 1):
|
|
216
|
+
part_meta = _api_post_with_retries(
|
|
217
|
+
session,
|
|
218
|
+
api_url,
|
|
219
|
+
"/api/v1/motion/weights/multipart/part-url",
|
|
220
|
+
api_key,
|
|
221
|
+
{
|
|
222
|
+
"s3_key": s3_key,
|
|
223
|
+
"upload_id": upload_id,
|
|
224
|
+
"part_number": part_number,
|
|
225
|
+
},
|
|
226
|
+
log_warn=log_warn,
|
|
227
|
+
max_attempts=3,
|
|
228
|
+
)
|
|
229
|
+
if part_meta is None:
|
|
230
|
+
upload_failed = True
|
|
231
|
+
break
|
|
232
|
+
|
|
233
|
+
try:
|
|
234
|
+
put = session.put(
|
|
235
|
+
part_meta["upload_url"],
|
|
236
|
+
data=chunk,
|
|
237
|
+
timeout=(30, 600),
|
|
238
|
+
)
|
|
239
|
+
put.raise_for_status()
|
|
240
|
+
etag = put.headers.get("ETag", "").strip('"')
|
|
241
|
+
if not etag:
|
|
242
|
+
raise RuntimeError(f"missing ETag for part {part_number}")
|
|
243
|
+
completed_parts.append(
|
|
244
|
+
{"part_number": part_number, "etag": etag}
|
|
245
|
+
)
|
|
246
|
+
uploaded = True
|
|
247
|
+
pct = 100.0 * part_number / part_count
|
|
248
|
+
print(
|
|
249
|
+
f" part {part_number}/{part_count} "
|
|
250
|
+
f"({pct:.0f}%, {len(chunk) / (1024 * 1024):.1f} MiB)"
|
|
251
|
+
)
|
|
252
|
+
break
|
|
253
|
+
except Exception as e:
|
|
254
|
+
if attempt < MAX_PART_RETRIES:
|
|
255
|
+
wait = min(15, 2 ** attempt)
|
|
256
|
+
print(
|
|
257
|
+
log_warn(
|
|
258
|
+
f" ⚠ part {part_number} failed "
|
|
259
|
+
f"(attempt {attempt}): {e}"
|
|
260
|
+
)
|
|
261
|
+
)
|
|
262
|
+
time.sleep(wait)
|
|
263
|
+
else:
|
|
264
|
+
print(
|
|
265
|
+
log_err(
|
|
266
|
+
f" ✗ part {part_number} failed after "
|
|
267
|
+
f"{MAX_PART_RETRIES} attempts: {e}"
|
|
268
|
+
)
|
|
269
|
+
)
|
|
270
|
+
upload_failed = True
|
|
271
|
+
break
|
|
272
|
+
|
|
273
|
+
if not uploaded:
|
|
274
|
+
upload_failed = True
|
|
275
|
+
break
|
|
276
|
+
|
|
277
|
+
if upload_failed:
|
|
278
|
+
_abort_multipart(session, api_url, api_key, s3_key, upload_id, log_warn)
|
|
279
|
+
return None
|
|
280
|
+
|
|
281
|
+
complete = _api_post_with_retries(
|
|
282
|
+
session,
|
|
283
|
+
api_url,
|
|
284
|
+
"/api/v1/motion/weights/multipart/complete",
|
|
285
|
+
api_key,
|
|
286
|
+
{
|
|
287
|
+
"s3_key": s3_key,
|
|
288
|
+
"upload_id": upload_id,
|
|
289
|
+
"parts": completed_parts,
|
|
290
|
+
},
|
|
291
|
+
log_warn=log_warn,
|
|
292
|
+
)
|
|
293
|
+
if complete is None:
|
|
294
|
+
_abort_multipart(session, api_url, api_key, s3_key, upload_id, log_warn)
|
|
295
|
+
return None
|
|
296
|
+
|
|
297
|
+
final_url = complete.get("s3_url", s3_url)
|
|
298
|
+
print(log_ok(f" ✓ uploaded {filename} → {final_url}"))
|
|
299
|
+
return final_url
|
|
300
|
+
|
|
301
|
+
except Exception as e:
|
|
302
|
+
print(log_err(f" ✗ multipart upload failed: {e}"))
|
|
303
|
+
_abort_multipart(session, api_url, api_key, s3_key, upload_id, log_warn)
|
|
304
|
+
return None
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _abort_multipart(
|
|
308
|
+
session,
|
|
309
|
+
api_url: str,
|
|
310
|
+
api_key: str,
|
|
311
|
+
s3_key: str,
|
|
312
|
+
upload_id: str,
|
|
313
|
+
log_warn: Callable[[str], str],
|
|
314
|
+
) -> None:
|
|
315
|
+
try:
|
|
316
|
+
_api_post(
|
|
317
|
+
session,
|
|
318
|
+
api_url,
|
|
319
|
+
"/api/v1/motion/weights/multipart/abort",
|
|
320
|
+
api_key,
|
|
321
|
+
{"s3_key": s3_key, "upload_id": upload_id},
|
|
322
|
+
)
|
|
323
|
+
except Exception as e:
|
|
324
|
+
print(log_warn(f" ⚠ could not abort incomplete multipart upload: {e}"))
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: motion-intelligence
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Author, build & deploy Python models for the Motion inference platform
|
|
5
|
+
Project-URL: Homepage, https://github.com/devathub9/Motion-packages
|
|
6
|
+
Project-URL: Repository, https://github.com/devathub9/Motion-packages
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: pyyaml>=6.0
|
|
10
|
+
Requires-Dist: requests>=2.28
|
|
11
|
+
Requires-Dist: boto3>=1.26
|
|
12
|
+
|
|
13
|
+
# motion-cli (Python)
|
|
14
|
+
|
|
15
|
+
Build and deploy Docker images for models on the Motion inference platform.
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install -e /path/to/cli-py # gives the `motion` command
|
|
19
|
+
export MOTION_API_KEY=motion_live_<your_key>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
23
|
+
|
|
24
|
+
A model is a folder with `motion.yml` + `predict.py`:
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
# predict.py
|
|
28
|
+
from motion import BasePredictor, Input, Path
|
|
29
|
+
|
|
30
|
+
class MyPredictor(BasePredictor):
|
|
31
|
+
def setup(self):
|
|
32
|
+
self.model = load_model(...)
|
|
33
|
+
|
|
34
|
+
def predict(self, video: Path = Input(description="Input clip")) -> Path:
|
|
35
|
+
out = "/tmp/out.mp4"
|
|
36
|
+
self.model.run(video, out)
|
|
37
|
+
return Path(out)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```yaml
|
|
41
|
+
# motion.yml
|
|
42
|
+
name: lingbot-map
|
|
43
|
+
predict: predict.py:MyPredictor
|
|
44
|
+
description: Streaming 3D reconstruction
|
|
45
|
+
|
|
46
|
+
# Where the model appears in GET /api/v1/motion/models
|
|
47
|
+
category: reconstruction # required for catalog listing
|
|
48
|
+
inference_model: lingbot-map # optional; defaults to `name`
|
|
49
|
+
input_type: video # video | image | text
|
|
50
|
+
output_type: mp4
|
|
51
|
+
|
|
52
|
+
# Used by `motion deploy` to measure CPU/RAM via docker stats
|
|
53
|
+
sample: demo_files/input.mp4
|
|
54
|
+
|
|
55
|
+
build:
|
|
56
|
+
gpu: true
|
|
57
|
+
cuda: "11.8"
|
|
58
|
+
python_version: "3.11"
|
|
59
|
+
system_packages: [ffmpeg]
|
|
60
|
+
python_packages: ["torch>=2.4.0"]
|
|
61
|
+
|
|
62
|
+
# Optional — overrides auto-profiling
|
|
63
|
+
compute:
|
|
64
|
+
cpu: 4
|
|
65
|
+
memory_mib: 16384
|
|
66
|
+
gpu: true
|
|
67
|
+
|
|
68
|
+
weights:
|
|
69
|
+
- path: weights/model.pt
|
|
70
|
+
url: https://example.com/model.pt
|
|
71
|
+
dest: weights/model.pt
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Commands
|
|
75
|
+
|
|
76
|
+
### `motion build`
|
|
77
|
+
|
|
78
|
+
Uploads local weights to S3 (via `MOTION_API_KEY`, no AWS creds on your machine),
|
|
79
|
+
generates `.motion/Dockerfile`, and builds `motion-<name>:latest`.
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
motion build . # build image
|
|
83
|
+
motion build . --dry-run # print Dockerfile only
|
|
84
|
+
motion build . --tag myimg:dev # custom tag
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Requires:** `MOTION_API_KEY` when `weights:` has local files to upload.
|
|
88
|
+
|
|
89
|
+
Large weights (≥64 MiB) use multipart S3 upload automatically.
|
|
90
|
+
|
|
91
|
+
### `motion deploy`
|
|
92
|
+
|
|
93
|
+
Profiles the local image, pushes to ECR, and registers the model in the platform.
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
motion deploy .
|
|
97
|
+
motion deploy . --no-profile # skip docker stats measurement
|
|
98
|
+
motion deploy . --sample tests/clip.mp4 # override profiling input
|
|
99
|
+
motion deploy . --profile-timeout 300 # longer profiling window
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Flow:**
|
|
103
|
+
1. Parse `motion.yml` (including `category`, `sample`, `compute`)
|
|
104
|
+
2. **Profile** — run the container locally, poll `docker stats`, derive `cpu_vcpus` + `memory_mib`
|
|
105
|
+
- Skipped if `compute:` is set in motion.yml (`compute_source=declared`)
|
|
106
|
+
- Skipped with `--no-profile`
|
|
107
|
+
- Skipped if no sample file is found
|
|
108
|
+
3. Push image to ECR (backend issues a short-lived docker login token)
|
|
109
|
+
4. **Register** — `POST /api/v1/motion/models/register` with:
|
|
110
|
+
- `model_code` = `<namespace>/<name>` (e.g. `saurav/lingbot-map`)
|
|
111
|
+
- `category_code`, `inference_model_slug`, compute profile, predict params
|
|
112
|
+
|
|
113
|
+
**Requires:** `MOTION_API_KEY`, local image from `motion build`.
|
|
114
|
+
|
|
115
|
+
**Category codes:** list with `GET /api/v1/motion/models` — e.g. `reconstruction`, `hand-tracking`, `mesh`, `3d-object`.
|
|
116
|
+
|
|
117
|
+
After deploy, submit jobs with:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
POST /api/v1/motion/submit model_name=<namespace>/<name>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Environment
|
|
124
|
+
|
|
125
|
+
| Variable | Purpose |
|
|
126
|
+
|----------|---------|
|
|
127
|
+
| `MOTION_API_KEY` | Auth for build (weight upload) and deploy (ECR + register) |
|
|
128
|
+
| `MOTION_API_URL` | Backend base URL (default: Motion App Runner prod) |
|
|
129
|
+
|
|
130
|
+
## Image contract
|
|
131
|
+
|
|
132
|
+
Every built image exposes: `GET /health`, `GET /schema`, `POST /predict`.
|
|
133
|
+
|
|
134
|
+
Weights declared in `motion.yml` are fetched from S3 at container startup — not baked into the image.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
motion/__init__.py,sha256=wntmmPl2ftB947kd3FcEtEF7uX_W5VGyye9ZLDF-JWc,292
|
|
2
|
+
motion/batch.py,sha256=nI_vP5Jy-IjXKV3e1vDK_XvSrAtSuMSaft-66Dvi4P8,5152
|
|
3
|
+
motion/categories.py,sha256=nfJpsBQsiTr8RvpKIR4UEh7wBmmOlt9ccKGlPKiTbUE,1235
|
|
4
|
+
motion/cli.py,sha256=PRoXW3oaHBcZ9RQjiNrJAgELIixJNgZgXtAJ-4JwJ6w,19761
|
|
5
|
+
motion/config.py,sha256=H093udCVsQkywVeEh5o3tYFU5NdToZ4BwdFcn0rmixA,5748
|
|
6
|
+
motion/dockerfile.py,sha256=yTpxRrRAjqlE8TZNsiM20oRPfuu9Nm38sc5Gsz1sZZo,5023
|
|
7
|
+
motion/predictor.py,sha256=hVGaE_0p5ohrx7QJW-fhzkl83oV9cVo42YwGtKInJ84,2211
|
|
8
|
+
motion/profile.py,sha256=5nx0OYox_btGx1DNg-tiq0EOoRVl37emcUp8eZfehSA,9735
|
|
9
|
+
motion/server.py,sha256=3v4I-78mEgcEjJ8Ler-miAscFi5qOCOCGshIgNjCs4g,5066
|
|
10
|
+
motion/weight_upload.py,sha256=J4EZKQjFZUFQ-ZU3UWsb1QvLDcqg0WQkLxOYvfqQSdI,10416
|
|
11
|
+
motion_intelligence-0.2.0.dist-info/METADATA,sha256=qsicn3hYj_WLtHrEx8ZjG7cWGXKURselGGky0kOa0OA,3991
|
|
12
|
+
motion_intelligence-0.2.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
13
|
+
motion_intelligence-0.2.0.dist-info/entry_points.txt,sha256=VNMZnZpeWzX2w7CtKSbalOs3mkKSYkeNTudgCkxnQnE,43
|
|
14
|
+
motion_intelligence-0.2.0.dist-info/top_level.txt,sha256=E9RTvbgvBIgO3RWcgGtgIHlM3v-RG0fqt-WzobhO1c0,7
|
|
15
|
+
motion_intelligence-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
motion
|