idiotproof 0.5.0__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.
- idiotproof-0.5.0/.github/workflows/publish.yml +74 -0
- idiotproof-0.5.0/.gitignore +11 -0
- idiotproof-0.5.0/CHANGELOG.md +88 -0
- idiotproof-0.5.0/PKG-INFO +364 -0
- idiotproof-0.5.0/README.md +344 -0
- idiotproof-0.5.0/examples/parallel_tools.py +34 -0
- idiotproof-0.5.0/pyproject.toml +61 -0
- idiotproof-0.5.0/src/idiotproof/__init__.py +38 -0
- idiotproof-0.5.0/src/idiotproof/cli.py +146 -0
- idiotproof-0.5.0/src/idiotproof/mcp_server.py +216 -0
- idiotproof-0.5.0/src/idiotproof/media.py +1373 -0
- idiotproof-0.5.0/src/idiotproof/py.typed +1 -0
- idiotproof-0.5.0/src/idiotproof/sdk.py +1248 -0
- idiotproof-0.5.0/tests/test_cli.py +112 -0
- idiotproof-0.5.0/tests/test_mcp_server.py +74 -0
- idiotproof-0.5.0/tests/test_media.py +846 -0
- idiotproof-0.5.0/tests/test_sdk.py +748 -0
- idiotproof-0.5.0/uv.lock +484 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build:
|
|
13
|
+
name: Build and verify distributions
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- name: Check out repository
|
|
18
|
+
uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Set up Python
|
|
21
|
+
uses: actions/setup-python@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: "3.13"
|
|
24
|
+
|
|
25
|
+
- name: Install build and test dependencies
|
|
26
|
+
run: |
|
|
27
|
+
python -m pip install --upgrade pip
|
|
28
|
+
python -m pip install -e .
|
|
29
|
+
python -m pip install build twine pytest pytest-asyncio ruff mypy
|
|
30
|
+
|
|
31
|
+
- name: Verify tag matches package version
|
|
32
|
+
run: |
|
|
33
|
+
python -c 'import os, tomllib; version = tomllib.load(open("pyproject.toml", "rb"))["project"]["version"]; assert os.environ["GITHUB_REF_NAME"] == f"v{version}", "Release tag must match package version"'
|
|
34
|
+
|
|
35
|
+
- name: Lint
|
|
36
|
+
run: ruff check .
|
|
37
|
+
|
|
38
|
+
- name: Type-check
|
|
39
|
+
run: mypy src/idiotproof
|
|
40
|
+
|
|
41
|
+
- name: Test
|
|
42
|
+
run: pytest
|
|
43
|
+
|
|
44
|
+
- name: Build distributions
|
|
45
|
+
run: python -m build
|
|
46
|
+
|
|
47
|
+
- name: Validate distributions
|
|
48
|
+
run: twine check dist/*
|
|
49
|
+
|
|
50
|
+
- name: Upload distributions
|
|
51
|
+
uses: actions/upload-artifact@v4
|
|
52
|
+
with:
|
|
53
|
+
name: python-package-distributions
|
|
54
|
+
path: dist/
|
|
55
|
+
|
|
56
|
+
publish:
|
|
57
|
+
name: Publish distributions to PyPI
|
|
58
|
+
needs: build
|
|
59
|
+
runs-on: ubuntu-latest
|
|
60
|
+
environment:
|
|
61
|
+
name: pypi
|
|
62
|
+
url: https://pypi.org/p/idiotproof
|
|
63
|
+
permissions:
|
|
64
|
+
id-token: write
|
|
65
|
+
|
|
66
|
+
steps:
|
|
67
|
+
- name: Download distributions
|
|
68
|
+
uses: actions/download-artifact@v4
|
|
69
|
+
with:
|
|
70
|
+
name: python-package-distributions
|
|
71
|
+
path: dist/
|
|
72
|
+
|
|
73
|
+
- name: Publish distributions to PyPI
|
|
74
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.5.0 — 2026-09-03
|
|
4
|
+
|
|
5
|
+
- Renamed the public PyPI distribution to `idiotproof` before its first
|
|
6
|
+
publication. The Python import is `idiotproof`; the CLI remains `idea-adk`.
|
|
7
|
+
- Replaced the provisional reservation endpoint with the deployed metadata-only
|
|
8
|
+
TUS batch contract.
|
|
9
|
+
- Added automatic client-side FFmpeg segmentation in `upload_batch()` for
|
|
10
|
+
sources longer than 600 decoded frames.
|
|
11
|
+
- Made `upload_file()` detect long video and transparently return an ordered
|
|
12
|
+
aggregate of reserved parts instead of allowing server-side truncation.
|
|
13
|
+
- Verify every generated part is independently decodable, contains at most 600
|
|
14
|
+
frames, and preserves the source's total decoded frame count.
|
|
15
|
+
- Add `uploadBatchId`, `uploadBatchIndex`, and `uploadBatchSize` directly to
|
|
16
|
+
every authenticated TUS creation without changing the public upload API.
|
|
17
|
+
- Validate batch placement and normalized output basenames before returning
|
|
18
|
+
ordered results.
|
|
19
|
+
- Preserve every concatenated video frame when remuxing `audio_source`, even
|
|
20
|
+
when the source audio ends slightly before the video, while stream-copying
|
|
21
|
+
the complete encoded audio track.
|
|
22
|
+
- Reuse `upload_file()`'s decoded source-frame count during segmentation
|
|
23
|
+
instead of decoding every long source twice.
|
|
24
|
+
- Run independent generated-part and downloaded-part verification probes with
|
|
25
|
+
bounded local concurrency while preserving ordered validation results.
|
|
26
|
+
|
|
27
|
+
## 0.4.0 — 2026-09-02
|
|
28
|
+
|
|
29
|
+
- Added immutable, locally validated `UploadSequence` placement metadata for
|
|
30
|
+
ordered multi-asset uploads.
|
|
31
|
+
- Added optional `sequence=` support to `upload_file()` while continuing to
|
|
32
|
+
send only server-authorized metadata to TUS.
|
|
33
|
+
- Added `upload_batch()` for complete preflight validation, generated opaque
|
|
34
|
+
batch/asset IDs, bounded concurrency, and nested input-order results across
|
|
35
|
+
segmented videos and singleton images.
|
|
36
|
+
- Added strict checks that authorization and normalized results preserve the
|
|
37
|
+
requested sequence, plus CLI and MCP schema support for explicit placement.
|
|
38
|
+
|
|
39
|
+
## 0.3.0 — 2026-09-02
|
|
40
|
+
|
|
41
|
+
- Added `Idea.download_media()` with safe streaming fallback, verified bounded
|
|
42
|
+
HTTP ranges, strong-ETag identity checks, resumable range state, SHA-256, and
|
|
43
|
+
atomic destination replacement.
|
|
44
|
+
- Added `download_and_concat()` for ordered workspace videos, including stable
|
|
45
|
+
indexed part names, complete ffprobe reports, strict dimension invariants,
|
|
46
|
+
copy compatibility validation, explicit encoding, optional source-audio
|
|
47
|
+
remuxing, resumable manifests, and verified atomic output.
|
|
48
|
+
- Kept signed media URLs out of manifests, subprocesses, diagnostics, and
|
|
49
|
+
durable state.
|
|
50
|
+
- Documented preview-first iteration as distinct from durable download and
|
|
51
|
+
concatenation.
|
|
52
|
+
|
|
53
|
+
## 0.2.1 — 2026-09-02
|
|
54
|
+
|
|
55
|
+
- Added `numbered_media()` for predictable, unique, ordered workspace and
|
|
56
|
+
output path generation.
|
|
57
|
+
|
|
58
|
+
## 0.2.0 — 2026-09-02
|
|
59
|
+
|
|
60
|
+
- Replaced generated async functions with cached, callable `RuntimeTool`
|
|
61
|
+
objects that retain live names, schemas, documentation, and signatures.
|
|
62
|
+
- Added reusable common-argument binding with `partial()`.
|
|
63
|
+
- Added ordered asynchronous `map()` with bounded concurrency, complete local
|
|
64
|
+
preflight validation, and no automatic remote-tool retries.
|
|
65
|
+
- Added structured `MapItem` outcomes and fail-after-settlement
|
|
66
|
+
`IdeaBatchError` handling.
|
|
67
|
+
- Added the CLI `map` command and documented cancellation and the boundary
|
|
68
|
+
between media segmentation and sequential resumable TUS chunks.
|
|
69
|
+
## 0.1.0 — 2026-09-02
|
|
70
|
+
|
|
71
|
+
- Established the working IDEA ADK package.
|
|
72
|
+
- Defined an ADK as unstable software for agents and one-off scripts, not an
|
|
73
|
+
application framework for persistent software.
|
|
74
|
+
- Kept the primary Python surface centered on `Idea`, `ChatId`, and structured
|
|
75
|
+
`IdeaError` failures.
|
|
76
|
+
- Added local chat ID creation, public TUS uploads, stable media publishing,
|
|
77
|
+
and live MCP tool discovery.
|
|
78
|
+
- Added callable `RuntimeTool` objects generated from MCP descriptions and JSON
|
|
79
|
+
Schemas, with reusable partial binding and ordered, bounded asynchronous
|
|
80
|
+
`map()`.
|
|
81
|
+
- Added `MapItem` outcomes and `IdeaBatchError` so mapped successes remain
|
|
82
|
+
observable when other items fail.
|
|
83
|
+
- Added a JSON CLI and stdio MCP adapter for coding agents.
|
|
84
|
+
- Added the JSON CLI `map` command with fail-after-settlement and collected-error
|
|
85
|
+
modes.
|
|
86
|
+
- Added explicit localhost visual-review instructions for transient signed
|
|
87
|
+
media URLs.
|
|
88
|
+
- Consolidated the complete public documentation into the PyPI README.
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: idiotproof
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Software for agents that create and edit video.
|
|
5
|
+
Project-URL: Repository, https://github.com/4014-Labs/idiotproof
|
|
6
|
+
Project-URL: Issues, https://github.com/4014-Labs/idiotproof/issues
|
|
7
|
+
Author: 4014 Labs
|
|
8
|
+
License: Proprietary
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Typing :: Typed
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Requires-Dist: httpx<1,>=0.27
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# idiotproof
|
|
22
|
+
|
|
23
|
+
Software for agents that create and edit video.
|
|
24
|
+
|
|
25
|
+
`idiotproof` is the IDEA ADK. ADK means **Agent Development Kit**: software
|
|
26
|
+
intended to be written and run by agents. It is designed for one-off Python scripts, CLI calls, and
|
|
27
|
+
headless agent workflows alongside FFmpeg. It is not an application framework
|
|
28
|
+
or a stable dependency for persistent software. Do not build a long-lived
|
|
29
|
+
service around its current interfaces; the package and dynamic tool schemas
|
|
30
|
+
will change as the product develops.
|
|
31
|
+
|
|
32
|
+
Bobbie plus FFmpeg is intended to be a complete headless media toolchain. Use
|
|
33
|
+
FFmpeg for cutting, joining, transcoding, and audio. Use Bobbie for GPU-native
|
|
34
|
+
vision and rendering.
|
|
35
|
+
|
|
36
|
+
## Install and authenticate
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install idiotproof
|
|
40
|
+
export IDEA_SECRET_KEY=idea_sk_test_...
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The alpha client defaults to `https://dev.67idea.com/v1`. Override it with
|
|
44
|
+
`IDEA_API_BASE_URL` or `Idea(base_url=...)`. A secret key belongs to one IDEA
|
|
45
|
+
project and environment. The API mints short-lived, workspace-scoped JWTs
|
|
46
|
+
internally; your code never creates or forwards those JWTs.
|
|
47
|
+
|
|
48
|
+
## Required visual-review workflow
|
|
49
|
+
|
|
50
|
+
`media_publish()` returns signed URLs specifically so an agent can inspect its
|
|
51
|
+
work without downloading it. Signed URLs are transient credentials, not values
|
|
52
|
+
that must be ignored.
|
|
53
|
+
|
|
54
|
+
After publishing media, an agent should:
|
|
55
|
+
|
|
56
|
+
1. Keep the signed URL only in the local viewer process's memory.
|
|
57
|
+
2. Generate the viewer response dynamically. A page may contain several URLs
|
|
58
|
+
to compare multiple edits, but no signed URL belongs in an on-disk HTML file
|
|
59
|
+
or manifest.
|
|
60
|
+
3. Serve only that directory with a server bound to `127.0.0.1`, never
|
|
61
|
+
`0.0.0.0`.
|
|
62
|
+
4. Open the localhost URL in the available browser. If browser control is
|
|
63
|
+
unavailable, still create the viewer and provide its localhost URL.
|
|
64
|
+
5. View the remote media through the page; do not download it merely to
|
|
65
|
+
preview it.
|
|
66
|
+
6. Do not print, paste into chat, persist, or commit the signed URL. Stop the
|
|
67
|
+
viewer process after review or expiration.
|
|
68
|
+
|
|
69
|
+
Downloading is appropriate when saving an edit for continued local work or
|
|
70
|
+
durable storage. It is not required for visual review.
|
|
71
|
+
|
|
72
|
+
## Python
|
|
73
|
+
|
|
74
|
+
The public vocabulary is deliberately small: one `Idea` client, opaque
|
|
75
|
+
`ChatId` values, dynamic runtime tools, and structured `IdeaError` failures.
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
import asyncio
|
|
79
|
+
import json
|
|
80
|
+
from pathlib import Path
|
|
81
|
+
|
|
82
|
+
from idiotproof import Idea
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
async def main() -> None:
|
|
86
|
+
async with Idea() as idea:
|
|
87
|
+
chat_id = await idea.create_chat()
|
|
88
|
+
upload = await idea.upload_file(chat_id, "clip.mp4")
|
|
89
|
+
|
|
90
|
+
tools = await idea.get_tools()
|
|
91
|
+
print([definition["id"] for definition in tools])
|
|
92
|
+
|
|
93
|
+
# Functions and documentation come from the live MCP catalog.
|
|
94
|
+
print(tools.submit_bobbie_job.__doc__)
|
|
95
|
+
render_input = json.loads(Path("render.json").read_text())
|
|
96
|
+
render_input.setdefault("input_media", upload["workspace_uri"])
|
|
97
|
+
result = await tools.submit_bobbie_job(chat_id, **render_input)
|
|
98
|
+
|
|
99
|
+
published = await idea.media_publish(chat_id, result["output_media"])
|
|
100
|
+
# Give display_url directly to an in-memory viewer bound to 127.0.0.1.
|
|
101
|
+
# That viewer is agent workflow code, not an SDK side effect. Do not
|
|
102
|
+
# print the URL or write it into HTML, JSON, logs, or a manifest.
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
asyncio.run(main())
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`create_chat()` returns a random opaque `ChatId` locally. The API lazily
|
|
109
|
+
materializes its project-scoped server workspace on the first upload or tool
|
|
110
|
+
call. Save the ID if another process must continue in the same workspace. Do
|
|
111
|
+
not put email addresses or other personal data in it.
|
|
112
|
+
|
|
113
|
+
`upload_file(chat_id, path)` probes video before network activity. An image or
|
|
114
|
+
video of at most 600 decoded frames follows the ordinary authorized TUS path
|
|
115
|
+
and returns one upload JSON object with `workspace_uri`. A longer video
|
|
116
|
+
automatically follows the ordered TUS metadata workflow described below and
|
|
117
|
+
returns an `upload_asset` JSON object with ordered `parts` and
|
|
118
|
+
`workspace_uris`; it never silently uploads a truncated single result.
|
|
119
|
+
|
|
120
|
+
For long videos or concurrent ordered uploads, use `upload_batch()` and
|
|
121
|
+
describe logical assets rather than a global upload queue:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
uploads = await idea.upload_batch(
|
|
125
|
+
chat_id,
|
|
126
|
+
[
|
|
127
|
+
"long-video-a.mp4",
|
|
128
|
+
["video-b-001.mp4", "video-b-002.mp4"],
|
|
129
|
+
"reference.png",
|
|
130
|
+
],
|
|
131
|
+
concurrency=4,
|
|
132
|
+
)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Each top-level item is one logical asset. A bare path is one source asset; an
|
|
136
|
+
ordered path sequence supplies already separated pieces of one asset. Before
|
|
137
|
+
any request, the helper probes videos with `ffprobe` and uses FFmpeg to
|
|
138
|
+
re-encode every source longer than 600 frames into independently decodable
|
|
139
|
+
parts of at most 600 frames. It verifies that the parts preserve the complete
|
|
140
|
+
decoded frame count.
|
|
141
|
+
|
|
142
|
+
The helper flattens those parts in caller order and adds `uploadBatchId`,
|
|
143
|
+
`uploadBatchIndex`, and `uploadBatchSize` to each authenticated TUS creation.
|
|
144
|
+
The first creation reserves the complete contiguous block of `upload_NNN`
|
|
145
|
+
names; transfers may then proceed concurrently in any order. No reservation
|
|
146
|
+
endpoint or client-side completion throttle is involved. Results retain the
|
|
147
|
+
same nested asset/part order, and the helper verifies that returned basenames
|
|
148
|
+
form one contiguous block in that order. If an upload fails, it lets every
|
|
149
|
+
started operation settle and raises `IdeaBatchError`; its flat `items` retain
|
|
150
|
+
successes and failures in original asset/part traversal order.
|
|
151
|
+
|
|
152
|
+
`media_publish(chat_id, media_path)` calls the stable API-key-authenticated
|
|
153
|
+
media endpoint and returns short-lived viewing and download URLs. It is outside
|
|
154
|
+
the dynamic MCP catalog and does not depend on tool discovery.
|
|
155
|
+
|
|
156
|
+
`download_media(chat_id, media_path, destination)` is the durable counterpart.
|
|
157
|
+
It publishes internally, keeps the signed download URL in memory, verifies the
|
|
158
|
+
download, and atomically replaces the destination only after success. Small
|
|
159
|
+
objects and origins without safe range support use one streamed request. Large
|
|
160
|
+
objects use bounded parallel byte ranges only when the origin supplies a
|
|
161
|
+
content length, byte-range support, and a strong ETag. Range responses must
|
|
162
|
+
match their requested offsets and object identity; otherwise the helper safely
|
|
163
|
+
falls back or fails. `resume=True` verifies completed temporary ranges after
|
|
164
|
+
republishing the stable workspace path. Resume files never contain signed
|
|
165
|
+
URLs. In `download_and_concat()`, one shared transfer limit bounds publishing,
|
|
166
|
+
whole-object requests, and nested range requests so concurrency does not
|
|
167
|
+
multiply by the number of files.
|
|
168
|
+
|
|
169
|
+
`get_tools()` fetches each dynamic tool's name, description, and JSON Schema at
|
|
170
|
+
runtime. It returns an iterable catalog and generates documented async methods
|
|
171
|
+
such as `tools.submit_bobbie_job(...)`. Punctuation becomes `_`; use
|
|
172
|
+
`await tools.call(exact_name, chat_id, input_dict)` for unusual or colliding
|
|
173
|
+
names. Tool methods return the tool output rather than a transport wrapper.
|
|
174
|
+
|
|
175
|
+
Every discovered tool is a callable `RuntimeTool`. Call one normally, bind
|
|
176
|
+
shared arguments once with `partial()`, or map it over varying inputs with
|
|
177
|
+
bounded concurrency:
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
render = tools.submit_bobbie_job.partial(
|
|
181
|
+
chat_id,
|
|
182
|
+
passes=effect,
|
|
183
|
+
timeout_seconds=300,
|
|
184
|
+
)
|
|
185
|
+
jobs = await render.map(
|
|
186
|
+
[
|
|
187
|
+
{"input_media": "workspace:segment-001.mp4", "output_media": "one.mp4"},
|
|
188
|
+
{"input_media": "workspace:segment-002.mp4", "output_media": "two.mp4"},
|
|
189
|
+
],
|
|
190
|
+
concurrency=4,
|
|
191
|
+
)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Results retain input order even when calls finish out of order. The entire
|
|
195
|
+
input iterable is validated before any request starts, and an input key may
|
|
196
|
+
not duplicate a bound/common key. `tools.map(exact_name, chat_id, inputs)` is
|
|
197
|
+
the escape hatch for exact MCP names. `errors="collect"` returns ordered
|
|
198
|
+
`MapItem` values; the default waits for every item to settle and then raises
|
|
199
|
+
`IdeaBatchError`, whose `items` preserve both successes and failures. Mapped
|
|
200
|
+
tool calls are not automatically retried because they may have side effects.
|
|
201
|
+
|
|
202
|
+
Cancelling `map()` prevents queued calls from starting and cancels local waits
|
|
203
|
+
for calls already in flight. It cannot retract a remote operation that the
|
|
204
|
+
service already accepted.
|
|
205
|
+
|
|
206
|
+
Use `numbered_media()` when a pipeline needs predictable ordered workspace or
|
|
207
|
+
output names:
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
from idiotproof import numbered_media
|
|
211
|
+
|
|
212
|
+
media = numbered_media(
|
|
213
|
+
"workspace:video_a_rendered_{index:03d}.mp4",
|
|
214
|
+
count=3,
|
|
215
|
+
)
|
|
216
|
+
# workspace:video_a_rendered_001.mp4, ...002.mp4, ...003.mp4
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
`count` must be non-negative, and the formatted values must be unique. Use a
|
|
220
|
+
stable sequence prefix when several source videos share a workspace. Generate
|
|
221
|
+
names before launching concurrent work and retain input order when collecting
|
|
222
|
+
results; completion time must never determine segment order. A workflow
|
|
223
|
+
manifest remains authoritative—zero-padding is convenient, not an ordering
|
|
224
|
+
guarantee. This helper is for predictable workspace and output names, never
|
|
225
|
+
`media_publish()` URLs: published URLs are signed and cannot be reconstructed
|
|
226
|
+
from a pattern.
|
|
227
|
+
|
|
228
|
+
Use `download_and_concat()` when ordered workspace videos are final and must
|
|
229
|
+
become one durable local video:
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
from idiotproof import download_and_concat, numbered_media
|
|
233
|
+
|
|
234
|
+
combined = await download_and_concat(
|
|
235
|
+
idea,
|
|
236
|
+
chat_id,
|
|
237
|
+
numbered_media(
|
|
238
|
+
"workspace:video_a_rendered_{index:03d}.mp4",
|
|
239
|
+
count=11,
|
|
240
|
+
),
|
|
241
|
+
output="output/final.mp4",
|
|
242
|
+
concurrency=4,
|
|
243
|
+
resume=True,
|
|
244
|
+
)
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
The input must be an ordered sequence; sets, mappings, and bare strings are
|
|
248
|
+
rejected. Downloads may complete in any order, but index-derived local names
|
|
249
|
+
and caller order control concatenation. Every part is probed with `ffprobe`.
|
|
250
|
+
All selected video streams must have exactly equal width and height—neither
|
|
251
|
+
mode scales, crops, pads, or rotates a mismatch. The default
|
|
252
|
+
`concat_mode="copy"` requires compatible streams and never re-encodes.
|
|
253
|
+
Explicit `"encode"` mode may normalize other stream properties while
|
|
254
|
+
preserving the dimension invariant. Supplying `audio_source` ignores segment
|
|
255
|
+
audio and remuxes that local audio once with stream copying. It preserves the
|
|
256
|
+
complete video sequence even when that audio ends slightly earlier. FFmpeg
|
|
257
|
+
and ffprobe must be installed. The result is a `CombinedMedia` containing the
|
|
258
|
+
final probe summary and SHA-256.
|
|
259
|
+
|
|
260
|
+
This is a durable-output helper, not the visual-review path. Iterate by
|
|
261
|
+
publishing a short representative clip and comparing remote `display_url`
|
|
262
|
+
variants through the localhost viewer. Download and concatenate only after an
|
|
263
|
+
effect is selected.
|
|
264
|
+
|
|
265
|
+
All individual failures are `IdeaError`; inspect `code`, `status_code`,
|
|
266
|
+
`details`, and `request_id`. Never log the secret key or upload token. Signed
|
|
267
|
+
media URLs may be retained transiently for the localhost viewer, but should
|
|
268
|
+
not appear in terminal history, chat messages, durable logs, or version
|
|
269
|
+
control.
|
|
270
|
+
|
|
271
|
+
## CLI and MCP
|
|
272
|
+
|
|
273
|
+
Commands emit JSON so agents can compose them with scripts:
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
idea-adk create-chat
|
|
277
|
+
idea-adk upload-file "$CHAT_ID" clip.mp4
|
|
278
|
+
idea-adk get-tools
|
|
279
|
+
idea-adk call "$CHAT_ID" submit_bobbie_job --input @render.json
|
|
280
|
+
idea-adk map "$CHAT_ID" submit_bobbie_job --input @batch.json --concurrency 4
|
|
281
|
+
idea-adk media-publish "$CHAT_ID" workspace:result.mp4
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
`batch.json` contains an `inputs` array and an optional `common` object:
|
|
285
|
+
|
|
286
|
+
```json
|
|
287
|
+
{
|
|
288
|
+
"common": {"passes": [{"kind": "fragment_shader", "shader_text": "..."}]},
|
|
289
|
+
"inputs": [
|
|
290
|
+
{"input_media": "workspace:one.mp4", "output_media": "one-out.mp4"},
|
|
291
|
+
{"input_media": "workspace:two.mp4", "output_media": "two-out.mp4"}
|
|
292
|
+
]
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
The CLI returns `{"results": [...]}` by default. With `--errors collect`, it
|
|
297
|
+
returns ordered `items`, each containing its `index` and exactly one of
|
|
298
|
+
`result` or structured `error`.
|
|
299
|
+
|
|
300
|
+
Use Python `upload_batch()` when a source may exceed 600 frames or the ADK must
|
|
301
|
+
reserve and upload a whole multi-asset manifest.
|
|
302
|
+
|
|
303
|
+
`media-publish` returns its requested JSON to stdout, including signed URLs;
|
|
304
|
+
do not use that command in a logged shell. A localhost review process should
|
|
305
|
+
call `media_publish()` internally and retain the response only in memory.
|
|
306
|
+
|
|
307
|
+
Run `idea-adk mcp` as a stdio MCP adapter and let it inherit
|
|
308
|
+
`IDEA_SECRET_KEY`. It exposes `create_chat`, `upload_file`, and `get_tools`,
|
|
309
|
+
followed by functions generated from the live server catalog. Media publishing
|
|
310
|
+
remains a separate stable Python/CLI operation and is intentionally not added
|
|
311
|
+
to the dynamic MCP catalog.
|
|
312
|
+
|
|
313
|
+
This README is the default advice agents should receive. Project-specific
|
|
314
|
+
instructions belong in `AGENTS.md` for Codex or `CLAUDE.md` for Claude Code.
|
|
315
|
+
|
|
316
|
+
## Rules of thumb for agents
|
|
317
|
+
|
|
318
|
+
Uploads are currently normalized to exactly 600 frames at 3 megapixels,
|
|
319
|
+
slightly above 1080p. `upload_file()` detects a source beyond the input limit
|
|
320
|
+
and automatically creates independently decodable parts without dropping
|
|
321
|
+
source frames. Use `upload_batch()` directly for several logical assets or
|
|
322
|
+
when you want an explicitly nested result.
|
|
323
|
+
|
|
324
|
+
For a novel effect, first probe frame rate and time base and render one
|
|
325
|
+
representative preview clip targeting about 20 seconds while staying safely
|
|
326
|
+
below the service limit—590 frames is a useful cap when the maximum is 600.
|
|
327
|
+
Use an explicit range when supplied; otherwise the source midpoint is a
|
|
328
|
+
deterministic fallback, while a vision-capable agent may deliberately select a
|
|
329
|
+
high-motion or representative region. Upload that preview once and map several
|
|
330
|
+
effect variants over it. After choosing an effect, render the complete ordered
|
|
331
|
+
segments once. Direct full-source rendering remains reasonable for a known or
|
|
332
|
+
trivial effect.
|
|
333
|
+
|
|
334
|
+
Each TUS resource uses sequential, resumable chunks; core TUS requires ordered
|
|
335
|
+
offsets within that resource. For a long source, `upload_file()` delegates to
|
|
336
|
+
`upload_batch()`, which performs media-aware splitting before network activity,
|
|
337
|
+
reserves every output name in one transaction, and uploads those separate
|
|
338
|
+
resources concurrently. TUS chunks are transport details; they are not
|
|
339
|
+
independently decodable media segments. Upload completion order is never media
|
|
340
|
+
order.
|
|
341
|
+
|
|
342
|
+
Bobbie is a custom rendering engine that runs arbitrary GLSL. You describe the
|
|
343
|
+
pipeline but do not control bindings; Bobbie assigns them programmatically. A
|
|
344
|
+
pipeline can combine GLSL, smaller ML models, optimized CUDA kernels for
|
|
345
|
+
classical computer vision, and optional Bayesian priors over color and shape.
|
|
346
|
+
CUDA-GLSL interop keeps video on the GPU. Because Bobbie controls every tensor
|
|
347
|
+
dimension, supported pipelines should not run out of GPU memory.
|
|
348
|
+
|
|
349
|
+
Invalid requests should fail before rendering. The expected render-time
|
|
350
|
+
failures are a video timeout and `file too large`. Incompressible output such as
|
|
351
|
+
raw static can exceed 100 MB; the service deletes it. Elaborate pipelines can
|
|
352
|
+
serve either image/video editing or advanced computer-vision work.
|
|
353
|
+
|
|
354
|
+
A separate tool extracts frames for a VLM. The current model is Qwen-VL 27B;
|
|
355
|
+
custom VLMs are not supported. Bobbie requires grounded objects and coordinates
|
|
356
|
+
normalized to the half-open interval `[0, 1000)`.
|
|
357
|
+
|
|
358
|
+
Frame/VLM inspection plus Bobbie lets an agent move through any video. Use code
|
|
359
|
+
to crop, zoom, and rotate. Use vision tools to measure camera and object motion
|
|
360
|
+
and perform tracking, detection, and segmentation. Other catalog tools expose
|
|
361
|
+
metadata about uploaded files, processes, and jobs.
|
|
362
|
+
|
|
363
|
+
IDEA deletes media aggressively. If an edit matters, download it for continued
|
|
364
|
+
local editing or copy it immediately into durable storage such as S3.
|