Dashboard Part 6 Video Analysis & Spatial Intelligence
🎥 Part 6 · Topic 3 of 3

Video Analysis & Spatial Intelligence

Two services that analyse moving pictures rather than still images. Azure AI Video Indexer extracts rich insights from uploaded video files — transcripts, faces, labels, scenes, keywords, and topics — via a REST API. Spatial Analysis uses live camera feeds to detect and track people movement in physical spaces in real time, running as an IoT Edge container on GPU hardware.

🌐 REST API — every endpoint explained ⏱️ ~40 min 🎯 High exam weight — account types & operations are trap areas

Key terms — defined before you touch the API

Azure AI Video Indexer

A cloud service that analyses uploaded video files and extracts structured insights — transcripts, OCR text, face identities, objects, scenes, keywords, topics, emotions, and more — without you writing any ML code. You upload a video, it indexes it (takes minutes to hours depending on length), and you query the results via REST API or the portal at www.videoindexer.ai. Ideal for media libraries, content search, compliance monitoring, and meeting analysis.

Trial Account (Video Indexer)

A free Video Indexer account that does NOT require an Azure subscription. Free, but limited: up to 10 hours of indexing for web uploads and up to 40 hours for API uploads. Content is stored and managed by Microsoft (not your own storage). Cannot be connected to custom Azure resources. Cannot use custom person, brand, or language models. Suitable for exploration and development — not for production workloads.

ARM-Connected Account (Video Indexer)

A Video Indexer account backed by an Azure subscription (ARM = Azure Resource Manager). Requires creating an Azure AI Video Indexer resource in your subscription. Benefits: uses your own Azure storage, no hour limits, can use custom models (person, brand, language), enterprise SLA, content stays in your subscription and region. Required for production workloads or when data residency matters.

Video Index / Insights

The structured output produced by Video Indexer after processing a video. Contains all extracted insights in JSON format — transcripts with timestamps, detected faces, label tags for frames, scene/shot boundaries, keywords, topics, OCR text, named entities, emotions, and sentiment. Retrieved via GET /Videos/{videoId}/Index. The "index" is what makes the video searchable and queryable.

Transcript

Time-coded speech-to-text output extracted from the audio track of the video. Includes speaker diarisation (who said what), timestamps for each spoken segment, and confidence scores. The transcript enables full-text search across the video and powers the auto-generated captions. Video Indexer uses the same speech model as Azure AI Speech — multi-language support included.

Scene / Shot / Keyframe

Three levels of video segmentation: Scene = a group of consecutive shots with related visual/narrative content (like a chapter). Shot = a continuous camera take — no cuts. Keyframe = the single representative frame selected from a shot. Video Indexer detects all three automatically. Keyframes become the thumbnails for scenes and shots. These are used to build a visual table of contents for the video.

Named Entities (Video Indexer)

People, locations, organisations, and brands extracted from the video transcript text using NLP — not from visual face recognition. Example: if a speaker says "Microsoft announced today…" the transcript NER detects "Microsoft" as an organisation. Separate from face detection (which identifies faces from video frames). Named entity extraction requires no Limited Access approval — it's purely text-based.

Face Detection vs Face Identification (Video Indexer)

Face Detection: finding that human faces exist in video frames — available to all accounts, does not identify who. Face Identification: matching detected faces to known identities (e.g. public figures or your own custom person model) — requires Limited Access approval from Microsoft. On the exam: if a scenario asks to identify WHO appears in the video, that requires Limited Access. If it just asks to detect face presence, no approval needed.

Access Token (Video Indexer)

A short-lived JWT used to authenticate Video Indexer API calls. Two scopes: Account-level token — lets you manage the whole account (list videos, upload, delete, configure). Video-level token — scoped to one video (get insights, get thumbnails). Obtain from the Auth endpoint: GET /Auth/{location}/Accounts/{accountId}/AccessToken. Tokens expire, so your app must refresh them. A Management API key (from your ARM resource) is used to get the access token.

Video Indexer Widget

An embeddable HTML widget that Video Indexer provides for adding video intelligence to web apps without writing a custom UI. Two widgets: Player widget — video player with transcript highlights and scene navigation. Cognitive Insights widget — side panel showing faces, labels, keywords, and topics with timeline bars. Both are generated as iframe URLs from the API and embedded directly in web pages.

Spatial Analysis

A feature of Azure AI Vision that analyses live or recorded camera feeds in real time to detect people, count them, track their movement across defined lines and zones, and measure distances between them. Runs as an IoT Edge container — deployed to edge hardware (not cloud API calls). Use cases: occupancy monitoring, people counting, social distancing, access control, retail analytics. Requires Limited Access approval from Microsoft.

IoT Edge Container (Spatial Analysis)

Spatial Analysis is delivered as a Docker container that runs as an Azure IoT Edge module on edge hardware — not as a cloud REST API. Requires a GPU-enabled device (NVIDIA T4, NVIDIA Jetson, or Azure Stack Edge Pro with GPU). Why edge? Real-time camera feed analysis needs low latency — sending live video to the cloud is impractical. The container processes video locally and emits JSON events to Azure IoT Hub or local IoT Edge message routing.

Spatial Analysis Operation

A specific analysis task configured in the Spatial Analysis container. Each operation monitors a camera feed for a defined behaviour: counting people in a zone, detecting line crossings, measuring distances, or detecting zone entry/exit. Operations are configured in a JSON deployment manifest alongside camera RTSP stream URLs and zone/line coordinates (defined as polygons or line segments in image coordinates). Multiple operations can run simultaneously on different cameras.

RTSP Stream

Real Time Streaming Protocol — the URL format used by IP cameras to provide a live video feed. Spatial Analysis connects to cameras via their RTSP URLs (e.g. rtsp://192.168.1.10:554/stream). The container pulls the live frames from the camera, processes them locally, and emits detection events. You can also use recorded video files for testing (simulated camera mode).

When to use Video Indexer vs Spatial Analysis

📚
Analogy — Archivist vs Security Guard

Video Indexer is the archivist: you give it a video file from the archive, it reads it carefully, extracts all the meaning (who spoke, what was shown, what topics were discussed), and files a structured report you can query later. Offline, batch-oriented, rich insights.

Spatial Analysis is the security guard watching live cameras: it monitors the feed in real time and immediately flags events ("someone crossed the entrance line", "zone capacity exceeded"). No deep content analysis — just real-time people movement detection.

If the scenario says "analyse uploaded video files for rich content insights" → Video Indexer. If the scenario says "real-time people counting from live camera" → Spatial Analysis.

DimensionAzure AI Video IndexerSpatial Analysis
InputUploaded video files (MP4, MOV, etc.)Live camera RTSP streams (or test video files)
ProcessingBatch / offline (takes minutes to hours)Real time (millisecond latency)
DeploymentCloud REST API — no hardware neededIoT Edge container — GPU hardware required
OutputJSON insights index (transcript, faces, labels…)JSON events streamed to IoT Hub
Limited Access?Only for face identificationWhole service requires Limited Access
Primary use casesMedia search, meeting analysis, content moderation, complianceOccupancy monitoring, people counting, social distancing, access control

Trial vs ARM-Connected — the most commonly tested distinction

🎯
Exam Tip — Account type scenario triggers

"The company processes thousands of hours of video monthly and must keep data in their own Azure subscription."
ARM-Connected account — data must stay in their subscription + volume requires unlimited indexing.

"A developer wants to quickly prototype a video search feature before committing to a paid service."
Trial account — no subscription needed, good for prototyping within hour limits.

"The company needs to train a custom person model to identify their executives in recorded meetings."
ARM-Connected account — custom models are only available on paid accounts. Also requires Limited Access for face identification.

What Video Indexer extracts from your video

All insights are returned in the JSON index. Knowing what each insight is and what limitations apply is exam-tested.

transcript

Time-coded speech-to-text with speaker diarisation. Supports multiple languages auto-detected. Powers full-text search. Each segment has a start/end time and speaker ID. Confidence scores per word. No Limited Access needed.

ocr

Text visible in video frames — e.g. slide text in a recorded presentation, on-screen captions, signs. Each OCR result has the frame time and bounding box coordinates. Complements the transcript (spoken words) with written words that appear on screen.

faces Limited Access

Detected faces across frames with timeline showing when each face appears. Face detection (finding faces) is available to all. Face identification (matching faces to known people) requires Microsoft approval. Custom person models let you identify your own people (ARM account + Limited Access).

labels

Objects and scenes detected in video frames — e.g. "person", "outdoor", "computer", "dog". Each label has a timeline showing the frames it appears in and a confidence score. Similar to image tags from Azure AI Vision — applied at video frame level.

keywords

Key terms extracted from the transcript text using NLP. Represent the most discussed topics in the video. Each keyword has appearance timestamps. Useful for building tag clouds, search indexes, and content summaries. Derived from speech, not visual content.

topics

High-level subject categories mapped to the IPTC news codes taxonomy (e.g. "Technology > Software", "Sports > Football"). Inferred from the transcript. Each topic has a confidence score and appears for a time range in the video. Useful for content classification and routing.

scenes / shots / keyframes

Three-level segmentation. Scene = narrative chapter (group of shots). Shot = uninterrupted camera take. Keyframe = representative single frame per shot. Keyframe thumbnails can be retrieved via the Thumbnails API. Used to build visual navigation for video content.

namedEntities

People, locations, organisations, and brands extracted from transcript text using NER. Example: "Satya Nadella" → Person, "Seattle" → Location, "Azure" → Brand. These come from what was said, not from face recognition. No Limited Access required.

emotions

Emotions detected from both the audio track (voice tone analysis) and visual face expressions. Categories: happiness, sadness, anger, fear, disgust, surprise, neutral. Each emotion has a time range and score. Face-based emotion detection also requires Limited Access.

sentiment

Positive, negative, or neutral sentiment per transcript segment — derived from the spoken text using NLP (same approach as Text Analytics sentiment). Each segment has a sentiment score and time range. Shows how the tone of speech changes throughout the video.

audioEffects

Non-speech audio events detected: silence, crowd applause, music, laughter, crying, gun shot, alarm. Each effect has a time range. Useful for news/media workflows — e.g. detecting crowd reactions in sports broadcasts, detecting silence periods in podcast editing.

brands

Brand names detected from transcript text AND OCR text (brands visible on screen). Uses a built-in Bing brand knowledge base. Custom brand models (ARM account) let you add your own brand list. Useful for brand mention monitoring in media content.

Upload, index, and query — the API workflow

1
Get an Access Token

All API calls need a JWT access token. Get one from the Auth endpoint using your Management API key (from the Azure portal resource keys). Account-level token: GET https://api.videoindexer.ai/Auth/{location}/Accounts/{accountId}/AccessToken?allowEdit=true — allows management operations. Video-level token: GET https://api.videoindexer.ai/Auth/{location}/Accounts/{accountId}/Videos/{videoId}/AccessToken — scoped to one video. Tokens expire, so refresh before they do.

2
Upload a video

POST https://api.videoindexer.ai/{location}/Accounts/{accountId}/Videos?name=MyVideo&accessToken={token} — multipart form body with the video file. Or pass a videoUrl query parameter to index a video from a public URL (no file upload needed). Returns a videoId. Indexing starts immediately but is asynchronous — the video is not searchable until indexing completes.

3
Poll for indexing completion

GET https://api.videoindexer.ai/{location}/Accounts/{accountId}/Videos/{videoId}/Index?accessToken={token} — returns the current indexing state. Check the state field: "Uploaded" → uploading, "Processing" → indexing in progress, "Processed" → complete, "Failed" → error. Poll until state is "Processed" before querying insights. For long videos this may take several minutes.

4
Get full insights (video index)

GET /Videos/{videoId}/Index — returns the complete JSON index with all insights: transcript, faces, labels, keywords, topics, scenes, shots, keyframes, OCR, named entities, emotions, sentiment, audio effects. Very large for long videos. Use GET /Videos/{videoId}/SummarizedInsights for a shorter summary (aggregated counts and top items without per-second detail).

5
Search across videos

GET /Videos/Search?query=azure+AI&accessToken={token} — full-text search across all indexed videos in the account. Searches transcript, OCR, labels, topics, and keywords simultaneously. Returns matching videos with the specific time ranges where the query term appears. This is the core use case for media asset management — find where "Azure" was mentioned across 10,000 hours of video.

6
Get thumbnails

GET /Videos/{videoId}/Thumbnails/{thumbnailId}?accessToken={token} — retrieve the keyframe image for a scene or shot as a JPEG. Thumbnail IDs are found in the Index response for each scene and shot. Use to build visual navigation UIs — show users a grid of keyframe images representing each scene in the video, clickable to jump to that point.

7
Embed widgets

Video Indexer provides two embeddable HTML iframe widgets. Player widget URL: GET /Videos/{videoId}/PlayerWidget?accessToken={token} — returns an iframe URL for an interactive player with transcript highlights. Insights widget URL: GET /Videos/{videoId}/InsightsWidget?accessToken={token} — returns an iframe URL for the side panel showing faces, labels, topics with timeline bars. Embed both iframes in your web app without building a custom insights UI.

Key API endpoint reference

OperationMethod + PathNotes
Get access tokenGET /Auth/{loc}/Accounts/{id}/AccessTokenPass Management API key. Returns JWT.
Upload videoPOST /Videos?name=…&accessToken=…File upload or videoUrl param.
Get full indexGET /Videos/{videoId}/IndexComplete insights JSON. Check state field.
Get summarized insightsGET /Videos/{videoId}/SummarizedInsightsAggregated summary — smaller response.
Search videosGET /Videos/Search?query=…Full-text search across all indexed videos.
Get thumbnailGET /Videos/{videoId}/Thumbnails/{thumbnailId}Returns keyframe JPEG. ID from index response.
List videosGET /VideosAll videos in the account.
Delete videoDELETE /Videos/{videoId}Removes video and its index.
Player widgetGET /Videos/{videoId}/PlayerWidgetReturns embeddable iframe URL.
Insights widgetGET /Videos/{videoId}/InsightsWidgetReturns embeddable iframe URL.

Real-time people detection in physical spaces

🔒
Limited Access — entire service

Spatial Analysis requires Microsoft approval before you can use it. Apply via the Azure AI Limited Access form. Unlike Video Indexer (where only face identification requires approval), all Spatial Analysis operations require Limited Access. The exam tests this distinction — if a scenario requires using Spatial Analysis, add "requires Limited Access approval" to any answer about requirements.

Deployment architecture

1. GPU edge hardware → runs the Spatial Analysis IoT Edge container (NVIDIA GPU required — e.g. Jetson AGX Xavier, T4-equipped server, Azure Stack Edge Pro)
2. IoT Edge runtime → installed on the edge device, manages the container lifecycle and message routing
3. Spatial Analysis container → pulls RTSP camera streams, runs the CV model locally on the GPU, emits JSON detection events
4. IoT Hub → receives the JSON events from the edge device via the IoT Edge messaging pipeline
5. Your application → reads events from IoT Hub to trigger actions (alerts, dashboards, database writes)

Spatial Analysis operations — the five you must know

cognitiveservices.vision.spatialanalysis-personcount

What: Count the total number of people visible in the entire camera frame at a given moment. Does not require a defined zone — counts all people in view.

Use case: Total occupancy monitoring for an entire room or area. "How many people are currently in the lobby?" Event output triggers on entering/exiting frame.

cognitiveservices.vision.spatialanalysis-personcountingzone

What: Count people within a specific polygon zone drawn on the camera view. You define the zone as a polygon in image coordinates. Continuously reports the current count for that zone.

Use case: Zone capacity enforcement. "Alert if more than 10 people enter the server room zone." "Count people in the checkout queue area." Events fire when count changes.

cognitiveservices.vision.spatialanalysis-personcrossingline

What: Detect when a person crosses a defined line drawn on the camera view. Tracks crossing direction (A→B or B→A). Events fire at each crossing.

Use case: Entrance/exit counting (count people entering vs leaving a store). Access control alerts (did someone cross the security perimeter?). Direction-aware foot traffic analysis.

cognitiveservices.vision.spatialanalysis-personcrossingpolygon

What: Detect when a person enters or exits a polygon zone. Distinguishes entry events from exit events. Tracks how long people stay in the zone (dwell time).

Use case: Zone entry/exit tracking with dwell time — "How long did customers spend in the display area?" Restricted area violations. Queue length and wait time monitoring.

cognitiveservices.vision.spatialanalysis-persondistance

What: Measure the distance between people detected in the camera view. Requires a calibration step to map image pixels to real-world metres. Fires events when people are closer than a configured threshold.

Use case: Social distancing monitoring (alert when people are closer than 1.5 metres). Safety compliance in workplaces or healthcare settings. Event output includes the actual measured distance in metres.

Operation selection — exam decision guide

ScenarioOperationWhy
Count total people visible in a camera viewpersoncountNo zone needed — whole frame count
Alert when conference room exceeds capacitypersoncountingzoneCount people within a defined room zone
Count how many people entered vs left a store entrancepersoncrossinglineDirectional line crossing with entry/exit tracking
Alert if anyone enters a restricted server room zonepersoncrossingpolygonZone entry/exit event detection
Ensure employees maintain safe distances on factory floorpersondistanceMeasures real-world distance between people
Analyse how long customers dwell in a product display areapersoncrossingpolygonTracks zone entry/exit timestamps → dwell time

Deployment manifest — how operations are configured

Operations are configured in an IoT Edge deployment manifest JSON file. Each operation specifies the camera RTSP URL, the operation type, and the zone/line geometry as polygon coordinates.

// Simplified IoT Edge deployment manifest for Spatial Analysis
// Full manifest also includes module image, resources, routes to IoT Hub
{
  "spatialanalysis": {
    "operationId": "cognitiveservices.vision.spatialanalysis-personcrossingline",
    "version": 1,
    "enabled": true,
    "parameters": {
      // RTSP stream URL from the IP camera
      "VIDEO_URL": "rtsp://192.168.1.10:554/stream1",
      "VIDEO_SOURCE_ID": "entrance-camera-01",
      // Line defined as two points in image coordinates (x,y as fraction of frame)
      "DETECTOR_NODE_CONFIG": "{ \"lines\": [{ \"name\": \"store_entrance\", \"line\": { \"start\": {\"x\": 0.1, \"y\": 0.5}, \"end\": {\"x\": 0.9, \"y\": 0.5}}}] }",
      "SPACEANALYTICS_CONFIG": "{ \"zones\": [] }"
    }
  }
}
📦
Privacy by design — Spatial Analysis does NOT store identities

Spatial Analysis tracks people as anonymous bounding boxes — it does NOT identify who people are. No face recognition, no biometric data stored. Events contain: timestamp, count, crossing direction, distance — but no identity information. If you need to identify specific people (e.g. recognise employees), that requires combining Spatial Analysis with the Face API, which adds a separate Limited Access requirement.

Upload, index, and query a video — every line explained

Video Indexer has no dedicated Python SDK — you use the requests library directly against the REST API. Install: pip install requests

import requests
import time
import os

# ── Connection details ─────────────────────────────────────────────────
# Find these in the Azure portal: your Video Indexer resource → Keys and Endpoint
# Also available at videoindexer.ai → Account Settings
ACCOUNT_ID       = os.environ.get("VIDEOINDEXER_ACCOUNT_ID")     # GUID format
ACCOUNT_LOCATION = os.environ.get("VIDEOINDEXER_LOCATION")       # e.g. "trial" or "eastus"
API_KEY          = os.environ.get("VIDEOINDEXER_API_KEY")         # Management API key (subscription key)

# Base URL for the Video Indexer API
BASE_URL = f"https://api.videoindexer.ai/{ACCOUNT_LOCATION}/Accounts/{ACCOUNT_ID}"
AUTH_URL = f"https://api.videoindexer.ai/Auth/{ACCOUNT_LOCATION}/Accounts/{ACCOUNT_ID}"


# ══════════════════════════════════════════════════════════════════════
# PART 1 — AUTHENTICATION: get an account-level access token
# ══════════════════════════════════════════════════════════════════════

def get_access_token(allow_edit: bool = True) -> str:
    """Get a JWT access token. allow_edit=True for management operations."""
    response = requests.get(
        f"{AUTH_URL}/AccessToken",
        params={"allowEdit": str(allow_edit).lower()},
        headers={"Ocp-Apim-Subscription-Key": API_KEY}
        # "Ocp-Apim-Subscription-Key" is the header name for the Management API key
        # This is different from the access token — you pass the API key HERE
        # to GET the short-lived access token that you use for all other calls
    )
    response.raise_for_status()
    # Response body is a JSON string — the token itself (with surrounding quotes)
    # .strip('"') removes those surrounding quotes
    return response.json()  # Returns the JWT string

access_token = get_access_token(allow_edit=True)
print("Access token obtained.")


# ══════════════════════════════════════════════════════════════════════
# PART 2 — UPLOAD: Submit a video for indexing
# ══════════════════════════════════════════════════════════════════════

# Option A — Upload from a URL (video is publicly accessible)
upload_response = requests.post(
    f"{BASE_URL}/Videos",
    params={
        "name"       : "quarterly-review-q1",   # Display name for the video
        "videoUrl"   : "https://example.com/videos/q1-review.mp4",  # Public URL
        "language"   : "en-US",               # Hint for speech-to-text; auto-detect if omitted
        "indexingPreset": "Default",          # "Default" (all insights) or "AudioOnly" or "VideoOnly"
        "accessToken": access_token           # Account-level token in query param (not header)
    }
)
upload_response.raise_for_status()
video = upload_response.json()
video_id = video["id"]             # GUID — needed for all subsequent calls on this video
print(f"Video submitted. ID: {video_id}  State: {video['state']}")

# Option B — Upload a local file (multipart POST)
# with open("meeting.mp4", "rb") as f:
#     upload_response = requests.post(
#         f"{BASE_URL}/Videos",
#         params={"name": "meeting", "accessToken": access_token},
#         files={"file": ("meeting.mp4", f, "video/mp4")}  # multipart file upload
#     )


# ══════════════════════════════════════════════════════════════════════
# PART 3 — POLL: Wait for indexing to complete
# ══════════════════════════════════════════════════════════════════════

def wait_for_indexing(video_id: str, token: str, poll_interval: int = 30) -> dict:
    """Poll the index endpoint until state is 'Processed' or 'Failed'."""
    while True:
        response = requests.get(
            f"{BASE_URL}/Videos/{video_id}/Index",
            params={"accessToken": token}
        )
        response.raise_for_status()
        index = response.json()
        state = index["state"]  # "Uploaded" | "Processing" | "Processed" | "Failed"

        if state == "Processed":
            print("Indexing complete!")
            return index  # The full index JSON — all insights inside
        elif state == "Failed":
            raise RuntimeError(f"Indexing failed: {index.get('failureMessage', 'unknown error')}")
        else:
            # Still processing — print progress if available
            progress = index.get("processingProgress", "unknown")
            print(f"  State: {state}  Progress: {progress}  (checking again in {poll_interval}s)")
            time.sleep(poll_interval)

index_data = wait_for_indexing(video_id, access_token)


# ══════════════════════════════════════════════════════════════════════
# PART 4 — EXTRACT INSIGHTS: Parse the index JSON
# ══════════════════════════════════════════════════════════════════════

# The full index is a large JSON object — insights are inside "videos[0].insights"
# (A single upload can contain multiple video tracks — usually just one)
insights = index_data["videos"][0]["insights"]

# ── Transcript ─────────────────────────────────────────────────────────
print("\n=== TRANSCRIPT (first 3 segments) ===")
for segment in insights.get("transcript", [])[:3]:
    # Each segment: text, confidence, speakerId, and instances (start/end times)
    text  = segment["text"]
    conf  = segment["confidence"]
    start = segment["instances"][0]["start"]  # e.g. "0:00:02.5"
    end   = segment["instances"][0]["end"]
    print(f"  [{start} → {end}] ({conf:.0%})  {text}")

# ── Keywords ───────────────────────────────────────────────────────────
print("\n=== KEYWORDS ===")
for kw in insights.get("keywords", [])[:5]:
    # Each keyword: text, confidence, instances (list of times it appeared)
    print(f"  {kw['text']}  ({kw['confidence']:.0%})")

# ── Topics ─────────────────────────────────────────────────────────────
print("\n=== TOPICS ===")
for topic in insights.get("topics", []):
    # Each topic: name (the category), confidence, iptcName (IPTC taxonomy label)
    print(f"  {topic['name']}  (IPTC: {topic.get('iaptcName', 'N/A')})  {topic['confidence']:.0%}")

# ── Scenes and Shots ───────────────────────────────────────────────────
print("\n=== SCENES ===")
for scene in insights.get("scenes", []):
    scene_id = scene["id"]
    start    = scene["instances"][0]["start"]
    end      = scene["instances"][0]["end"]
    print(f"  Scene {scene_id}: {start} → {end}")

# ── Named Entities ─────────────────────────────────────────────────────
print("\n=== NAMED ENTITIES ===")
for entity in insights.get("namedEntities", []):
    # type: "Person", "Location", "Organization", "Brand"
    print(f"  [{entity['type']}] {entity['name']}  ({entity['confidence']:.0%})")

# ── Faces (if Limited Access granted) ─────────────────────────────────
print("\n=== FACES ===")
for face in insights.get("faces", []):
    # name: identified person name (or "Unknown" if unrecognised)
    # thumbnailId: use to retrieve the face crop image via the Thumbnails endpoint
    print(f"  {face['name']}  confidence={face['confidence']:.0%}  seenFor={face['seenDuration']:.1f}s")


# ══════════════════════════════════════════════════════════════════════
# PART 5 — SEARCH: Find videos containing a term
# ══════════════════════════════════════════════════════════════════════

search_response = requests.get(
    f"{BASE_URL}/Videos/Search",
    params={
        "query"      : "quarterly revenue",     # Search across transcript, OCR, labels, topics
        "accessToken": access_token,
        "pageSize"   : 10,                    # Number of results per page
        "skip"       : 0                       # Pagination offset
    }
)
search_results = search_response.json()

print("\n=== SEARCH RESULTS ===")
for result in search_results.get("results", []):
    vid_name = result["name"]
    vid_id   = result["id"]
    # matchedText shows WHERE in the video the query was found (with context)
    print(f"  {vid_name} ({vid_id})")
    for match in result.get("matchedText", [])[:2]:
        print(f"    → at {match.get('start', '?')}: \"{match.get('text', '')}\"")


# ══════════════════════════════════════════════════════════════════════
# PART 6 — WIDGETS: Get embeddable iframe URLs
# ══════════════════════════════════════════════════════════════════════

# Get a video-level token (scoped to one video) for embedding widgets
video_token_response = requests.get(
    f"https://api.videoindexer.ai/Auth/{ACCOUNT_LOCATION}/Accounts/{ACCOUNT_ID}/Videos/{video_id}/AccessToken",
    params={"allowEdit": "false"},         # Read-only token for embedding in a web page
    headers={"Ocp-Apim-Subscription-Key": API_KEY}
)
video_token = video_token_response.json()

# Player widget — embeddable video player with transcript highlighting
player_widget = requests.get(
    f"{BASE_URL}/Videos/{video_id}/PlayerWidget",
    params={"accessToken": video_token}
)
print(f"\nPlayer widget URL: {player_widget.json()}")
# Embed in HTML: <iframe src="{player_widget.json()}" ...></iframe>

# Insights widget — side panel with faces, labels, topics timeline
insights_widget = requests.get(
    f"{BASE_URL}/Videos/{video_id}/InsightsWidget",
    params={"accessToken": video_token, "widgetType": "Keywords"}
    # widgetType: "Keywords", "Faces", "Sentiments", "Emotions", "Topics", "Labels"
    # Omit widgetType for all insights combined
)
print(f"Insights widget URL: {insights_widget.json()}")

Video Analysis — what the exam tests

⚠️
Top 7 Tested Facts

1. Trial vs ARM account — three trigger keywords: "own subscription/storage" → ARM. "custom person/brand models" → ARM. "prototype/no subscription" → Trial. Any production scenario → ARM.

2. Face identification requires Limited Access; face detection does not — Knowing someone is IN the video vs knowing WHO they are. Detection = location + count, no identity. Identification = who it is. Only identification requires approval.

3. Spatial Analysis = entire service is Limited Access — Not just one feature. The whole thing needs Microsoft approval.

4. Spatial Analysis runs on edge hardware (IoT Edge container), not as a cloud API call — Real-time camera analysis needs GPU hardware at the edge. This is the fundamental deployment difference from Video Indexer.

5. Operation selection for Spatial Analysis: zone capacity → personcountingzone. Entry/exit counting → personcrossingline (directional). Zone entry/exit events → personcrossingpolygon. Distance monitoring → persondistance.

6. Named entities in Video Indexer come from transcript text (NLP), not face recognition — If you see "Microsoft" mentioned in the audio, namedEntities detects it. This does NOT require Limited Access.

7. Video Indexer is async — always poll for completion — Upload returns immediately, state starts as "Processing". Must poll GET /Videos/{videoId}/Index until state = "Processed" before reading insights.

Test your understanding

Topic 3 — Video Analysis & Spatial Intelligence 1 / 5
Scenario: A media company has a library of 50,000 archived news broadcast recordings stored in Azure Blob Storage. They want to build a search tool that lets journalists find specific clips by searching for topics, keywords, and the names of people mentioned in the broadcasts. The company is under strict data residency requirements — all data must stay within their Azure subscription in a specific region.

Which Video Indexer account type should they use, and why?

💡 Explanation

B is correct. Two requirements force the ARM-Connected account: (1) Data residency — Trial accounts store content under Microsoft's control, not in the customer's own subscription. ARM-Connected accounts store data in the customer's own Azure Storage within their chosen region, satisfying data residency requirements. (2) Scale — 50,000 videos far exceeds the Trial account limits (10 hours web / 40 hours API). The ARM-Connected account also enables custom models if needed (e.g. recognising specific named individuals from their person model).

Scenario: A broadcasting company indexes their video library with Azure AI Video Indexer. They want to automatically detect (1) which faces appear in each broadcast, (2) which people are mentioned by name in the commentary, and (3) which well-known public figures can be identified in the footage.

Which of these three capabilities requires Limited Access approval from Microsoft?

💡 Explanation

C is correct. The distinction is critical: Face Detection (finding where faces are in frames — capability 1) is available to all accounts with no approval needed. Named entity extraction (capability 2) is pure NLP on transcript text — finding that the commentator said "Barack Obama" — no face recognition involved, no Limited Access needed. Face Identification (capability 3 — matching detected faces to a known person's identity) requires Limited Access approval because it involves biometric recognition of individuals. This is the same Limited Access policy that applies to the Azure AI Vision Face API identification features.

Scenario: A hospital wants to monitor patient waiting areas using existing IP cameras. They need to: (a) alert staff when the waiting room has more than 15 people, and (b) measure whether patients are maintaining safe distances from each other. The hospital is concerned about patient privacy — no patient identities should be recorded.

Which Spatial Analysis operations should be configured to meet these requirements?

💡 Explanation

C is correct. personcountingzone is designed to count people within a defined zone (the waiting room) and can fire events when the count exceeds a threshold — exactly requirement (a). persondistance measures the physical distance between detected people and fires alerts when that distance drops below a threshold — exactly requirement (b). The privacy concern is satisfied by default: Spatial Analysis never records identities — people are tracked as anonymous bounding boxes only. Note: the hospital still needs Limited Access approval for the whole Spatial Analysis service before deploying either operation.

A developer uploads a 2-hour video to Azure AI Video Indexer and immediately calls GET /Videos/{videoId}/Index. The response returns a JSON object where the state field contains "Processing". What does this mean and what should the developer do?

💡 Explanation

D is correct. Video Indexer indexing is asynchronous. After uploading, the state progresses: "Uploaded""Processing""Processed" (or "Failed" on error). A 2-hour video will be in the "Processing" state for several minutes. The developer must implement a polling loop — repeatedly call GET /Videos/{videoId}/Index with a delay between calls, and only proceed to read the insights when state = "Processed". Reading insights while the state is "Processing" will return incomplete or empty results. This is the same async POST-then-poll pattern used throughout Azure AI services (e.g. Document Intelligence, Speech batch transcription).

Scenario: A retail company wants to add real-time footfall analytics to their stores using existing ceiling-mounted IP cameras. The solution must count people entering and exiting each store entrance, alert store managers when specific areas exceed capacity, and operate with sub-second latency even if the internet connection is unreliable.

What are the two key infrastructure requirements that must be in place before Spatial Analysis can be deployed in this scenario?

💡 Explanation

C is correct. Two non-negotiable requirements: (1) GPU-enabled edge hardware with IoT Edge runtime — Spatial Analysis is NOT a cloud REST API. It is an IoT Edge container that runs locally on hardware (e.g. a device with an NVIDIA GPU). This is why it can handle real-time camera feeds with sub-second latency and remain functional even with unreliable internet. (2) Limited Access approval — unlike Video Indexer where only face identification requires approval, ALL Spatial Analysis features require Microsoft approval before use. The scenario with sub-second latency and unreliable connectivity is a clear signal that the solution must run on the edge (eliminating A). Spatial Analysis is completely separate from Video Indexer (eliminating B) and Custom Vision (eliminating D).

Mark this topic as complete