Repository navigation
[ar-api] Add native video upload and ingestion status to CLI (VID-53) - #541
digaobarbosa wants to merge 15 commits into
Conversation
…e CLI (VID-53) Action Recognition projects take whole videos as Sources, but the CLI had no way to ingest or label one. `roboflow video` only reached the legacy async video *inference* job API. Extend the existing video group with three commands over the already-published SDK methods: - `video upload` streams original MP4/MOV bytes and reports the canonical video ID, waiting for a terminal state by default. - `video upload-status` reads ingestion state. The name keeps it unambiguous against `video status`, which still checks an inference job. - `video annotate` forwards a complete roboflow-video-coco file unchanged, so native frame indices, PTS and rational time bases survive as authored. Upload composes `upload_video(wait=False)` with `wait_for_video_upload` rather than the SDK's internal wait, so a bounded-wait timeout can still name the video ID to re-check. Credential precedence, `--json` schemas and the 0/1/2/3 exit-code contract follow the existing handlers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…-53) Staging verification showed the import schema puts `segments` at the top level and takes `time_base` as a rational object, with `images`/`annotations` kept empty. The test fixture used a nested `annotations.segments` shape that the API would reject, so it documented something untrue even though the forwarding assertion passed. Rebuild the fixture from the MOV actually submitted to staging. Its presentation timestamps are not frame_index * ticks_per_frame, which is what makes "forwarded unchanged" worth asserting. Also split the upload ValueError hint: a missing path and an unsupported container arrive as the same exception type but need different fixes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Staging verificationRun against Disposable fixtures only, in FixturesTwo clips cut from my own footage, re-encoded so the content is unique to this card and no shared/deduplicated Source is touched. Both have integer nominal FPS, so this run does not cross the VID-49 fractional-FPS boundary (see limitations).
Upload, status, canonical identityOwned-file dedup — re-uploading the same owned file returns the same canonical ID with Signed bytes preservation — SHA256 of the stored object fetched from the Source's signed URL, vs the local file: Annotation submit and readbackReadback of the MOV Source. Its real presentation timestamps are not split: "test"
segments: [ { "id": "coco-1", "label": "hand_gesture",
"startFrameIndex": 5, "endFrameIndex": 64,
"startPts": 3067, "endPts": 33275,
"startTimestampUs": 199674, "endTimestampUs": 2166341 } ]
Defaults, options, conflicts
Dataset and export persistenceProject after both annotations:
Error paths through real dispatch
The three malformed-input cases never reached the API (asserted in the unit tests too). CleanupOnly the owned project was removed, through public Trash. Trash ID matches the delete response. No shared or deduplicated Source, GCS object or Firestore record was touched. CIThe matrix workflow does not run on this PR. Checks that did run on the exact head, all terminal and passing: To cover the gap, the CI matrix was reproduced locally on the exact head — full suite on each interpreter the workflow uses:
Limitations
|
…ID-53) Upload, upload-status and the project lookup now go through the shared output_api_error helper, so a rejected API key exits 2 and only a real 404 exits 3. get_project carries the HTTP status so the lookup can tell them apart. Fold duplicated video CLI tests into table-driven cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bad --poll-interval/--poll-timeout values and a missing file are now rejected before any network call, so an upload never stores bytes and then reports failure without an ID. Upload API errors follow the exit-code contract, and their hint no longer blames the file when the failing call came after the PUT. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Let typer enforce the poll bounds with min= constraints instead of a handler-side check, collapse the metadata parsing into one error path, and drop CLI tests that only re-checked --help or shared resolver behaviour. Add a direct test for the get_project status code that the exit-code mapping relies on. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
click's str(BadParameter) drops the parameter name, so --json callers saw "0.0 is not in the range x>=0.1." with nothing to act on. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Superseded by #544, which contained all of this branch and is merged to main. |
Motivation
Action Recognition projects take whole videos as Sources, but the CLI only offered legacy video inference jobs. Users could not upload an original video or follow its processing from the terminal.
roboflow video uploadnow sends the original MP4/MOV bytes unchanged and reports the canonical video ID. That ID can differ from the first one when the same content already exists.roboflow video upload-statusreads or waits for processing with that ID. Legacyvideo inferandvideo statuskeep their own behavior.Failures follow the CLI exit-code contract: a rejected API key exits 2, an unknown project or video exits 3, and other failures exit 1. A missing file or a bad wait bound is rejected before any bytes are sent.
Stack and dependency
The SDK dependency (PR #534) is merged, so this PR targets
main. Segment annotation is stacked on top in PR #544 (VID-59), which contains this branch. Merge this one first.Changes since reopening
This PR was closed by mistake and revived on 2026-10-09 with
mainmerged in and a simplification pass:--poll-interval(min=0.1) and--poll-timeout(min=0), replacing a handler-side check. Bad bounds are usage errors (exit 2) and still fail before any bytes are stored.--metadataparsing has one error path.Invalid value for '--poll-interval': …. Click'sstr()dropped it for everymin=option in the CLI.--helpor shared resolver behaviour. Added a direct test for theget_projectstatus code that the exit-code mapping relies on.Verification
834f4bd: 1,224 tests pass, one skipped, also with Rich color forced as in CI.ruff check,ruff format --checkandmypy roboflowpass.dc3f815: 11/11 passed againstapi.roboflow.one/model-evaluation-workspacewith the installed CLI, in 31 s. The only later commit changes one test assertion. The run's project was moved to Trash: a laterproject getexits 3 andtrash listshows it.main.Staging E2E results: upload and upload-status (head dc3f815, 2026-10-09)
Commands ran as
roboflow --json -k $KEY -w model-evaluation-workspace …(shown without the key and workspace).$Pis a private Action Recognition project the run created and then moved to Trash. Uploads are byte-unique stream copies of real 24 fps clips (time base 1/12288), so no shared Source is touched. The cases are scripted inrun_native_video_e2e.shfrom the e2e-test skill.roboflow --json video upload -f $SCRATCH/unique-copy.mp4 -p $P --no-waitroboflow --json video upload-status <id> -p $Proboflow --json video upload-status <id> -p $P --wait --poll-timeout 120roboflow --json video upload -f $SCRATCH/unique-copy.mp4 -p $Proboflow --json video upload-status doesnotexist123 -p $Proboflow --json video upload -f $SCRATCH/clip.avi -p $Proboflow --json video upload -f $SCRATCH/absent.mp4 -p $Proboflow --json video upload -f $SCRATCH/unique-copy.mp4 -p no-such-project-xyzroboflow --json -k bogus video upload-status <id> -p $Proboflow --json video upload -f $SCRATCH/unique-copy.mp4 -p $P --poll-interval 0roboflow --json video upload-status <id> -p $P --wait --poll-timeout -1Notes
--helphint instead of the CLI's project hint. The sharedoutput_errorlets a server hint win for every command, so it is out of scope here.🤖 Generated with Claude Code