A virtual camera is a camera without a live video feed. You can upload a video file to a virtual camera, and the Media Server saves the video as if a real camera recorded it.
This article describes how to import video files to the archive of a virtual camera using the Media Server REST API.
What is a virtual camera?
A virtual camera allows you to store pre-recorded video files in the server archive. The Media Server handles the uploaded media just like a live camera stream, allowing you to view and manage it on the timeline.
Import process flow
The import process consists of the following REST API workflow:
Log in:
POST {server}/rest/v4/login/sessionswith{"username": "...", "password": "..."}to obtain a sessiontoken.Create a virtual camera:
POST {server}/rest/v4/devices/*/virtualwith{"name": "..."}to create a new device and retrieve itsid. The*wildcard represents the current server.Lock camera:
PATCH {server}/rest/v4/devices/{id}/virtual/lockwith{"ttlMs": ...}to acquire a locktokeninlockInfo.token.Create upload:
POST {server}/rest/v4/devices/{id}/virtual/uploadswith{"items": [{filename, sizeB, md5, startTimeMs, chunkSizeB}]}to declare file details and set the chunk size.Upload bytes:
PUT {server}/rest/v4/devices/{id}/virtual/uploads/{uploadId}?chunk=<n>using raw chunk bytes andContent-Type: application/octet-stream. Use the server-returneduploadId, or the file name if none is returned.Check status:
GET {server}/rest/v4/devices/{id}/virtual/uploads/{uploadId}to monitor progress. The import starts automatically once all chunks arrive.Release lock:
PATCH {server}/rest/v4/devices/{id}/virtual/releasewith{"token": ...}to free the camera lock. Always run this step, even if an error occurs.
Prerequisites
Before starting, ensure you have the following:
Python 3.8 or later installed on your computer.
-
The
requestslibrary installed. Install it by running:pip install requests A video file (
MP4,MKV, orAVI—H.264 is recommended).The URL of your Media Server (for example,
https://192.168.1.10:7001.Local administrator credentials for your server (do not use a cloud account).
Step 1: Log in to the server
Send your username and password to authenticate with the server and receive an authorization token:
import requests
server = "https://192.168.1.10:7001"
response = requests.post(
f"{server}/rest/v4/login/sessions",
json={"username": "admin", "password": "your-password"},
)
token = response.json()["token"]
headers = {"Authorization": f"Bearer {token}"}Step 2: Create a virtual camera
Create a new virtual camera on the server. The server responds with a unique device ID:
response = requests.post(
f"{server}/rest/v4/devices/*/virtual",
json={"name": "My Virtual Camera"},
headers=headers,
)
device_id = response.json()["id"]Step 3: Lock the virtual camera
Lock the camera to prevent concurrent uploads. This request returns a lock token that you must use to release the camera later:
response = requests.patch(
f"{server}/rest/v4/devices/{device_id}/virtual/lock",
json={"ttlMs": 300000}, # Lock for 5 minutes
headers=headers,
)
lock_token = response.json()["lockInfo"]["token"]Step 4: Register the upload payload
Send the file metadata, including the file name, size in bytes, and MD5 hash, to initialize the upload session:
import base64
import hashlib
import os
file_path = "my_video.mp4"
size_b = os.path.getsize(file_path)
with open(file_path, "rb") as f:
md5_b64 = base64.b64encode(hashlib.md5(f.read()).digest()).decode()
response = requests.post(
f"{server}/rest/v4/devices/{device_id}/virtual/uploads",
json={
"items": [
{
"filename": os.path.basename(file_path),
"sizeB": size_b,
"md5": md5_b64,
"startTimeMs": 0, # 0 sets start time to the current time
"chunkSizeB": 1024 * 1024, # 1 MB per chunk
}
]
},
headers=headers,
)
upload_id = response.json()["items"][0]["uploadId"]
NOTE: Set startTimeMs to an epoch timestamp in milliseconds if you want the video to appear at a specific time on the timeline. Set startTimeMs to 0 or omit it to start from the current time. |
Step 5: Upload the file chunks
Upload the video file sequentially in fixed-size chunks:
chunk_size = 1024 * 1024 # 1 MB, must match Step 4
index = 0
with open(file_path, "rb") as f:
while True:
chunk = f.read(chunk_size)
if not chunk:
break
requests.put(
f"{server}/rest/v4/devices/{device_id}/virtual/uploads/{upload_id}",
params={"chunk": index},
data=chunk,
headers={**headers, "Content-Type": "application/octet-stream"},
)
index += 1The server automatically begins importing the video once it receives every chunk. You do not need to call an additional endpoint to trigger consumption.
Step 6: Check the upload status
Monitor the status of your upload using a GET request:
response = requests.get(
f"{server}/rest/v4/devices/{device_id}/virtual/uploads/{upload_id}",
headers=headers,
)
print(response.json())Step 7: Release the lock
Release the lock after uploading to free the camera for future operations.
In production code, wrap Steps 3 through 7 in a try...finally block to ensure the lock releases even if an error occurs:
requests.patch(
f"{server}/rest/v4/devices/{device_id}/virtual/release",
json={"token": lock_token},
headers=headers,
)Troubleshooting - Video missing from the timeline
If your video does not appear on the timeline after uploading, the server might fail to determine the video duration automatically. Pass an explicit durationMs value in milliseconds during Step 4:
"items": [{
"filename": os.path.basename(file_path),
"sizeB": size_b,
"md5": md5_b64,
"startTimeMs": 0,
"durationMs": 60000, # 60 seconds
"chunkSizeB": 1024 * 1024,
}]Code samples
For a complete, production-ready script featuring full error handling and command-line arguments, visit the Nx Open GitHub repository.
If you are not using Python, refer to the JavaScript, Node.js, and browser-based examples in the Nx Open GitHub API Samples directory.
Comments
0 comments
Article is closed for comments.