Video
Upload videos, probe metadata, and extract frames as images into a dataset.
Pictograph annotates frames, not videos. This resource handles upload and frame extraction; once extracted, frames are ordinary images you annotate with the standard tools.
Ingestion is three steps - upload, then optionally probe, then
extract_frames. Each step passes along the gcs_path returned by upload,
which is a temporary handle: the source video is deleted once extraction
finishes.
upload
Upload a local video to a temporary path. The SDK requests a signed URL, PUTs
the bytes straight to it and returns the path, so the video never passes through
the API itself. content_type defaults to video/mp4; set it for other
containers (video/quicktime, video/webm).
Source: Video.upload
| Arg | Type | Default | Notes |
|---|---|---|---|
local_path |
str | Path |
required | Path to the local video file. |
content_type |
str |
'video/mp4' |
MIME type (default video/mp4). Set explicitly for non-MP4 sources (video/quicktime, video/webm, etc.). |
info = client.video.upload(
local_path="./recording.mp4",
content_type="video/mp4",
)
print(info.gcs_path)
pictograph video upload ./recording.mp4 --content-type video/mp4
# Step 1: request a signed upload URL and the temporary path.
curl -s -X POST "https://api.pictograph.io/api/v1/developer/video/upload-url" \
-H "X-API-Key: $PICTOGRAPH_API_KEY" \
-H "Content-Type: application/json" \
-d '{"filename": "recording.mp4", "content_type": "video/mp4"}'
# Step 2: PUT the bytes to the returned upload_url. No API key - the signed URL
# carries its own signature.
curl -s -X PUT "<upload_url>" \
-H "Content-Type: video/mp4" \
--data-binary @./recording.mp4
Returns VideoUploadInfo
VideoUploadInfo · 3 fields
class VideoUploadInfo(BaseModel):
"""Signed URL + temporary storage path returned by `upload-url`."""
upload_url: str
gcs_path: str
gcs_uri: str
Pass gcs_path to probe and extract_frames. Any container ffmpeg can demux works - H.264, H.265, VP9, AV1 and friends.
probe
Inspect a video’s metadata without extracting frames. This reads the full file server-side, so it is slow on large videos - call it once, never poll it.
Source: Video.probe
| Arg | Type | Default | Notes |
|---|---|---|---|
gcs_path |
str |
required | The gcs_path returned from upload. |
meta = client.video.probe(
gcs_path=info.gcs_path,
)
print(meta.duration_seconds, meta.native_fps, meta.width, meta.height, meta.frame_count)
pictograph video probe "<gcs_path-from-upload>"
curl -s -X POST "https://api.pictograph.io/api/v1/developer/video/probe" \
-H "X-API-Key: $PICTOGRAPH_API_KEY" \
-H "Content-Type: application/json" \
-d '{"gcs_path": "<gcs_path-from-upload>"}'
Returns VideoMetadata
VideoMetadata · 5 fields
class VideoMetadata(BaseModel):
"""Probe result for an uploaded video."""
duration_seconds: float
native_fps: float
width: int
height: int
frame_count: int
extract_frames
Decode frames at sample_fps into a new directory under the destination
dataset, registering each as an image with embeddings and content tags
generated automatically.
Source: Video.extract_frames
| Arg | Type | Default | Notes |
|---|---|---|---|
dataset_name |
str |
required | Dataset name. |
gcs_path |
str |
required | Source video from upload |
directory_name |
str |
required | Directory created to hold the frames |
sample_fps |
float |
1.0 |
Frames per source second, up to 60 |
parent_directory_path |
str |
"/" |
Where the new directory is created |
wait |
bool |
True |
Poll until terminal |
poll_interval |
float |
3.0 |
Seconds between polls |
timeout |
float |
1800.0 |
Max seconds to wait |
job = client.video.extract_frames(
dataset_name="my-dataset",
gcs_path=info.gcs_path,
directory_name="frames",
sample_fps=2.0,
parent_directory_path="/raw-footage",
timeout=1800.0,
)
print(job.status, job.frames_extracted, job.directory_path)
pictograph video extract-frames my-dataset "<gcs_path-from-upload>" \
--directory-name frames \
--sample-fps 2.0 \
--parent-directory-path /raw-footage
curl -s -X POST "https://api.pictograph.io/api/v1/developer/video/extract-frames" \
-H "X-API-Key: $PICTOGRAPH_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dataset_name": "my-dataset", "gcs_path": "<gcs_path-from-upload>",
"directory_name": "frames", "sample_fps": 2.0,
"parent_directory_path": "/raw-footage"}'
Returns VideoExtractionJob
VideoExtractionJob · 9 fields
class VideoExtractionJob(BaseModel):
"""Snapshot of a frame-extraction job."""
job_id: str
status: Literal['processing', 'complete', 'failed']
progress: int = 0
frames_extracted: int = 0
total_frames: int = 0
error: str | None = None
directory_path: str | None = None
warning: str | None = None
image_ids: list[str] | None = None
Each extracted frame becomes a regular image, ready for annotation, search and
training. sample_fps=1.0 is the cheapest setting; sample_fps=30.0 extracts
every frame of a 30 fps source. Extraction itself costs no credits - you pay
only to store the resulting images.
get_extraction
Read a job’s current state. Use this after extract_frames(wait=False), or the
CLI’s --no-wait.
Source: Video.get_extraction
| Arg | Type | Default | Notes |
|---|---|---|---|
job_id |
str |
required | Job id, as returned when the job was created. |
job = client.video.get_extraction(
job_id="7c1f4a90-2b3d-4e56-8f70-9a1b2c3d4e5f",
)
pictograph video status 7c1f4a90-2b3d-4e56-8f70-9a1b2c3d4e5f
curl -s "https://api.pictograph.io/api/v1/developer/video/extract-frames/7c1f4a90-2b3d-4e56-8f70-9a1b2c3d4e5f" \
-H "X-API-Key: $PICTOGRAPH_API_KEY"
Returns VideoExtractionJob
VideoExtractionJob · 9 fields
class VideoExtractionJob(BaseModel):
"""Snapshot of a frame-extraction job."""
job_id: str
status: Literal['processing', 'complete', 'failed']
progress: int = 0
frames_extracted: int = 0
total_frames: int = 0
error: str | None = None
directory_path: str | None = None
warning: str | None = None
image_ids: list[str] | None = None
Once status is complete, image_ids holds the registered frames - hand
them straight to auto_annotate.batch.
wait_for_extraction
Poll a job until it is complete or failed. No CLI equivalent - re-run
pictograph video status until it settles.
Source: Video.wait_for_extraction
| Arg | Type | Default | Notes |
|---|---|---|---|
job_id |
str |
required | Job id, as returned when the job was created. |
poll_interval |
float |
3.0 |
Seconds between polls. |
timeout |
float |
1800.0 |
Max seconds to wait before raising PollTimeoutError. The job keeps running. |
job = client.video.wait_for_extraction(
job_id="7c1f4a90-2b3d-4e56-8f70-9a1b2c3d4e5f",
timeout=600.0,
)
# extract-frames already waits; poll status for a detached job.
pictograph video status <job-id>
curl -s "https://api.pictograph.io/api/v1/developer/video/extract-frames/<job-id>" \
-H "X-API-Key: $PICTOGRAPH_API_KEY"
Returns VideoExtractionJob
VideoExtractionJob · 9 fields
class VideoExtractionJob(BaseModel):
"""Snapshot of a frame-extraction job."""
job_id: str
status: Literal['processing', 'complete', 'failed']
progress: int = 0
frames_extracted: int = 0
total_frames: int = 0
error: str | None = None
directory_path: str | None = None
warning: str | None = None
image_ids: list[str] | None = None
Common errors
| Status | Exception | Cause |
|---|---|---|
| 404 | NotFoundError |
gcs_path missing or not yours, or dataset_name invalid |
| 400 | ValidationError |
The file could not be parsed as a video |
| 408 | PollTimeoutError |
Long videos can exceed the default timeout |