Sign in Get started

Video

Upload videos, probe metadata, and extract frames as images into a dataset.

View as Markdown

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
Copied to clipboard