diff --git a/.github/release.yml b/.github/release.yml
new file mode 100644
index 0000000..e798472
--- /dev/null
+++ b/.github/release.yml
@@ -0,0 +1,14 @@
+changelog:
+ categories:
+ - title: New Features
+ labels: [enhancement, feature]
+ - title: Bug Fixes
+ labels: [bug, fix]
+ - title: Improvements
+ labels: [improvement, refactor]
+ - title: Documentation
+ labels: [documentation, docs]
+ - title: Other Changes
+ labels: ["*"]
+ exclude:
+ labels: [skip-changelog]
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yml
similarity index 51%
rename from .github/workflows/ci.yaml
rename to .github/workflows/ci.yml
index 8939c13..6121388 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yml
@@ -1,72 +1,92 @@
-name: Continuous Integration Workflow
-# Trigger the workflow on push or pull request, on main branch
+name: CI
on:
push:
- branches: [ main ]
+ branches: [ main, release-* ]
pull_request:
branches: [ main ]
+ workflow_call:
+
+permissions: {}
-# Define environment variables
-env:
+concurrency:
+ group: ci-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+
+env:
PYTHON_VERSION: 3.9
- APP_DIR: ./
jobs:
lint:
runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ timeout-minutes: 10
steps:
- - uses: actions/checkout@v2
- - name: Set up Python 3
- uses: actions/setup-python@v2
+ - uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
-
+
- name: Install dependencies
- run: |
+ run: |
python -m pip install --upgrade pip
pip install -r requirements-dev.txt
-
+
- name: Run linter
- run: |
- ruff check ${{ env.APP_DIR }}
+ run: ruff check --output-format github ./
test:
runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ timeout-minutes: 15
steps:
- - uses: actions/checkout@v2
- - name: Set up Python 3
- uses: actions/setup-python@v2
+ - uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
-
+
- name: Install dependencies
- run: |
+ run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-dev.txt
- name: Run tests
- run: |
- python -m pytest tests/
+ run: python -m pytest tests/
build:
runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ timeout-minutes: 10
steps:
- - uses: actions/checkout@v2
- - name: Set up Python 3
- uses: actions/setup-python@v2
+ - uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
-
+
- name: Install dependencies
- run: |
+ run: |
python -m pip install --upgrade pip
pip install setuptools==68.0.0
pip install -r requirements.txt
pip install -r requirements-dev.txt
- name: Build
- run: |
+ run: |
python setup.py sdist bdist_wheel
twine check dist/*
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 0000000..c7f74b5
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,37 @@
+name: CodeQL Analysis
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+ schedule:
+ - cron: "0 6 * * 1" # Every Monday at 6am UTC
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ security-events: write
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [python]
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v3
+ with:
+ languages: ${{ matrix.language }}
+
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v3
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v3
+ with:
+ category: "/language:${{ matrix.language }}"
diff --git a/.github/workflows/generate-from-spec.yml b/.github/workflows/generate-from-spec.yml
new file mode 100644
index 0000000..d093e79
--- /dev/null
+++ b/.github/workflows/generate-from-spec.yml
@@ -0,0 +1,167 @@
+name: Generate SDK from OpenAPI Spec
+
+on:
+ push:
+ paths:
+ - 'openapi.yaml'
+
+ # Manual trigger for testing
+ workflow_dispatch:
+
+jobs:
+ generate:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ pull-requests: write
+
+ steps:
+ - name: Checkout Python SDK
+ uses: actions/checkout@v5
+ with:
+ fetch-depth: 0
+
+ - name: Get branch and diff info
+ id: info
+ run: |
+ # Get the branch name this workflow is running on
+ BRANCH_NAME="${GITHUB_REF#refs/heads/}"
+ echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT
+
+ # Get commit SHAs for diff
+ BEFORE_SHA="${{ github.event.before }}"
+ AFTER_SHA="${{ github.sha }}"
+
+ # Handle new branch case
+ if [[ "$BEFORE_SHA" == "0000000000000000000000000000000000000000" ]]; then
+ BEFORE_SHA=$(git rev-parse HEAD~1 2>/dev/null || echo "")
+ fi
+
+ echo "before_sha=$BEFORE_SHA" >> $GITHUB_OUTPUT
+ echo "after_sha=$AFTER_SHA" >> $GITHUB_OUTPUT
+
+ # Generate diff for the spec file
+ if [[ -n "$BEFORE_SHA" ]]; then
+ git diff "$BEFORE_SHA" "$AFTER_SHA" -- openapi.yaml > spec.diff || touch spec.diff
+ else
+ touch spec.diff
+ fi
+
+ echo "Diff size: $(wc -l < spec.diff) lines"
+ echo "Running on branch: $BRANCH_NAME"
+
+ - name: Fetch prompt and build context
+ run: |
+ # Fetch static prompt from agent-toolkit
+ curl -sL https://raw.githubusercontent.com/video-db/agent-toolkit/main/context/prompts/spec-to-python-sdk.txt > static_prompt.txt
+
+ # Build full prompt with dynamic content
+ cat > codex_prompt.md << 'PROMPT_EOF'
+ ## Git Diff of OpenAPI Spec Changes
+
+ The following diff shows what changed in the API specification:
+
+ ```diff
+ PROMPT_EOF
+
+ cat spec.diff >> codex_prompt.md
+
+ cat >> codex_prompt.md << 'PROMPT_EOF'
+ ```
+
+ ## Current OpenAPI Spec
+
+ If you need to reference the full spec for context, it's available at: openapi.yaml
+
+ ---
+
+ PROMPT_EOF
+
+ # Append static instructions
+ cat static_prompt.txt >> codex_prompt.md
+
+ echo "Prompt built successfully"
+
+ - name: Run Codex
+ uses: openai/codex-action@v1
+ with:
+ openai-api-key: ${{ secrets.OPENAI_API_KEY }}
+ model: o4-mini
+ sandbox: workspace-write
+ prompt-file: codex_prompt.md
+
+ - name: Check for changes and create PR
+ id: create_pr
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ # Configure git
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+
+ # Check if there are changes
+ if git diff --quiet && git diff --staged --quiet; then
+ echo "No changes generated by Codex"
+ echo "has_changes=false" >> $GITHUB_OUTPUT
+ exit 0
+ fi
+
+ echo "has_changes=true" >> $GITHUB_OUTPUT
+
+ # Clean up temporary files - DO NOT commit these
+ rm -f spec.diff static_prompt.txt codex_prompt.md
+
+ # Get the base branch name
+ BASE_BRANCH="${{ steps.info.outputs.branch_name }}"
+
+ # Create work branch from the current branch
+ WORK_BRANCH="auto/spec-sync-$(date +%Y%m%d-%H%M%S)"
+ git checkout -b "$WORK_BRANCH"
+ git add -A
+
+ # Commit
+ git commit -m "feat: sync with OpenAPI spec changes
+
+ Source branch: ${BASE_BRANCH}
+
+ Generated by OpenAI Codex"
+
+ # Push
+ git push origin "$WORK_BRANCH"
+
+ echo "work_branch=$WORK_BRANCH" >> $GITHUB_OUTPUT
+ echo "base_branch=$BASE_BRANCH" >> $GITHUB_OUTPUT
+
+ # Create PR targeting the original branch
+ gh pr create \
+ --base "$BASE_BRANCH" \
+ --title "feat: sync with OpenAPI spec" \
+ --body "## Summary
+
+ Automated SDK update based on OpenAPI spec changes.
+
+ **Base branch**: \`$BASE_BRANCH\`
+
+ ## Review Checklist
+
+ - [ ] Generated code follows SDK conventions
+ - [ ] Method signatures are correct
+ - [ ] No breaking changes introduced
+ - [ ] Tests pass locally
+
+ ---
+ *Generated by [OpenAI Codex](https://github.com/openai/codex)*"
+
+ - name: Trigger Node SDK Generation
+ if: steps.create_pr.outputs.has_changes == 'true'
+ uses: peter-evans/repository-dispatch@v3
+ with:
+ token: ${{ secrets.SDK_SYNC_PAT }}
+ repository: ${{ github.repository_owner }}/videodb-node
+ event-type: python-updated
+ client-payload: |
+ {
+ "source_branch": "${{ steps.create_pr.outputs.work_branch }}",
+ "target_branch": "${{ steps.create_pr.outputs.base_branch }}",
+ "trigger_type": "spec_change"
+ }
diff --git a/.github/workflows/notify-node-sdk.yml b/.github/workflows/notify-node-sdk.yml
new file mode 100644
index 0000000..e83ba3a
--- /dev/null
+++ b/.github/workflows/notify-node-sdk.yml
@@ -0,0 +1,91 @@
+name: Notify Node SDK on Python Code Changes
+
+on:
+ push:
+ paths:
+ - 'videodb/*.py'
+ - 'videodb/**/*.py'
+ - '!videodb/__about__.py'
+ - '!videodb/__init__.py'
+
+jobs:
+ notify:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v5
+ with:
+ fetch-depth: 0
+
+ - name: Check if spec also changed
+ id: check_spec
+ run: |
+ BEFORE_SHA="${{ github.event.before }}"
+ AFTER_SHA="${{ github.sha }}"
+
+ # Handle new branch case
+ if [[ "$BEFORE_SHA" == "0000000000000000000000000000000000000000" ]]; then
+ BEFORE_SHA=$(git rev-parse HEAD~1 2>/dev/null || echo "")
+ fi
+
+ # Check if openapi.yaml changed in this push
+ if [[ -n "$BEFORE_SHA" ]]; then
+ SPEC_CHANGED=$(git diff --name-only "$BEFORE_SHA" "$AFTER_SHA" -- openapi.yaml | wc -l)
+ else
+ SPEC_CHANGED=0
+ fi
+
+ if [[ "$SPEC_CHANGED" -gt 0 ]]; then
+ echo "spec_changed=true" >> $GITHUB_OUTPUT
+ echo "Spec also changed - skipping (generate-from-spec.yml will handle this)"
+ else
+ echo "spec_changed=false" >> $GITHUB_OUTPUT
+ echo "Only Python code changed - will notify Node SDK"
+ fi
+
+ - name: Get branch and changed files
+ if: steps.check_spec.outputs.spec_changed == 'false'
+ id: info
+ run: |
+ # Get the branch name
+ BRANCH_NAME="${GITHUB_REF#refs/heads/}"
+ echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT
+
+ BEFORE_SHA="${{ github.event.before }}"
+ AFTER_SHA="${{ github.sha }}"
+
+ # Handle new branch case
+ if [[ "$BEFORE_SHA" == "0000000000000000000000000000000000000000" ]]; then
+ BEFORE_SHA=$(git rev-parse HEAD~1 2>/dev/null || echo "")
+ fi
+
+ # Get changed Python files (comma-separated for JSON)
+ if [[ -n "$BEFORE_SHA" ]]; then
+ FILES=$(git diff --name-only "$BEFORE_SHA" "$AFTER_SHA" -- 'videodb/*.py' 'videodb/**/*.py' | grep -v '__about__\|__init__' | tr '\n' ',' | sed 's/,$//' || true)
+ else
+ FILES=""
+ fi
+
+ echo "changed_files=$FILES" >> $GITHUB_OUTPUT
+ echo "before_sha=$BEFORE_SHA" >> $GITHUB_OUTPUT
+ echo "after_sha=$AFTER_SHA" >> $GITHUB_OUTPUT
+
+ echo "Branch: $BRANCH_NAME"
+ echo "Changed files: $FILES"
+
+ - name: Trigger Node SDK Generation
+ if: steps.check_spec.outputs.spec_changed == 'false' && steps.info.outputs.changed_files != ''
+ uses: peter-evans/repository-dispatch@v3
+ with:
+ token: ${{ secrets.SDK_SYNC_PAT }}
+ repository: ${{ github.repository_owner }}/videodb-node
+ event-type: python-updated
+ client-payload: |
+ {
+ "source_branch": "${{ steps.info.outputs.branch_name }}",
+ "target_branch": "${{ steps.info.outputs.branch_name }}",
+ "trigger_type": "code_change",
+ "changed_files": "${{ steps.info.outputs.changed_files }}",
+ "before_sha": "${{ steps.info.outputs.before_sha }}",
+ "after_sha": "${{ steps.info.outputs.after_sha }}"
+ }
diff --git a/.github/workflows/release-pypi.yml b/.github/workflows/release-pypi.yml
new file mode 100644
index 0000000..3c85f65
--- /dev/null
+++ b/.github/workflows/release-pypi.yml
@@ -0,0 +1,117 @@
+name: Release
+
+on:
+ push:
+ tags:
+ - "v*"
+ workflow_dispatch:
+
+permissions: {}
+
+env:
+ PYTHON_VERSION: 3.9
+
+jobs:
+ ci:
+ name: CI Checks
+ permissions:
+ contents: read
+ uses: ./.github/workflows/ci.yml
+
+ build:
+ name: Build & Verify
+ needs: ci
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: ${{ env.PYTHON_VERSION }}
+
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install setuptools wheel twine
+ pip install -r requirements.txt
+
+ - name: Verify version matches tag
+ if: startsWith(github.ref, 'refs/tags/v')
+ run: |
+ TAG="${GITHUB_REF#refs/tags/v}"
+ PKG_VERSION=$(python -c "exec(open('videodb/__about__.py').read()); print(__version__)")
+ if [ "$TAG" != "$PKG_VERSION" ]; then
+ echo "::error::Tag version ($TAG) does not match package version ($PKG_VERSION)"
+ exit 1
+ fi
+ echo "Version verified: $PKG_VERSION"
+
+ - name: Check version not already on PyPI
+ run: |
+ PKG_VERSION=$(python -c "exec(open('videodb/__about__.py').read()); print(__version__)")
+ echo "Local version: $PKG_VERSION"
+
+ PYPI_VERSIONS=$(pip index versions videodb 2>/dev/null | head -1 || echo "")
+ echo "PyPI versions: $PYPI_VERSIONS"
+
+ if echo "$PYPI_VERSIONS" | grep -qF "$PKG_VERSION"; then
+ echo "::error::Version $PKG_VERSION already exists on PyPI. Bump the version before releasing."
+ exit 1
+ fi
+ echo "Version $PKG_VERSION is new — safe to publish"
+
+ - name: Build
+ run: |
+ python setup.py sdist bdist_wheel
+ twine check dist/*
+
+ - name: Upload build artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: dist
+ path: dist/
+
+ pypi:
+ name: Publish to PyPI
+ needs: build
+ runs-on: ubuntu-latest
+ environment: pypi
+ permissions:
+ id-token: write
+ steps:
+ - name: Download build artifacts
+ uses: actions/download-artifact@v4
+ with:
+ name: dist
+ path: dist/
+
+ - name: Publish to PyPI
+ uses: pypa/gh-action-pypi-publish@release/v1
+
+ github-release:
+ name: GitHub Release
+ needs: pypi
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+
+ - name: Download build artifacts
+ uses: actions/download-artifact@v4
+ with:
+ name: dist
+ path: dist/
+
+ - name: Create GitHub Release
+ uses: softprops/action-gh-release@v2
+ with:
+ generate_release_notes: true
+ files: dist/*
diff --git a/.github/workflows/trigger-agent-toolkit-update.yaml b/.github/workflows/trigger-agent-toolkit-update.yml
similarity index 100%
rename from .github/workflows/trigger-agent-toolkit-update.yaml
rename to .github/workflows/trigger-agent-toolkit-update.yml
diff --git a/.gitignore b/.gitignore
index 8ae2cb6..7f54310 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,3 +16,7 @@ venv/
.vscode/*
example.ipynb
example.py
+
+# Credentials
+*-adminsdk.json
+*.credentials.json
\ No newline at end of file
diff --git a/README.md b/README.md
index a3ab4a1..be59222 100644
--- a/README.md
+++ b/README.md
@@ -36,207 +36,785 @@
# VideoDB Python SDK
-VideoDB Python SDK allows you to interact with the VideoDB serverless database. Manage videos as intelligent data, not files. It's scalable, cost-efficient & optimized for AI applications and LLM integration.
-
-
-
-
-
+VideoDB Python SDK provides programmatic access to VideoDB's serverless video infrastructure. Build AI applications that understand and process video as structured data with support for semantic search, scene extraction, transcript generation, and multimodal content generation.
+
+## 📑 Table of Contents
+
+- [Installation](#installation)
+- [Quick Start](#quick-start)
+ - [Establishing a Connection](#establishing-a-connection)
+ - [Uploading Media](#uploading-media)
+ - [Updating Video Metadata](#updating-video-metadata)
+ - [Viewing and Streaming Videos](#viewing-and-streaming-videos)
+ - [Searching Inside Videos](#searching-inside-videos)
+ - [Working with Transcripts](#working-with-transcripts)
+ - [Scene Extraction and Indexing](#scene-extraction-and-indexing)
+ - [Adding Subtitles](#adding-subtitles)
+ - [Generating Thumbnails](#generating-thumbnails)
+- [Working with Collections](#working-with-collections)
+ - [Audio and Image Management](#audio-and-image-management)
+- [Advanced Features](#advanced-features)
+ - [Realtime Video Editor](#realtime-video-editor)
+ - [Real-Time Streams (RTStream)](#real-time-streams-rtstream)
+ - [Capture Sessions (Desktop Recording)](#capture-sessions-desktop-recording)
+ - [WebSocket Events](#websocket-events)
+ - [Meeting Recording](#meeting-recording)
+ - [Generative Media](#generative-media)
+ - [Video Dubbing and Translation](#video-dubbing-and-translation)
+ - [Transcoding](#transcoding)
+ - [YouTube Integration](#youtube-integration)
+ - [Billing and Usage](#billing-and-usage)
+ - [Download Streams](#download-streams)
+- [Configuration Options](#configuration-options)
+ - [Subtitle Customization](#subtitle-customization)
+ - [Text Overlay Styling](#text-overlay-styling)
+- [Error Handling](#error-handling)
+- [API Reference](#api-reference)
+- [Examples and Tutorials](#examples-and-tutorials)
+- [Contributing](#contributing)
+- [Resources](#resources)
+- [License](#license)
## Installation
-To install the package, run the following command in your terminal:
-
-```
+```bash
pip install videodb
```
-
+**Requirements:**
+- Python 3.8 or higher
+- Dependencies: `requests>=2.25.1`, `backoff>=2.2.1`, `tqdm>=4.66.1`
## Quick Start
-### Creating a Connection
+### Establishing a Connection
-Get an API key from the [VideoDB console](https://console.videodb.io). Free for first 50 uploads _(No credit card required)_.
+Get your API key from [VideoDB Console](https://console.videodb.io). Free for first 50 uploads (no credit card required).
```python
import videodb
+
+# Connect using API key
conn = videodb.connect(api_key="YOUR_API_KEY")
+
+# Or set environment variable VIDEO_DB_API_KEY
+# conn = videodb.connect()
```
-## Working with a Single Video
+### Uploading Media
----
+Upload videos, audio files, or images from various sources:
-### ⬆️ Uploading a Video
+```python
+# Upload video from YouTube URL
+video = conn.upload(url="https://www.youtube.com/watch?v=VIDEO_ID")
-Now that you have established a connection to VideoDB, you can upload your videos using `conn.upload()`.
-You can directly upload from `youtube`, `any public url`, `S3 bucket` or a `local file path`. A default collection is created when you create your first connection.
+# Upload from public URL
+video = conn.upload(url="https://example.com/video.mp4")
-`upload` method returns a `Video` object. You can simply pass a single string
-representing either a local file path or a URL.
+# Upload from local file
+video = conn.upload(file_path="./my_video.mp4")
-```python
-# Upload a video by url
-video = conn.upload("https://www.youtube.com/watch?v=WDv4AWk0J3U")
+# Upload with metadata
+video = conn.upload(
+ file_path="./video.mp4",
+ name="My Video",
+ description="Video description"
+)
+```
-# Upload a video from file system
-video_f = conn.upload("./my_video.mp4")
+The `upload()` method returns `Video`, `Audio`, or `Image` objects based on the media type.
+### Updating Video Metadata
+
+```python
+# Update video name
+video.update(name="New Video Title")
```
-### 📺 View your Video
+### Viewing and Streaming Videos
-Once uploaded, your video is immediately available for viewing in 720p resolution. ⚡️
+```python
+# Generate stream URL
+stream_url = video.generate_stream()
-- Generate a streamable url for the video using video.generate_stream()
-- Preview the video using video.play(). This will open the video in your default browser/notebook
+# Play stream using VideoDB player
+videodb.play_stream(stream_url)
-```python
-video.generate_stream()
+# Play in browser/notebook
video.play()
```
-### ⛓️ Stream Specific Sections of Videos
+### Searching Inside Videos
-You can easily clip specific sections of a video by passing a timeline of the start and end timestamps (in seconds) as a parameter.
-For example, this will generate and play a compilation of the first `10 seconds` and the clip between the `120th` and the `140th` second.
+Index and search video content semantically:
```python
-stream_link = video.generate_stream(timeline=[[0,10], [120,140]])
-play_stream(stream_link)
+from videodb import SearchType, IndexType
+
+# Index spoken words for semantic search
+video.index_spoken_words()
+
+# Search for content
+results = video.search("morning sunlight")
+
+# Access search results
+shots = results.get_shots()
+for shot in shots:
+ print(f"Found at {shot.start}s - {shot.end}s: {shot.text}")
+
+# Sort results by timestamp instead of relevance score
+results = coll.search(query="morning sunlight", sort_docs_on="start")
+
+# Play compiled results
+results.play()
```
-### 🔍 Search Inside a Video
+**Search Types:**
+- `SearchType.semantic` - Semantic search (default)
+- `SearchType.keyword` - Keyword-based search
+- `SearchType.scene` - Visual scene search
-To search bits inside a video, you have to `index` the video first. This can be done by a simple command.
-_P.S. Indexing may take some time for longer videos._
+### Working with Transcripts
```python
-video.index_spoken_words()
-result = video.search("Morning Sunlight")
-result.play()
-video.get_transcript()
+# Generate transcript
+video.generate_transcript()
+
+# Generate transcript with language hint
+video.generate_transcript(language_code="en")
+
+# Get transcript with timestamps
+transcript = video.get_transcript()
+
+# Get plain text transcript
+text = video.get_transcript_text()
+
+# Get transcript for specific time range
+transcript = video.get_transcript(start=10, end=60)
+
+# Translate transcript
+translated = video.translate_transcript(
+ language="Spanish",
+ additional_notes="Formal tone"
+)
```
-`Videodb` is launching more indexing options in upcoming versions. As of now you can try the `semantic` index - Index by spoken words.
+**Segmentation Options:**
+- `videodb.Segmenter.word` - Word-level timestamps
+- `videodb.Segmenter.sentence` - Sentence-level timestamps
+- `videodb.Segmenter.time` - Time-based segments
-In the future you'll be able to index videos using:
+### Scene Extraction and Indexing
-1. **Scene** - Visual concepts and events.
-2. **Faces**.
-3. **Specific domain Index** like Football, Baseball, Drone footage, Cricket etc.
+Extract and analyze scenes from videos:
-### Viewing Search Results
+```python
+from videodb import SceneExtractionType
+
+# Extract scenes using shot detection
+scene_collection = video.extract_scenes(
+ extraction_type=SceneExtractionType.shot_based,
+ extraction_config={"threshold": 20, "frame_count": 1}
+)
+
+# Extract scenes at time intervals
+scene_collection = video.extract_scenes(
+ extraction_type=SceneExtractionType.time_based,
+ extraction_config={
+ "time": 10,
+ "frame_count": 1,
+ "select_frames": ["first"]
+ }
+)
+
+# Describe individual scenes with custom model config
+scenes = video.get_scene_index(scene_collection.scene_index_id)
+scene = scenes[0]
+scene.describe(
+ prompt="Describe this scene",
+ model_config={"model_name": "pro", "temperature": 0.5}
+)
+
+# Index scenes for semantic search
+scene_index_id = video.index_scenes(
+ extraction_type=SceneExtractionType.shot_based,
+ prompt="Describe the visual content of this scene"
+)
+
+# Search within scenes
+results = video.search(
+ query="outdoor landscape",
+ search_type=SearchType.scene,
+ index_type=IndexType.scene
+)
+
+# List scene indexes
+scene_indexes = video.list_scene_index()
+
+# Get specific scene index
+scenes = video.get_scene_index(scene_index_id)
+
+# Delete scene collection
+video.delete_scene_collection(scene_collection.id)
+```
-`video.search()` returns a `SearchResults` object, which contains the sections or as we call them, `shots` of videos which semantically match your search query.
+### Adding Subtitles
-- `result.get_shots()` Returns a list of Shot(s) that matched the search query.
-- `result.play()` Returns a playable url for the video (similar to video.play(); you can open this link in the browser, or embed it into your website using an iframe).
+```python
+from videodb import SubtitleStyle
+
+# Add subtitles with default style
+stream_url = video.add_subtitle()
+
+# Customize subtitle appearance
+style = SubtitleStyle(
+ font_name="Arial",
+ font_size=24,
+ primary_colour="&H00FFFFFF",
+ bold=True
+)
+stream_url = video.add_subtitle(style=style)
+```
-## RAG: Search inside Multiple Videos
+### Generating Thumbnails
----
+```python
+# Get default thumbnail
+thumbnail_url = video.generate_thumbnail()
+
+# Generate thumbnail at specific timestamp
+thumbnail_image = video.generate_thumbnail(time=30.5)
+
+# Get all thumbnails
+thumbnails = video.get_thumbnails()
+```
-`VideoDB` can store and search inside multiple videos with ease. By default, videos are uploaded to your default collection.
+## Working with Collections
-### 🔄 Using Collection to Upload Multiple Videos
+Organize and search across multiple videos:
```python
-# Get the default collection
+# Get default collection
coll = conn.get_collection()
-# Upload Videos to a collection
-coll.upload("https://www.youtube.com/watch?v=lsODSDmY4CY")
-coll.upload("https://www.youtube.com/watch?v=vZ4kOr38JhY")
-coll.upload("https://www.youtube.com/watch?v=uak_dXHh6s4")
+# Create new collection
+coll = conn.create_collection(
+ name="My Collection",
+ description="Collection description",
+ is_public=False
+)
+
+# List all collections
+collections = conn.get_collections()
+
+# Update collection
+coll = conn.update_collection(
+ id="collection_id",
+ name="Updated Name",
+ description="Updated description"
+)
+
+# Upload to collection
+video = coll.upload(url="https://example.com/video.mp4")
+
+# Get videos in collection
+videos = coll.get_videos()
+video = coll.get_video(video_id)
+
+# Search across collection
+results = coll.search(query="specific content")
+
+# Search by title
+results = coll.search_title("video title")
+
+# Make collection public/private
+coll.make_public()
+coll.make_private()
+
+# Delete collection
+coll.delete()
+```
+
+### Audio and Image Management
+
+```python
+# Get audio files
+audios = coll.get_audios()
+audio = coll.get_audio(audio_id)
+
+# Generate audio URL
+audio_url = audio.generate_url()
+
+# Get images
+images = coll.get_images()
+image = coll.get_image(image_id)
+
+# Generate image URL
+image_url = image.generate_url()
+
+# Delete media
+audio.delete()
+image.delete()
```
-- `conn.get_collection()` : Returns a Collection object; the default collection.
-- `coll.get_videos()` : Returns a list of Video objects; all videos in the collections.
-- `coll.get_video(video_id)`: Returns a Video object, corresponding video from the provided `video_id`.
-- `coll.delete_video(video_id)`: Deletes the video from the Collection.
+## Advanced Features
+
+### Realtime Video Editor
-### 📂 Search Inside Collection
+Build multi-track video compositions programmatically using VideoDB's 4-layer architecture: **Assets** (raw media), **Clips** (how assets appear), **Tracks** (timeline lanes), and **Timeline** (final canvas).
-You can simply Index all the videos in a collection and use the search method to find relevant results.
-Here we are indexing the spoken content of a collection and performing semantic search.
+**Example: Video with background music**
```python
-# Index all videos in collection
-for video in coll.get_videos():
- video.index_spoken_words()
+from videodb import connect
+from videodb.editor import Timeline, Track, Clip, VideoAsset, AudioAsset
+
+conn = connect(api_key="YOUR_API_KEY")
+video = conn.upload(url="https://www.youtube.com/watch?v=VIDEO_ID")
+audio = conn.upload(file_path="./music.mp3")
+
+# Create timeline
+timeline = Timeline(conn)
+
+# Video track
+video_track = Track()
+video_asset = VideoAsset(id=video.id, start=10)
+video_clip = Clip(asset=video_asset, duration=30)
+video_track.add_clip(0, video_clip)
+
+# Audio track
+audio_track = Track()
+audio_asset = AudioAsset(id=audio.id, start=0, volume=0.3)
+audio_clip = Clip(asset=audio_asset, duration=30)
+audio_track.add_clip(0, audio_clip)
+
+# Compose and render
+timeline.add_track(video_track)
+timeline.add_track(audio_track)
+stream_url = timeline.generate_stream()
+```
-# search in the collection of videos
-results = coll.search(query = "What is Dopamine?")
-results.play()
+**Asset Types:**
+- `VideoAsset` - Video clips with trim control (`start`, `volume`)
+- `AudioAsset` - Background music, voiceovers, sound effects
+- `ImageAsset` - Logos, watermarks, static overlays
+- `TextAsset` - Custom text with typography (`Font`, `Background`, `Alignment`)
+- `CaptionAsset` - Auto-generated subtitles synced to speech
+
+**Clip Controls:**
+- **Position & Scale**: `position=Position.topRight`, `scale=0.5`, `offset=Offset(x=0.1, y=-0.2)`
+- **Visual Effects**: `opacity=0.8`, `fit=Fit.cover`, `filter=Filter.greyscale`
+- **Transitions**: `transition=Transition(in_="fade", out="fade", duration=1)`
+
+**Track Layering:**
+- Clips on the same track play sequentially
+- Clips on different tracks at the same time play simultaneously (overlays)
+
+For advanced patterns (picture-in-picture, multi-audio layers, auto-captions), see the [Editor SDK documentation](https://docs.videodb.io/realtime-video-editor-sdk-44).
+
+### Real-Time Streams (RTStream)
+
+Process live video streams in real-time:
+
+```python
+from videodb import SceneExtractionType
+
+# Connect to real-time stream
+rtstream = coll.connect_rtstream(
+ url="rtsp://example.com/stream",
+ name="Live Stream"
+)
+
+# Start or Stop processing
+rtstream.stop()
+rtstream.start()
+
+# Index scenes from stream
+scene_index = rtstream.index_scenes(
+ extraction_type=SceneExtractionType.time_based,
+ extraction_config={"time": 2, "frame_count": 5},
+ prompt="Describe the scene"
+)
+
+# Start or Stop scene indexing
+scene_index.stop()
+scene_index.start()
+
+# Get scenes
+scenes = scene_index.get_scenes(page=1, page_size=100)
+
+# Create alerts for events
+alert_id = scene_index.create_alert(
+ event_id=event_id,
+ callback_url="https://example.com/callback"
+)
+
+# Enable/disable alerts
+scene_index.disable_alert(alert_id)
+scene_index.enable_alert(alert_id)
+
+# Generate stream with player metadata
+stream_url = rtstream.generate_stream(
+ start=1711000000,
+ end=1711003600,
+ player_config={
+ "title": "Live Feed",
+ "description": "Stream recording",
+ "slug": "live-feed"
+ }
+)
+
+# Export a stopped stream as a video/audio asset
+rtstream.stop()
+export_result = rtstream.export(name="my_recording")
+
+# List streams
+streams = coll.list_rtstreams()
```
-The result here has all the matching bits in a single stream from your collection. You can use these results in your application right away.
+### Capture Sessions (Desktop Recording)
-### 🌟 Explore the Video object
+Record screen, microphone, and system audio from desktop applications using native capture binaries:
-There are multiple methods available on a Video Object, that can be helpful for your use-case.
+```bash
+# Install capture dependencies
+pip install 'videodb[capture]'
+```
-**Get the Transcript**
+```python
+from videodb.capture import CaptureClient
+
+# Backend: Create a capture session
+cap = coll.create_capture_session(
+ end_user_id="user_abc",
+ callback_url="https://example.com/webhook"
+)
+
+# Generate a client token for secure desktop auth
+token = conn.generate_client_token(expires_in=86400)
+
+# Desktop client: Start capture
+client = CaptureClient(session_token=token)
+
+# Request permissions
+await client.request_permission("microphone")
+await client.request_permission("screen")
+
+# Configure channels and start recording
+await client.start_capture_session(
+ session_id=cap.id,
+ channels=[
+ {"type": "mic", "name": "mic:default"},
+ {"type": "system_audio", "name": "system_audio:default"},
+ {"type": "display", "name": "display:1"},
+ ]
+)
+
+# Stop capture
+await client.stop_capture_session()
+
+# Get session details and export
+cap = coll.get_capture_session(cap.id)
+export_result = cap.export()
+
+# List all capture sessions
+sessions = coll.list_capture_sessions()
+```
+
+### WebSocket Events
+
+Receive real-time transcript and indexing events via WebSocket:
```python
-# words with timestamps
-text_json = video.get_transcript()
-text = video.get_transcript_text()
-print(text)
+# Connect to WebSocket
+ws = conn.connect_websocket()
+await ws.connect()
+print(f"Connection ID: {ws.connection_id}")
+
+# Stream events
+async for event in ws.receive():
+ print(event)
+
+# Close connection
+await ws.close()
```
-**Add Subtitles to a video**
+### Meeting Recording
-It returns a new stream instantly with subtitles added to the video.
+Record and process virtual meetings:
```python
-new_stream = video.add_subtitle()
-play_stream(new_stream)
+# Start meeting recording
+meeting = conn.record_meeting(
+ meeting_url="https://meet.google.com/xxx-yyyy-zzz",
+ bot_name="Recorder Bot",
+ meeting_title="Team Meeting",
+ callback_url="https://example.com/callback"
+)
+
+# Check meeting status
+meeting.refresh()
+print(meeting.status) # initializing, processing, or done
+
+# Wait for completion
+meeting.wait_for_status("done", timeout=14400, interval=120)
+
+# Get meeting details
+if meeting.is_completed:
+ video_id = meeting.video_id
+ video = coll.get_video(video_id)
+
+# Get meeting from video
+meeting_info = video.get_meeting()
```
-**Get Thumbnail of a Video:**
+### Generative Media
-`video.generate_thumbnail()`: Returns a thumbnail image of video.
+Generate images, audio, and videos using AI:
-**Delete a video:**
+```python
+# Generate image
+image = coll.generate_image(
+ prompt="A beautiful sunset over mountains",
+ aspect_ratio="16:9"
+)
+
+# Generate music
+audio = coll.generate_music(
+ prompt="Upbeat electronic music",
+ duration=30
+)
+
+# Generate sound effects
+audio = coll.generate_sound_effect(
+ prompt="Door closing sound",
+ duration=2
+)
+
+# Generate voice from text
+audio = coll.generate_voice(
+ text="Hello, welcome to VideoDB",
+ voice_name="Default"
+)
+
+# Generate video
+video = coll.generate_video(
+ prompt="A cat playing with a ball",
+ duration=5
+)
+
+# Generate text using LLM
+response = coll.generate_text(
+ prompt="Summarize this content",
+ model_name="pro", # basic, pro, or ultra
+ response_type="text" # text or json
+)
+```
-`video.delete()`: Deletes the video.
+### Video Dubbing and Translation
-Checkout more examples and tutorials 👉 [Build with VideoDB](https://docs.videodb.io/build-with-videodb-35) to explore what you can build with `VideoDB`.
+```python
+# Dub video to another language
+dubbed_video = coll.dub_video(
+ video_id=video.id,
+ language_code="es",
+ callback_url="https://example.com/callback"
+)
+```
----
+### Transcoding
+
+```python
+from videodb import TranscodeMode, VideoConfig, AudioConfig
+
+# Start transcoding job
+job_id = conn.transcode(
+ source="https://example.com/video.mp4",
+ callback_url="https://example.com/callback",
+ mode=TranscodeMode.economy,
+ video_config=VideoConfig(resolution=1080, quality=23),
+ audio_config=AudioConfig(mute=False)
+)
+
+# Check transcode status
+status = conn.get_transcode_details(job_id)
+```
-
+### YouTube Integration
-## Roadmap
+```python
+# Search YouTube
+results = conn.youtube_search(
+ query="machine learning tutorial",
+ result_threshold=10,
+ duration="medium"
+)
+
+for result in results:
+ print(result["title"], result["url"])
+```
-- Adding More Indexes : `Face`, `Scene`, `Security`, `Events`, and `Sports`
-- Give prompt support to generate thumbnails using GenAI.
-- Give prompt support to access content.
-- Give prompt support to edit videos.
-- See the [open issues](https://github.com/video-db/videodb-python/issues) for a list of proposed features (and known issues).
+### Billing and Usage
----
+```python
+# Check usage
+usage = conn.check_usage()
-
+# Get invoices
+invoices = conn.get_invoices()
+```
+
+### Download Streams
+
+```python
+# Download compiled stream
+download_info = conn.download(
+ stream_link="https://stream.videodb.io/...",
+ name="my_compilation"
+)
+```
+
+## Configuration Options
+
+### Subtitle Customization
+
+```python
+from videodb import SubtitleStyle, SubtitleAlignment, SubtitleBorderStyle
+
+style = SubtitleStyle(
+ font_name="Arial",
+ font_size=18,
+ primary_colour="&H00FFFFFF", # White
+ secondary_colour="&H000000FF", # Blue
+ outline_colour="&H00000000", # Black
+ back_colour="&H00000000", # Black
+ bold=False,
+ italic=False,
+ underline=False,
+ strike_out=False,
+ scale_x=1.0,
+ scale_y=1.0,
+ spacing=0,
+ angle=0,
+ border_style=SubtitleBorderStyle.outline,
+ outline=1.0,
+ shadow=0.0,
+ alignment=SubtitleAlignment.bottom_center,
+ margin_l=10,
+ margin_r=10,
+ margin_v=10
+)
+```
+
+### Text Overlay Styling
+
+```python
+from videodb import TextStyle
+
+style = TextStyle(
+ fontsize=24,
+ fontcolor="black",
+ font="Sans",
+ box=True,
+ boxcolor="white",
+ boxborderw="10"
+)
+```
+
+## Error Handling
+
+```python
+from videodb.exceptions import (
+ VideodbError,
+ AuthenticationError,
+ InvalidRequestError,
+ SearchError
+)
+
+try:
+ conn = videodb.connect(api_key="invalid_key")
+except AuthenticationError as e:
+ print(f"Authentication failed: {e}")
+
+try:
+ video = conn.upload(url="invalid_url")
+except InvalidRequestError as e:
+ print(f"Invalid request: {e}")
+
+try:
+ results = video.search("query")
+except SearchError as e:
+ print(f"Search error: {e}")
+```
+
+## API Reference
+
+### Core Objects
+
+- **Connection**: Main client for API interaction
+- **Collection**: Container for organizing media
+- **Video**: Video file with processing methods
+- **Audio**: Audio file representation
+- **Image**: Image file representation
+- **Timeline**: Multi-track video editor
+- **SearchResult**: Search results with shots
+- **Shot**: Time-segmented video clip
+- **Scene**: Visual scene with frames
+- **SceneCollection**: Collection of extracted scenes
+- **Meeting**: Meeting recording session
+- **RTStream**: Real-time stream processor
+- **CaptureSession**: Desktop capture session with export
+- **CaptureClient**: Native binary client for screen/audio recording
+- **WebSocketConnection**: Real-time event streaming
+
+### Constants and Enums
+
+- `IndexType`: `spoken_word`, `scene`
+- `SearchType`: `semantic`, `keyword`, `scene`
+- `SceneExtractionType`: `shot_based`, `time_based`
+- `Segmenter`: `word`, `sentence`, `time`
+- `TranscodeMode`: `lightning`, `economy`
+- `MediaType`: `video`, `audio`, `image`
+
+For detailed API documentation, visit [docs.videodb.io](https://docs.videodb.io).
+
+## Examples and Tutorials
+
+Explore practical examples and use cases in the [VideoDB Cookbook](https://github.com/video-db/videodb-cookbook):
+
+- Semantic video search
+- Scene-based indexing and retrieval
+- Custom video compilations
+- Meeting transcription and analysis
+- Real-time stream processing
+- Multi-language video dubbing
## Contributing
-Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**.
+Contributions are welcome! To contribute:
-1. Fork the Project
-2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
-3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
-4. Push to the Branch (`git push origin feature/AmazingFeature`)
+1. Fork the repository
+2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
+3. Commit your changes (`git commit -m 'Add AmazingFeature'`)
+4. Push to the branch (`git push origin feature/AmazingFeature`)
5. Open a Pull Request
+## Resources
+
+- **Documentation**: [docs.videodb.io](https://docs.videodb.io)
+- **Console**: [console.videodb.io](https://console.videodb.io)
+- **Examples**: [github.com/video-db/videodb-cookbook](https://github.com/video-db/videodb-cookbook)
+- **Community**: [Discord](https://discord.gg/py9P639jGz)
+- **Issues**: [GitHub Issues](https://github.com/video-db/videodb-python/issues)
+
+## License
+
+Apache License 2.0 - see [LICENSE](LICENSE) file for details.
+
---
-
[pypi-shield]: https://img.shields.io/pypi/v/videodb?style=for-the-badge
[pypi-url]: https://pypi.org/project/videodb/
diff --git a/openapi.yaml b/openapi.yaml
new file mode 100644
index 0000000..417b87b
--- /dev/null
+++ b/openapi.yaml
@@ -0,0 +1,5605 @@
+openapi: 3.0.3
+info:
+ title: VideoDB Server API
+ description: |
+ VideoDB Server API for video, audio, and image processing with AI capabilities.
+ This API provides comprehensive video management, search, indexing, and AI-powered features.
+ version: 1.0.0
+ contact:
+ name: VideoDB Support
+ url: https://videodb.io
+ license:
+ name: MIT
+ url: https://opensource.org/licenses/MIT
+
+servers:
+ - url: https://api.videodb.io
+ description: Production server
+ - url: https://staging-api.videodb.io
+ description: Staging server
+
+security:
+ - ApiKeyAuth: []
+
+components:
+ securitySchemes:
+ ApiKeyAuth:
+ type: apiKey
+ in: header
+ name: x-access-token
+ description: API key for authentication (sk-xxx format)
+
+ schemas:
+ Error:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: false
+ message:
+ type: string
+ example: "Error message"
+ error_code:
+ type: string
+ example: "ERROR_CODE"
+
+ SuccessResponse:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ message:
+ type: string
+ example: "Operation successful"
+
+ AsyncResponse:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ status:
+ type: string
+ enum: [processing, done, failed]
+ example: "processing"
+ data:
+ type: object
+ properties:
+ id:
+ type: string
+ example: "job-123"
+ output_url:
+ type: string
+ example: "https://api.videodb.io/async-response/job-123"
+
+ User:
+ type: object
+ properties:
+ user_id:
+ type: string
+ example: "u-12345"
+ user_name:
+ type: string
+ example: "John Doe"
+ user_email:
+ type: string
+ example: "john@example.com"
+ collections:
+ type: array
+ items:
+ type: string
+ example: ["default", "c-67890"]
+ default_collection:
+ type: string
+ example: "default"
+
+ Collection:
+ type: object
+ properties:
+ id:
+ type: string
+ example: "default"
+ name:
+ type: string
+ example: "My Collection"
+ description:
+ type: string
+ example: "Collection description"
+ is_public:
+ type: boolean
+ example: false
+ owner:
+ type: string
+ example: "u-12345"
+ created_at:
+ type: string
+ format: date-time
+
+ Video:
+ type: object
+ properties:
+ id:
+ type: string
+ example: "m-12345"
+ name:
+ type: string
+ example: "video.mp4"
+ description:
+ type: string
+ example: "Video description"
+ collection_id:
+ type: string
+ example: "default"
+ length:
+ type: number
+ example: 120.5
+ size:
+ type: number
+ example: 1048576
+ stream_url:
+ type: string
+ example: "https://stream.videodb.io/v/12345"
+ player_url:
+ type: string
+ example: "https://console.videodb.io/player/12345"
+ thumbnail_url:
+ type: string
+ example: "https://assets.videodb.io/thumb/12345.jpg"
+ created_at:
+ type: string
+ format: date-time
+
+ Audio:
+ type: object
+ properties:
+ id:
+ type: string
+ example: "a-12345"
+ name:
+ type: string
+ example: "audio.mp3"
+ collection_id:
+ type: string
+ example: "default"
+ length:
+ type: number
+ example: 60.0
+ size:
+ type: number
+ example: 524288
+ created_at:
+ type: string
+ format: date-time
+
+ Image:
+ type: object
+ properties:
+ id:
+ type: string
+ example: "img-12345"
+ name:
+ type: string
+ example: "image.jpg"
+ collection_id:
+ type: string
+ example: "default"
+ width:
+ type: number
+ example: 1920
+ height:
+ type: number
+ example: 1080
+ size:
+ type: number
+ example: 262144
+ url:
+ type: string
+ example: "https://assets.videodb.io/img/12345.jpg"
+ created_at:
+ type: string
+ format: date-time
+
+ SearchResult:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ query:
+ type: string
+ example: "search query"
+ results:
+ type: array
+ items:
+ type: object
+ properties:
+ video_id:
+ type: string
+ example: "m-12345"
+ start:
+ type: number
+ example: 10.5
+ end:
+ type: number
+ example: 20.3
+ text:
+ type: string
+ example: "matched content"
+ score:
+ type: number
+ example: 0.95
+
+ Timeline:
+ type: object
+ properties:
+ video_id:
+ type: string
+ example: "m-12345"
+ clips:
+ type: array
+ items:
+ type: object
+ properties:
+ start:
+ type: number
+ example: 0
+ end:
+ type: number
+ example: 30
+ volume:
+ type: number
+ example: 1.0
+
+ BillingUsage:
+ type: object
+ properties:
+ credit_balance:
+ type: number
+ example: 100.50
+ usage_this_month:
+ type: number
+ example: 25.75
+ breakdown:
+ type: object
+ additionalProperties:
+ type: number
+
+ RTStream:
+ type: object
+ properties:
+ id:
+ type: string
+ example: "rts-12345"
+ name:
+ type: string
+ example: "My Stream"
+ status:
+ type: string
+ enum: [connected, stopped]
+ example: "connected"
+ sample_rate:
+ type: integer
+ example: 30
+ media_types:
+ type: array
+ items:
+ type: string
+ enum: [video, audio]
+ example: ["video", "audio"]
+ collection_id:
+ type: string
+ example: "default"
+ store:
+ type: boolean
+ example: false
+ created_at:
+ type: string
+ format: date-time
+
+ CaptureSession:
+ type: object
+ properties:
+ session_id:
+ type: string
+ example: "capture-12345"
+ end_user_id:
+ type: string
+ example: "user-123"
+ status:
+ type: string
+ enum: [created, starting, active, stopped, failed]
+ example: "created"
+ collection_id:
+ type: string
+ example: "default"
+ ws_connection_id:
+ type: string
+ example: "conn-123"
+ metadata:
+ type: object
+ created_at:
+ type: string
+ format: date-time
+
+paths:
+ /:
+ get:
+ summary: Get service information
+ description: Returns basic service information
+ responses:
+ '200':
+ description: Service information
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ service:
+ type: string
+ example: "VideoDB Server"
+
+ /user:
+ get:
+ summary: Get user information
+ security:
+ - ApiKeyAuth: []
+ responses:
+ '200':
+ description: User information
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ $ref: '#/components/schemas/User'
+ '401':
+ description: Unauthorized
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+
+ /user/api_key:
+ get:
+ summary: Get user API keys
+ security:
+ - ApiKeyAuth: []
+ responses:
+ '200':
+ description: List of API keys
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: array
+ items:
+ type: object
+ properties:
+ key:
+ type: string
+ example: "sk-xxx"
+ created_at:
+ type: string
+ format: date-time
+
+ post:
+ summary: Create new API key
+ security:
+ - ApiKeyAuth: []
+ responses:
+ '200':
+ description: API key created
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ api_key:
+ type: string
+ example: "sk-xxx"
+
+ /user/api_key/{api_key}:
+ delete:
+ summary: Delete API key
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: api_key
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "sk-xxx"
+ responses:
+ '200':
+ description: API key deleted
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/SuccessResponse'
+
+ /collection:
+ get:
+ summary: Get user collections
+ security:
+ - ApiKeyAuth: []
+ responses:
+ '200':
+ description: List of collections
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ collections:
+ type: array
+ items:
+ $ref: '#/components/schemas/Collection'
+ default_collection:
+ type: string
+ example: "default"
+
+ post:
+ summary: Create new collection
+ security:
+ - ApiKeyAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ name:
+ type: string
+ example: "My New Collection"
+ description:
+ type: string
+ example: "Collection for my videos"
+ is_public:
+ type: boolean
+ example: false
+ responses:
+ '200':
+ description: Collection created
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ $ref: '#/components/schemas/Collection'
+
+ /collection/{collection_id}:
+ get:
+ summary: Get collection details
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: collection_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "default"
+ responses:
+ '200':
+ description: Collection details
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ $ref: '#/components/schemas/Collection'
+
+ patch:
+ summary: Update collection
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: collection_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "default"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ name:
+ type: string
+ example: "Updated Collection Name"
+ description:
+ type: string
+ example: "Updated description"
+ is_public:
+ type: boolean
+ example: true
+ responses:
+ '200':
+ description: Collection updated
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ $ref: '#/components/schemas/Collection'
+
+ delete:
+ summary: Delete collection
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: collection_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "default"
+ responses:
+ '200':
+ description: Collection deleted
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/SuccessResponse'
+
+ /collection/{collection_id}/upload:
+ post:
+ summary: Upload media to collection
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: collection_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "default"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - url
+ properties:
+ url:
+ type: string
+ example: "https://example.com/video.mp4"
+ name:
+ type: string
+ example: "My Video"
+ media_type:
+ type: string
+ enum: [video, audio, image]
+ example: "video"
+ callback_url:
+ type: string
+ example: "https://webhook.example.com/callback"
+ responses:
+ '200':
+ description: Upload initiated
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - $ref: '#/components/schemas/AsyncResponse'
+ - type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ $ref: '#/components/schemas/Video'
+
+ /collection/{collection_id}/search/:
+ post:
+ summary: Search within collection
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: collection_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "default"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - query
+ properties:
+ query:
+ type: string
+ example: "search query"
+ index_type:
+ type: string
+ enum: [spoken_word, scene]
+ example: "spoken_word"
+ search_type:
+ type: string
+ enum: [semantic, custom]
+ example: "semantic"
+ score_threshold:
+ type: number
+ example: 0.2
+ result_threshold:
+ type: integer
+ example: 10
+ stitch:
+ type: boolean
+ example: true
+ rerank:
+ type: boolean
+ example: false
+ filter:
+ type: array
+ items:
+ type: object
+ responses:
+ '200':
+ description: Search results
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/SearchResult'
+
+ /video/:
+ get:
+ summary: List videos
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: collection_id
+ in: query
+ schema:
+ type: string
+ example: "default"
+ - name: page_index
+ in: query
+ schema:
+ type: integer
+ example: 0
+ - name: count
+ in: query
+ schema:
+ type: integer
+ maximum: 5000
+ example: 50
+ responses:
+ '200':
+ description: List of videos
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ videos:
+ type: array
+ items:
+ $ref: '#/components/schemas/Video'
+
+ /video/{video_id}:
+ get:
+ summary: Get video details
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: video_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^m-"
+ example: "m-12345"
+ - name: collection_id
+ in: query
+ schema:
+ type: string
+ example: "default"
+ responses:
+ '200':
+ description: Video details
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ $ref: '#/components/schemas/Video'
+
+ patch:
+ summary: Update video
+ tags:
+ - Videos
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: video_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^m-"
+ example: "m-12345"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ name:
+ type: string
+ example: "Updated Video Name"
+ responses:
+ '200':
+ description: Video updated
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ $ref: '#/components/schemas/Video'
+
+ delete:
+ summary: Delete video
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: video_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^m-"
+ example: "m-12345"
+ responses:
+ '200':
+ description: Video deleted
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/SuccessResponse'
+
+ /video/{video_id}/storage/:
+ delete:
+ summary: Delete video storage
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: video_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^m-"
+ example: "m-12345"
+ responses:
+ '200':
+ description: Video storage deleted
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/SuccessResponse'
+
+ /video/{video_id}/stream/:
+ post:
+ summary: Create video stream
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: video_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^m-"
+ example: "m-12345"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ format:
+ type: string
+ enum: [mp4, webm, hls]
+ example: "mp4"
+ quality:
+ type: string
+ enum: [low, medium, high]
+ example: "high"
+ responses:
+ '200':
+ description: Stream created
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ stream_url:
+ type: string
+ example: "https://stream.videodb.io/v/12345"
+
+ /video/{video_id}/thumbnail/:
+ get:
+ summary: Get video thumbnail
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: video_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^m-"
+ example: "m-12345"
+ - name: timestamp
+ in: query
+ schema:
+ type: number
+ example: 10.5
+ responses:
+ '200':
+ description: Thumbnail URL
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ thumbnail_url:
+ type: string
+ example: "https://assets.videodb.io/thumb/12345.jpg"
+
+ post:
+ summary: Generate custom thumbnail
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: video_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^m-"
+ example: "m-12345"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ timestamp:
+ type: number
+ example: 10.5
+ width:
+ type: integer
+ example: 320
+ height:
+ type: integer
+ example: 180
+ responses:
+ '200':
+ description: Thumbnail generated
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AsyncResponse'
+
+ /video/{video_id}/thumbnails/:
+ get:
+ summary: Get all video thumbnails
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: video_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^m-"
+ example: "m-12345"
+ responses:
+ '200':
+ description: List of thumbnails
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: array
+ items:
+ type: object
+ properties:
+ timestamp:
+ type: number
+ example: 10.5
+ url:
+ type: string
+ example: "https://assets.videodb.io/thumb/12345_10.jpg"
+
+ /video/{video_id}/transcription/:
+ get:
+ summary: Get video transcription
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: video_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^m-"
+ example: "m-12345"
+ - name: engine
+ in: query
+ schema:
+ type: string
+ default: "default"
+ example: "default"
+ - name: start
+ in: query
+ schema:
+ type: number
+ default: 0
+ example: 10.5
+ - name: end
+ in: query
+ schema:
+ type: number
+ default: -1
+ example: 60.0
+ - name: segmenter
+ in: query
+ schema:
+ type: string
+ default: "word"
+ example: "word"
+ - name: length
+ in: query
+ schema:
+ type: integer
+ default: 1
+ example: 1
+ responses:
+ '200':
+ description: Transcription data
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ status:
+ type: string
+ enum: [completed, processing, failed]
+ example: "completed"
+ data:
+ type: object
+ properties:
+ transcript:
+ type: array
+ items:
+ type: object
+ properties:
+ text:
+ type: string
+ example: "Hello world"
+ start:
+ type: number
+ example: 1.5
+ end:
+ type: number
+ example: 3.2
+
+ post:
+ summary: Generate video transcription
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: video_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^m-"
+ example: "m-12345"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ engine:
+ type: string
+ default: "default"
+ example: "default"
+ force:
+ type: boolean
+ example: false
+ language_code:
+ type: string
+ example: "en-US"
+ callback_url:
+ type: string
+ example: "https://webhook.example.com/callback"
+ callback_data:
+ type: object
+ responses:
+ '200':
+ description: Transcription job started
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - $ref: '#/components/schemas/AsyncResponse'
+ - type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ message:
+ type: string
+ example: "transcription already exists"
+
+ /video/{video_id}/index/:
+ get:
+ summary: Get video index status
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: video_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^m-"
+ example: "m-12345"
+ - name: index_type
+ in: query
+ required: true
+ schema:
+ type: string
+ enum: [spoken_word, scene]
+ example: "spoken_word"
+ - name: engine
+ in: query
+ schema:
+ type: string
+ default: "default"
+ example: "default"
+ responses:
+ '200':
+ description: Index status
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ status:
+ type: string
+ enum: [done, processing, failed]
+ example: "done"
+ message:
+ type: string
+ example: "Index is available"
+
+ post:
+ summary: Create video index
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: video_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^m-"
+ example: "m-12345"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ index_type:
+ type: string
+ enum: [spoken_word, scene]
+ default: "spoken_word"
+ example: "spoken_word"
+ engine:
+ type: string
+ default: "default"
+ example: "default"
+ force:
+ type: boolean
+ example: false
+ language_code:
+ type: string
+ example: "en-US"
+ segmentation_type:
+ type: string
+ default: "sentence"
+ example: "sentence"
+ callback_url:
+ type: string
+ example: "https://webhook.example.com/callback"
+ responses:
+ '200':
+ description: Index job started
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AsyncResponse'
+
+ /video/{video_id}/search/:
+ post:
+ summary: Search within video
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: video_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^m-"
+ example: "m-12345"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - query
+ properties:
+ query:
+ type: string
+ example: "search query"
+ index_type:
+ type: string
+ enum: [spoken_word, scene]
+ example: "spoken_word"
+ search_type:
+ type: string
+ enum: [semantic, keyword]
+ example: "semantic"
+ score_threshold:
+ type: number
+ example: 0.2
+ result_threshold:
+ type: integer
+ example: 10
+ stitch:
+ type: boolean
+ example: true
+ scene_index_id:
+ type: string
+ example: "idx-12345"
+ filter:
+ type: array
+ items:
+ type: object
+ responses:
+ '200':
+ description: Search results
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/SearchResult'
+
+ /video/{video_id}/scenes/:
+ get:
+ summary: Get video scenes
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: video_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^m-"
+ example: "m-12345"
+ responses:
+ '200':
+ description: List of scenes
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: array
+ items:
+ type: object
+ properties:
+ scene_id:
+ type: string
+ example: "scene-123"
+ start_time:
+ type: number
+ example: 10.5
+ end_time:
+ type: number
+ example: 25.3
+ description:
+ type: string
+ example: "Scene description"
+ thumbnail_url:
+ type: string
+ example: "https://assets.videodb.io/scene/123.jpg"
+
+ post:
+ summary: Create video scenes
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: video_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^m-"
+ example: "m-12345"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ scene_type:
+ type: string
+ enum: [shot, time_based]
+ example: "shot"
+ callback_url:
+ type: string
+ example: "https://webhook.example.com/callback"
+ responses:
+ '200':
+ description: Scene creation started
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AsyncResponse'
+
+ /video/{video_id}/scene/{scene_id}/describe/:
+ post:
+ summary: Describe video scene
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: video_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^m-"
+ example: "m-12345"
+ - name: scene_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "scene-123"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ prompt:
+ type: string
+ example: "Describe what happens in this scene"
+ model_name:
+ type: string
+ example: "gpt-4"
+ responses:
+ '200':
+ description: Scene description generated
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ description:
+ type: string
+ example: "Scene description text"
+
+ /video/{video_id}/frame/{frame_id}/describe/:
+ post:
+ summary: Describe video frame
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: video_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^m-"
+ example: "m-12345"
+ - name: frame_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "frame-123"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ prompt:
+ type: string
+ example: "Describe this frame"
+ model_name:
+ type: string
+ example: "gpt-4-vision"
+ responses:
+ '200':
+ description: Frame description generated
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ description:
+ type: string
+ example: "Frame description text"
+
+ /video/{video_id}/clip:
+ post:
+ summary: Generate video clip
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: video_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^m-"
+ example: "m-12345"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - prompt
+ properties:
+ prompt:
+ type: string
+ example: "Create a clip about the introduction"
+ content_type:
+ type: string
+ default: "spoken"
+ example: "spoken"
+ model_name:
+ type: string
+ default: "basic"
+ example: "basic"
+ scene_index_id:
+ type: string
+ example: "idx-12345"
+ callback_url:
+ type: string
+ example: "https://webhook.example.com/callback"
+ responses:
+ '200':
+ description: Clip generation started
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AsyncResponse'
+
+ /video/{video_id}/workflow/:
+ post:
+ summary: Execute video workflow
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: video_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^m-"
+ example: "m-12345"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - workflow_type
+ properties:
+ workflow_type:
+ type: string
+ enum: [transcribe, index, analyze]
+ example: "transcribe"
+ config:
+ type: object
+ properties:
+ language:
+ type: string
+ example: "en"
+ model:
+ type: string
+ example: "gpt-4"
+ responses:
+ '200':
+ description: Workflow started
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AsyncResponse'
+
+ /audio/:
+ get:
+ summary: List audios
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: collection_id
+ in: query
+ schema:
+ type: string
+ example: "default"
+ responses:
+ '200':
+ description: List of audios
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ audios:
+ type: array
+ items:
+ $ref: '#/components/schemas/Audio'
+
+ /audio/{audio_id}:
+ get:
+ summary: Get audio details
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: audio_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^a-"
+ example: "a-12345"
+ - name: collection_id
+ in: query
+ schema:
+ type: string
+ example: "default"
+ responses:
+ '200':
+ description: Audio details
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ $ref: '#/components/schemas/Audio'
+
+ patch:
+ summary: Update audio
+ tags:
+ - Audio
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: audio_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^a-"
+ example: "a-12345"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ name:
+ type: string
+ example: "Updated Audio Name"
+ responses:
+ '200':
+ description: Audio updated
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ $ref: '#/components/schemas/Audio'
+
+ delete:
+ summary: Delete audio
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: audio_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^a-"
+ example: "a-12345"
+ responses:
+ '200':
+ description: Audio deleted
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/SuccessResponse'
+
+ /audio/{audio_id}/generate_url:
+ post:
+ summary: Generate audio stream URL
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: audio_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^a-"
+ example: "a-12345"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ format:
+ type: string
+ enum: [mp3, wav, flac]
+ example: "mp3"
+ quality:
+ type: string
+ enum: [low, medium, high]
+ example: "high"
+ responses:
+ '200':
+ description: Stream URL generated
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ stream_url:
+ type: string
+ example: "https://stream.videodb.io/a/12345"
+
+ /image/:
+ get:
+ summary: List images
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: collection_id
+ in: query
+ schema:
+ type: string
+ example: "default"
+ responses:
+ '200':
+ description: List of images
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ images:
+ type: array
+ items:
+ $ref: '#/components/schemas/Image'
+
+ /image/{image_id}:
+ get:
+ summary: Get image details
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: image_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^img-"
+ example: "img-12345"
+ - name: collection_id
+ in: query
+ schema:
+ type: string
+ example: "default"
+ responses:
+ '200':
+ description: Image details
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ $ref: '#/components/schemas/Image'
+
+ patch:
+ summary: Update image
+ tags:
+ - Images
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: image_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^img-"
+ example: "img-12345"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ name:
+ type: string
+ example: "Updated Image Name"
+ responses:
+ '200':
+ description: Image updated
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ $ref: '#/components/schemas/Image'
+
+ delete:
+ summary: Delete image
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: image_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^img-"
+ example: "img-12345"
+ responses:
+ '200':
+ description: Image deleted
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/SuccessResponse'
+
+ /image/{image_id}/generate_url:
+ post:
+ summary: Generate image URL
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: image_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^img-"
+ example: "img-12345"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ format:
+ type: string
+ enum: [jpg, png, webp]
+ example: "jpg"
+ quality:
+ type: integer
+ minimum: 1
+ maximum: 100
+ example: 90
+ width:
+ type: integer
+ example: 1024
+ height:
+ type: integer
+ example: 768
+ responses:
+ '200':
+ description: Image URL generated
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ image_url:
+ type: string
+ example: "https://assets.videodb.io/img/12345.jpg"
+
+ /collection/{collection_id}/generate/image/:
+ post:
+ summary: Generate image using AI
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: collection_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "default"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - prompt
+ properties:
+ prompt:
+ type: string
+ example: "A beautiful sunset over mountains"
+ aspect_ratio:
+ type: string
+ example: "16:9"
+ callback_url:
+ type: string
+ example: "https://webhook.example.com/callback"
+ responses:
+ '200':
+ description: Image generation started
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AsyncResponse'
+
+ /collection/{collection_id}/generate/video/:
+ post:
+ summary: Generate video using AI
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: collection_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "default"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - prompt
+ properties:
+ prompt:
+ type: string
+ example: "A cat playing with a ball"
+ duration:
+ type: number
+ example: 5
+ callback_url:
+ type: string
+ example: "https://webhook.example.com/callback"
+ responses:
+ '200':
+ description: Video generation started
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AsyncResponse'
+
+ /collection/{collection_id}/generate/audio/:
+ post:
+ summary: Generate audio using AI
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: collection_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "default"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - prompt
+ - audio_type
+ properties:
+ prompt:
+ type: string
+ example: "Generate upbeat background music"
+ audio_type:
+ type: string
+ enum: [speech, sound_effect, music]
+ example: "music"
+ callback_url:
+ type: string
+ example: "https://webhook.example.com/callback"
+ responses:
+ '200':
+ description: Audio generation started
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AsyncResponse'
+
+ /collection/{collection_id}/generate/text/:
+ post:
+ summary: Generate text using AI
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: collection_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "default"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - prompt
+ properties:
+ prompt:
+ type: string
+ example: "Summarize the content of this video"
+ video_id:
+ type: string
+ example: "m-12345"
+ model_name:
+ type: string
+ example: "gpt-4"
+ max_tokens:
+ type: integer
+ example: 500
+ temperature:
+ type: number
+ example: 0.7
+ callback_url:
+ type: string
+ example: "https://webhook.example.com/callback"
+ responses:
+ '200':
+ description: Text generation started or completed
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - $ref: '#/components/schemas/AsyncResponse'
+ - type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ output:
+ type: string
+ example: "Generated text content"
+
+ /timeline:
+ post:
+ summary: Compile timeline
+ security:
+ - ApiKeyAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - request_type
+ - timeline
+ properties:
+ request_type:
+ type: string
+ enum: [compile]
+ example: "compile"
+ timeline:
+ type: array
+ items:
+ $ref: '#/components/schemas/Timeline'
+ responses:
+ '200':
+ description: Timeline compilation result
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ stream_url:
+ type: string
+ example: "https://stream.videodb.io/compiled/12345"
+
+ /billing/usage:
+ get:
+ summary: Get billing usage information
+ security:
+ - ApiKeyAuth: []
+ responses:
+ '200':
+ description: Billing usage data
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ $ref: '#/components/schemas/BillingUsage'
+
+ /billing/checkout:
+ post:
+ summary: Create billing checkout session
+ security:
+ - ApiKeyAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ mode:
+ type: string
+ enum: [payment, subscription]
+ example: "payment"
+ plan_id:
+ type: string
+ example: "plan-basic"
+ amount:
+ type: number
+ example: 100
+ responses:
+ '200':
+ description: Checkout URL
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ url:
+ type: string
+ example: "https://checkout.stripe.com/pay/xxx"
+
+ /billing/checkouts:
+ get:
+ summary: Get billing checkout history
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: limit
+ in: query
+ schema:
+ type: integer
+ example: 10
+ - name: offset
+ in: query
+ schema:
+ type: integer
+ example: 0
+ responses:
+ '200':
+ description: Checkout history
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: array
+ items:
+ type: object
+ properties:
+ id:
+ type: string
+ example: "cs_test_xxx"
+ amount:
+ type: number
+ example: 100
+ currency:
+ type: string
+ example: "usd"
+ status:
+ type: string
+ example: "completed"
+ created_at:
+ type: string
+ format: date-time
+
+ /billing/invoices:
+ get:
+ summary: Get billing invoices
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: limit
+ in: query
+ schema:
+ type: integer
+ example: 10
+ - name: offset
+ in: query
+ schema:
+ type: integer
+ example: 0
+ responses:
+ '200':
+ description: Invoice list
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: array
+ items:
+ type: object
+ properties:
+ id:
+ type: string
+ example: "in_xxx"
+ amount:
+ type: number
+ example: 100
+ currency:
+ type: string
+ example: "usd"
+ status:
+ type: string
+ example: "paid"
+ pdf_url:
+ type: string
+ example: "https://invoice.stripe.com/pdf/xxx"
+ created_at:
+ type: string
+ format: date-time
+
+ /billing/topup:
+ post:
+ summary: Create topup payment
+ security:
+ - ApiKeyAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - amount
+ properties:
+ amount:
+ type: number
+ example: 50
+ currency:
+ type: string
+ example: "usd"
+ responses:
+ '200':
+ description: Topup checkout URL
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ url:
+ type: string
+ example: "https://checkout.stripe.com/pay/xxx"
+
+ /billing/auto_recharge:
+ get:
+ summary: Get auto recharge settings
+ security:
+ - ApiKeyAuth: []
+ responses:
+ '200':
+ description: Auto recharge settings
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ enabled:
+ type: boolean
+ example: true
+ threshold:
+ type: number
+ example: 10
+ amount:
+ type: number
+ example: 50
+
+ post:
+ summary: Update auto recharge settings
+ security:
+ - ApiKeyAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ enabled:
+ type: boolean
+ example: true
+ threshold:
+ type: number
+ example: 10
+ amount:
+ type: number
+ example: 50
+ responses:
+ '200':
+ description: Auto recharge updated
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ message:
+ type: string
+ example: "Auto recharge settings updated"
+
+ /async-response/{response_id}:
+ get:
+ summary: Get async operation result
+ parameters:
+ - name: response_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "job-12345"
+ responses:
+ '200':
+ description: Operation result
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ status:
+ type: string
+ enum: [processing, done, failed]
+ example: "done"
+ data:
+ type: object
+ description: "Result data varies by operation type"
+ '404':
+ description: Response not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+
+ /download:
+ get:
+ summary: List download entries
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: page_index
+ in: query
+ schema:
+ type: integer
+ example: 0
+ - name: count
+ in: query
+ schema:
+ type: integer
+ maximum: 5000
+ example: 50
+ responses:
+ '200':
+ description: List of downloads
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ downloads:
+ type: array
+ items:
+ type: object
+ properties:
+ id:
+ type: string
+ example: "download-12345"
+ name:
+ type: string
+ example: "video_download.mp4"
+ status:
+ type: string
+ enum: [processing, done, error]
+ example: "done"
+ created_at:
+ type: string
+ format: date-time
+ download_url:
+ type: string
+ example: "https://example.com/download/video.mp4"
+
+ post:
+ summary: Create download request
+ security:
+ - ApiKeyAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - stream_link
+ properties:
+ stream_link:
+ type: string
+ example: "https://stream.videodb.io/v/12345"
+ name:
+ type: string
+ example: "my_download.mp4"
+ responses:
+ '200':
+ description: Download initiated
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AsyncResponse'
+
+ delete:
+ summary: Delete download entry
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: download_id
+ in: query
+ required: true
+ schema:
+ type: string
+ example: "download-12345"
+ responses:
+ '200':
+ description: Download deleted
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/SuccessResponse'
+
+ /download/{download_id}:
+ get:
+ summary: Get download status/details
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: download_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "download-12345"
+ responses:
+ '200':
+ description: Download status
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - $ref: '#/components/schemas/AsyncResponse'
+ - type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ id:
+ type: string
+ example: "download-12345"
+ name:
+ type: string
+ example: "video_download.mp4"
+ status:
+ type: string
+ enum: [processing, done, error]
+ example: "done"
+ download_url:
+ type: string
+ example: "https://example.com/download/video.mp4"
+ created_at:
+ type: string
+ format: date-time
+
+ post:
+ summary: Retry download
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: download_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "download-12345"
+ responses:
+ '200':
+ description: Download retry initiated
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AsyncResponse'
+
+ /chat/completions:
+ post:
+ summary: OpenAI-compatible chat completions proxy
+ description: Proxy endpoint for OpenAI chat completions API with VideoDB billing
+ security:
+ - ApiKeyAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - model
+ - messages
+ properties:
+ model:
+ type: string
+ enum: [gpt-4o-2024-11-20]
+ example: "gpt-4o-2024-11-20"
+ messages:
+ type: array
+ items:
+ type: object
+ properties:
+ role:
+ type: string
+ enum: [system, user, assistant]
+ example: "user"
+ content:
+ type: string
+ example: "Hello, how are you?"
+ max_tokens:
+ type: integer
+ example: 100
+ temperature:
+ type: number
+ example: 0.7
+ stream:
+ type: boolean
+ example: false
+ responses:
+ '200':
+ description: Chat completion response
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ id:
+ type: string
+ example: "chatcmpl-123"
+ object:
+ type: string
+ example: "chat.completion"
+ created:
+ type: integer
+ example: 1677652288
+ model:
+ type: string
+ example: "gpt-4o-2024-11-20"
+ choices:
+ type: array
+ items:
+ type: object
+ properties:
+ index:
+ type: integer
+ example: 0
+ message:
+ type: object
+ properties:
+ role:
+ type: string
+ example: "assistant"
+ content:
+ type: string
+ example: "Hello! I'm doing well, thank you for asking."
+ finish_reason:
+ type: string
+ example: "stop"
+ usage:
+ type: object
+ properties:
+ prompt_tokens:
+ type: integer
+ example: 10
+ completion_tokens:
+ type: integer
+ example: 15
+ total_tokens:
+ type: integer
+ example: 25
+
+ /timeline_v2:
+ post:
+ summary: Compile timeline (v2)
+ security:
+ - ApiKeyAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - request_type
+ - timeline
+ properties:
+ request_type:
+ type: string
+ enum: [compile]
+ example: "compile"
+ timeline:
+ type: array
+ items:
+ $ref: '#/components/schemas/Timeline'
+ output_format:
+ type: string
+ enum: [mp4, webm, hls]
+ example: "mp4"
+ quality:
+ type: string
+ enum: [low, medium, high]
+ example: "high"
+ responses:
+ '200':
+ description: Timeline compilation result
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ stream_url:
+ type: string
+ example: "https://stream.videodb.io/compiled/12345"
+ duration:
+ type: number
+ example: 120.5
+ format:
+ type: string
+ example: "mp4"
+
+ /timeline_v2/download:
+ post:
+ summary: Download compiled timeline
+ security:
+ - ApiKeyAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - timeline_id
+ properties:
+ timeline_id:
+ type: string
+ example: "timeline-12345"
+ format:
+ type: string
+ enum: [mp4, webm, avi]
+ example: "mp4"
+ quality:
+ type: string
+ enum: [low, medium, high]
+ example: "high"
+ responses:
+ '200':
+ description: Download initiated
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AsyncResponse'
+
+ /compile/:
+ post:
+ summary: Compile media content
+ security:
+ - ApiKeyAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - inputs
+ properties:
+ inputs:
+ type: array
+ items:
+ type: object
+ properties:
+ media_id:
+ type: string
+ example: "m-12345"
+ start_time:
+ type: number
+ example: 10.0
+ end_time:
+ type: number
+ example: 30.0
+ output_format:
+ type: string
+ enum: [mp4, webm, hls]
+ example: "mp4"
+ callback_url:
+ type: string
+ example: "https://webhook.example.com/callback"
+ responses:
+ '200':
+ description: Compilation started
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AsyncResponse'
+
+ /video/{video_id}/index/scene/:
+ get:
+ summary: Get video scene index status
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: video_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^m-"
+ example: "m-12345"
+ responses:
+ '200':
+ description: Scene index status
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ status:
+ type: string
+ enum: [done, processing, failed]
+ example: "done"
+ data:
+ type: object
+ properties:
+ scene_count:
+ type: integer
+ example: 25
+ total_duration:
+ type: number
+ example: 120.5
+ last_updated:
+ type: string
+ format: date-time
+
+ post:
+ summary: Create video scene index
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: video_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^m-"
+ example: "m-12345"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ scene_type:
+ type: string
+ enum: [shot, time_based]
+ example: "shot"
+ segmentation_threshold:
+ type: number
+ example: 0.8
+ callback_url:
+ type: string
+ example: "https://webhook.example.com/callback"
+ responses:
+ '200':
+ description: Scene index creation started
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AsyncResponse'
+
+ /video/{video_id}/index/scene/{scene_index_id}:
+ get:
+ summary: Get scene index details
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: video_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^m-"
+ example: "m-12345"
+ - name: scene_index_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "scene-idx-12345"
+ responses:
+ '200':
+ description: Scene index details
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ id:
+ type: string
+ example: "scene-idx-12345"
+ video_id:
+ type: string
+ example: "m-12345"
+ scene_type:
+ type: string
+ example: "shot"
+ status:
+ type: string
+ enum: [done, processing, failed]
+ example: "done"
+ scenes:
+ type: array
+ items:
+ type: object
+ properties:
+ start_time:
+ type: number
+ example: 10.5
+ end_time:
+ type: number
+ example: 25.3
+ confidence:
+ type: number
+ example: 0.85
+
+ delete:
+ summary: Delete scene index
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: video_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^m-"
+ example: "m-12345"
+ - name: scene_index_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "scene-idx-12345"
+ responses:
+ '200':
+ description: Scene index deleted
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/SuccessResponse'
+
+ /audio/{audio_id}/transcription/:
+ get:
+ summary: Get audio transcription
+ tags:
+ - Audio
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: audio_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^a-"
+ example: "a-12345"
+ - name: engine
+ in: query
+ schema:
+ type: string
+ example: "default"
+ - name: start
+ in: query
+ schema:
+ type: number
+ default: 0
+ example: 0
+ - name: end
+ in: query
+ schema:
+ type: number
+ default: -1
+ example: 60.0
+ responses:
+ '200':
+ description: Audio transcription data
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ status:
+ type: string
+ enum: [completed, processing, failed]
+ example: "completed"
+ data:
+ type: object
+ properties:
+ transcript:
+ type: array
+ items:
+ type: object
+ properties:
+ text:
+ type: string
+ example: "Hello world"
+ start:
+ type: number
+ example: 1.5
+ end:
+ type: number
+ example: 3.2
+ '404':
+ description: Transcription not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+
+ post:
+ summary: Generate audio transcription
+ tags:
+ - Audio
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: audio_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^a-"
+ example: "a-12345"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ engine:
+ type: string
+ default: "default"
+ example: "default"
+ language_code:
+ type: string
+ default: "en"
+ example: "en"
+ force:
+ type: boolean
+ default: false
+ example: false
+ callback_url:
+ type: string
+ example: "https://webhook.example.com/callback"
+ callback_data:
+ type: object
+ responses:
+ '200':
+ description: Transcription job started
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - $ref: '#/components/schemas/AsyncResponse'
+ - type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ message:
+ type: string
+ example: "transcription already exists"
+
+ /collection/{collection_id}/upload_url:
+ get:
+ summary: Get presigned upload URL
+ tags:
+ - Collections
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: collection_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "default"
+ - name: name
+ in: query
+ schema:
+ type: string
+ example: "my_video.mp4"
+ responses:
+ '200':
+ description: Upload URL generated
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ upload_url:
+ type: string
+ example: "https://s3.amazonaws.com/..."
+ video_id:
+ type: string
+ example: "m-12345"
+
+ /collection/{collection_id}/websocket:
+ get:
+ summary: Get WebSocket connection URL
+ tags:
+ - Collections
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: collection_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "default"
+ responses:
+ '200':
+ description: WebSocket URL
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ websocket_url:
+ type: string
+ example: "wss://ws.videodb.io/..."
+
+ /collection/{collection_id}/search/title/:
+ post:
+ summary: Search by title within collection
+ tags:
+ - Search
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: collection_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "default"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - query
+ properties:
+ query:
+ type: string
+ example: "search query"
+ search_type:
+ type: string
+ default: "llm"
+ example: "llm"
+ responses:
+ '200':
+ description: Title search results
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: array
+ items:
+ type: object
+
+ /collection/{collection_id}/search/web/:
+ post:
+ summary: Web search within collection
+ tags:
+ - Search
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: collection_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "default"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - query
+ properties:
+ query:
+ type: string
+ example: "search query"
+ responses:
+ '200':
+ description: Web search results
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+
+ /collection/{collection_id}/generate/video/dub:
+ post:
+ summary: Dub video with AI-generated audio
+ tags:
+ - AI Generation
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: collection_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "default"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ video_id:
+ type: string
+ example: "m-12345"
+ target_language:
+ type: string
+ example: "es"
+ callback_url:
+ type: string
+ example: "https://webhook.example.com/callback"
+ callback_data:
+ type: object
+ responses:
+ '200':
+ description: Dubbing job started
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AsyncResponse'
+
+ /collection/{collection_id}/video/{video_id}/translate:
+ post:
+ summary: Translate video content
+ tags:
+ - AI Generation
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: collection_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "default"
+ - name: video_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^m-"
+ example: "m-12345"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ target_language:
+ type: string
+ example: "es"
+ callback_url:
+ type: string
+ example: "https://webhook.example.com/callback"
+ callback_data:
+ type: object
+ responses:
+ '200':
+ description: Translation job started
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AsyncResponse'
+
+ /video/{video_id}/scenes/{scene_collection_id}/:
+ get:
+ summary: Get scene collection details
+ tags:
+ - Videos
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: video_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^m-"
+ example: "m-12345"
+ - name: scene_collection_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "sc-12345"
+ - name: offset
+ in: query
+ schema:
+ type: integer
+ example: 0
+ - name: limit
+ in: query
+ schema:
+ type: integer
+ example: 100
+ responses:
+ '200':
+ description: Scene collection details
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ id:
+ type: string
+ example: "sc-12345"
+ scenes:
+ type: array
+ items:
+ type: object
+ properties:
+ scene_id:
+ type: string
+ start:
+ type: number
+ end:
+ type: number
+ description:
+ type: string
+
+ patch:
+ summary: Update scene collection
+ tags:
+ - Videos
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: video_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^m-"
+ example: "m-12345"
+ - name: scene_collection_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "sc-12345"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - scenes
+ properties:
+ scenes:
+ type: array
+ items:
+ type: object
+ responses:
+ '200':
+ description: Scene collection updated
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/SuccessResponse'
+
+ delete:
+ summary: Delete scene collection
+ tags:
+ - Videos
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: video_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^m-"
+ example: "m-12345"
+ - name: scene_collection_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "sc-12345"
+ responses:
+ '200':
+ description: Scene collection deleted
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AsyncResponse'
+
+ /video/{video_id}/index/delete:
+ post:
+ summary: Delete video index
+ tags:
+ - Videos
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: video_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^m-"
+ example: "m-12345"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ index_type:
+ type: string
+ enum: [spoken_word, scene, all]
+ default: "all"
+ example: "all"
+ model_name:
+ type: string
+ default: "gpt4-v"
+ example: "gpt4-v"
+ responses:
+ '200':
+ description: Index deleted
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/SuccessResponse'
+
+ /video/{video_id}/reframe:
+ post:
+ summary: Reframe video to different aspect ratio
+ tags:
+ - Videos
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: video_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^m-"
+ example: "m-12345"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - target
+ - mode
+ properties:
+ target:
+ type: string
+ example: "9:16"
+ mode:
+ type: string
+ example: "auto"
+ start:
+ type: number
+ default: 0
+ example: 0
+ end:
+ type: number
+ example: 30
+ callback_url:
+ type: string
+ example: "https://webhook.example.com/callback"
+ callback_data:
+ type: object
+ responses:
+ '200':
+ description: Reframe job started
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AsyncResponse'
+
+ /video/{video_id}/reframe/{reframe_id}:
+ get:
+ summary: Get reframe job status
+ tags:
+ - Videos
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: video_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^m-"
+ example: "m-12345"
+ - name: reframe_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "reframe-12345"
+ responses:
+ '200':
+ description: Reframe job status
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ status:
+ type: string
+ enum: [processing, done, failed]
+ example: "done"
+ data:
+ type: object
+ '404':
+ description: Reframe job not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+
+ /assets:
+ get:
+ summary: List all assets across collections
+ tags:
+ - Assets
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: collection_id
+ in: query
+ schema:
+ type: string
+ example: "default"
+ - name: asset_type
+ in: query
+ description: Comma-separated asset types
+ schema:
+ type: string
+ example: "video,audio,image"
+ - name: sort_by
+ in: query
+ schema:
+ type: string
+ enum: [name, duration, size, created_at]
+ default: "created_at"
+ example: "created_at"
+ - name: sort_order
+ in: query
+ schema:
+ type: string
+ enum: [asc, desc]
+ default: "desc"
+ example: "desc"
+ - name: min_duration
+ in: query
+ schema:
+ type: number
+ example: 10
+ - name: max_duration
+ in: query
+ schema:
+ type: number
+ example: 300
+ - name: min_size
+ in: query
+ schema:
+ type: number
+ example: 1024
+ - name: max_size
+ in: query
+ schema:
+ type: number
+ example: 104857600
+ - name: name_pattern
+ in: query
+ description: Regex pattern for name filter
+ schema:
+ type: string
+ example: ".*intro.*"
+ - name: page
+ in: query
+ schema:
+ type: integer
+ default: 1
+ example: 1
+ - name: page_size
+ in: query
+ schema:
+ type: integer
+ default: 50000
+ maximum: 50000
+ example: 100
+ responses:
+ '200':
+ description: List of assets
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: array
+ items:
+ type: object
+
+ /user/api-key-collections:
+ put:
+ summary: Set API key collection scoping
+ tags:
+ - Authentication
+ security:
+ - ApiKeyAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - api_key
+ - collection_ids
+ properties:
+ api_key:
+ type: string
+ example: "sk-xxx"
+ collection_ids:
+ type: array
+ items:
+ type: string
+ example: ["default", "c-12345"]
+ responses:
+ '200':
+ description: API key collections updated
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/SuccessResponse'
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '403':
+ description: Pro-only feature
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+
+ /editor:
+ post:
+ summary: Compile editor timeline
+ tags:
+ - Editor
+ security:
+ - ApiKeyAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ description: Timeline compilation payload with tracks and clips
+ responses:
+ '200':
+ description: Timeline compilation started
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AsyncResponse'
+
+ /editor/download:
+ post:
+ summary: Download compiled editor timeline
+ tags:
+ - Editor
+ security:
+ - ApiKeyAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - stream_url
+ properties:
+ stream_url:
+ type: string
+ example: "https://stream.videodb.io/compiled/12345"
+ responses:
+ '200':
+ description: Download URL generated
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ download_url:
+ type: string
+ example: "https://download.videodb.io/..."
+
+ /transcode:
+ post:
+ summary: Start transcode job
+ tags:
+ - Transcode
+ security:
+ - ApiKeyAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - source
+ properties:
+ source:
+ type: string
+ description: Source video ID
+ example: "m-12345"
+ responses:
+ '200':
+ description: Transcode job started
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ job_id:
+ type: string
+ example: "job-12345"
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+
+ /transcode/{job_id}:
+ get:
+ summary: Get transcode job status
+ tags:
+ - Transcode
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: job_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "job-12345"
+ responses:
+ '200':
+ description: Transcode job details
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+
+ /collection/{collection_id}/meeting/record:
+ post:
+ summary: Record a meeting
+ tags:
+ - Meeting
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: collection_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "default"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - meeting_url
+ properties:
+ meeting_url:
+ type: string
+ example: "https://meet.google.com/abc-def-ghi"
+ bot_name:
+ type: string
+ default: "VideoDB Assistant"
+ example: "VideoDB Assistant"
+ meeting_title:
+ type: string
+ example: "Weekly standup"
+ time_zone:
+ type: string
+ default: "UTC"
+ example: "UTC"
+ bot_image_url:
+ type: string
+ example: "https://example.com/bot-avatar.png"
+ realtime_stream:
+ type: boolean
+ default: false
+ example: false
+ callback_url:
+ type: string
+ example: "https://webhook.example.com/callback"
+ callback_data:
+ type: object
+ responses:
+ '200':
+ description: Meeting recording started
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ bot_id:
+ type: string
+ example: "bot-12345"
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+
+ /collection/{collection_id}/meeting/{bot_id}:
+ get:
+ summary: Get meeting recording information
+ tags:
+ - Meeting
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: collection_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "default"
+ - name: bot_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "bot-12345"
+ responses:
+ '200':
+ description: Meeting information
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ bot_id:
+ type: string
+ example: "bot-12345"
+ status:
+ type: string
+ example: "recording"
+ video_url:
+ type: string
+ example: "https://stream.videodb.io/v/12345"
+ '400':
+ description: Invalid meeting ID
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+
+ /video/{video_id}/meeting:
+ get:
+ summary: Get meeting by video
+ tags:
+ - Meeting
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: video_id
+ in: path
+ required: true
+ schema:
+ type: string
+ pattern: "^m-"
+ example: "m-12345"
+ responses:
+ '200':
+ description: Meeting information for video
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+
+ /collection/{collection_id}/capture/session:
+ post:
+ summary: Create capture session
+ tags:
+ - Capture
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: collection_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "default"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - end_user_id
+ properties:
+ end_user_id:
+ type: string
+ example: "user-123"
+ callback_url:
+ type: string
+ example: "https://webhook.example.com/callback"
+ ws_connection_id:
+ type: string
+ example: "conn-123"
+ metadata:
+ type: object
+ responses:
+ '200':
+ description: Capture session created
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ $ref: '#/components/schemas/CaptureSession'
+ '400':
+ description: Validation error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+
+ get:
+ summary: List capture sessions
+ tags:
+ - Capture
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: collection_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "default"
+ - name: status
+ in: query
+ schema:
+ type: string
+ enum: [created, starting, active, stopped, failed]
+ example: "active"
+ responses:
+ '200':
+ description: List of capture sessions
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ sessions:
+ type: array
+ items:
+ $ref: '#/components/schemas/CaptureSession'
+ next_page:
+ type: string
+ nullable: true
+
+ /collection/{collection_id}/capture/session/{session_id}:
+ get:
+ summary: Get capture session details
+ tags:
+ - Capture
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: collection_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "default"
+ - name: session_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "capture-12345"
+ responses:
+ '200':
+ description: Capture session details
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ $ref: '#/components/schemas/CaptureSession'
+ '404':
+ description: Session not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+
+ /capture/session/token:
+ post:
+ summary: Create capture session token
+ tags:
+ - Capture
+ security:
+ - ApiKeyAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ user_id:
+ type: string
+ description: End user identifier for partner tracking
+ example: "user-123"
+ expires_in:
+ type: integer
+ description: Token validity in seconds
+ default: 86400
+ example: 86400
+ responses:
+ '200':
+ description: Session token created
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ token:
+ type: string
+ example: "st-xxx"
+ expires_at:
+ type: number
+ example: 1700000000
+ expires_in:
+ type: integer
+ example: 86400
+
+ /capture/session/start:
+ post:
+ summary: Start capture session
+ tags:
+ - Capture
+ security:
+ - ApiKeyAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - session_id
+ - channels
+ properties:
+ session_id:
+ type: string
+ example: "capture-12345"
+ channels:
+ type: array
+ items:
+ type: object
+ required:
+ - channel_id
+ properties:
+ channel_id:
+ type: string
+ example: "mic"
+ channel_name:
+ type: string
+ example: "Microphone"
+ type:
+ type: string
+ enum: [audio, video]
+ default: "audio"
+ example: "audio"
+ store:
+ type: boolean
+ default: true
+ example: true
+ ws_connection_id:
+ type: string
+ example: "conn-123"
+ callback_url:
+ type: string
+ example: "https://webhook.example.com/callback"
+ responses:
+ '200':
+ description: Capture session started
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ session_id:
+ type: string
+ example: "capture-12345"
+ status:
+ type: string
+ example: "starting"
+ '400':
+ description: Validation error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '402':
+ description: Insufficient credits
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+
+ /capture/session/{streaming_session_id}:
+ get:
+ summary: Get capture session with RTSP URLs
+ tags:
+ - Capture
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: streaming_session_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "capture-12345"
+ responses:
+ '200':
+ description: Session details with RTSP URLs
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ session_id:
+ type: string
+ example: "capture-12345"
+ status:
+ type: string
+ example: "active"
+ channels:
+ type: array
+ items:
+ type: object
+ properties:
+ channel_id:
+ type: string
+ rtsp_url:
+ type: string
+ '404':
+ description: Session not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+
+ /capture/session/{streaming_session_id}/stop:
+ post:
+ summary: Stop capture session
+ tags:
+ - Capture
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: streaming_session_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "capture-12345"
+ responses:
+ '200':
+ description: Session stopped
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/SuccessResponse'
+
+ /rtstream/:
+ get:
+ summary: List RTStreams
+ tags:
+ - RTStream
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: limit
+ in: query
+ schema:
+ type: integer
+ default: 10
+ example: 10
+ - name: offset
+ in: query
+ schema:
+ type: integer
+ default: 0
+ example: 0
+ - name: status
+ in: query
+ schema:
+ type: string
+ enum: [connected, stopped]
+ example: "connected"
+ - name: name
+ in: query
+ description: Filter by name substring
+ schema:
+ type: string
+ example: "my stream"
+ - name: ordering
+ in: query
+ description: Sort field (prefix with - for descending)
+ schema:
+ type: string
+ example: "-created_at"
+ responses:
+ '200':
+ description: List of RTStreams
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ results:
+ type: array
+ items:
+ $ref: '#/components/schemas/RTStream'
+ count:
+ type: integer
+ example: 25
+ next:
+ type: string
+ nullable: true
+ example: "/rtstream/?limit=10&offset=10"
+ previous:
+ type: string
+ nullable: true
+ example: null
+
+ post:
+ summary: Create RTStream
+ tags:
+ - RTStream
+ security:
+ - ApiKeyAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - url
+ properties:
+ url:
+ type: string
+ description: RTSP or YouTube stream URL
+ example: "rtsp://example.com:8554/stream"
+ name:
+ type: string
+ example: "My Stream"
+ collection_id:
+ type: string
+ default: "default"
+ example: "default"
+ sample_rate:
+ type: integer
+ default: 30
+ example: 30
+ media_types:
+ type: array
+ items:
+ type: string
+ enum: [video, audio]
+ example: ["video", "audio"]
+ store:
+ type: boolean
+ default: false
+ example: false
+ enable_transcript:
+ type: boolean
+ default: true
+ example: true
+ ws_connection_id:
+ type: string
+ example: "conn-123"
+ responses:
+ '201':
+ description: RTStream created
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ $ref: '#/components/schemas/RTStream'
+ '400':
+ description: Validation error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '402':
+ description: Insufficient credits
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+
+ /rtstream/{stream_id}/:
+ get:
+ summary: Get RTStream details
+ tags:
+ - RTStream
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: stream_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "rts-12345"
+ responses:
+ '200':
+ description: RTStream details
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ $ref: '#/components/schemas/RTStream'
+ '404':
+ description: Stream not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+
+ patch:
+ summary: Update RTStream
+ tags:
+ - RTStream
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: stream_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "rts-12345"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ name:
+ type: string
+ example: "Updated Stream Name"
+ sample_rate:
+ type: integer
+ example: 15
+ responses:
+ '200':
+ description: RTStream updated
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ $ref: '#/components/schemas/RTStream'
+ '400':
+ description: Invalid fields
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+
+ /rtstream/{stream_id}/status/:
+ patch:
+ summary: Start or stop RTStream
+ tags:
+ - RTStream
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: stream_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "rts-12345"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - action
+ properties:
+ action:
+ type: string
+ enum: [start, stop]
+ example: "stop"
+ responses:
+ '200':
+ description: RTStream status updated
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/SuccessResponse'
+
+ /rtstream/{stream_id}/export:
+ post:
+ summary: Export RTStream recording as VideoDB asset
+ tags:
+ - RTStream
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: stream_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "rts-12345"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ name:
+ type: string
+ example: "Exported Recording"
+ responses:
+ '200':
+ description: Recording exported
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ video_id:
+ type: string
+ example: "m-12345"
+ name:
+ type: string
+ example: "Exported Recording"
+ stream_url:
+ type: string
+ example: "https://stream.videodb.io/v/12345"
+ player_url:
+ type: string
+ example: "https://console.videodb.io/player/12345"
+ duration:
+ type: number
+ example: 123.45
+ '400':
+ description: No recordings available
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+
+ /rtstream/{stream_id}/index/scene:
+ post:
+ summary: Create RTStream scene index
+ tags:
+ - RTStream
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: stream_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "rts-12345"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ extraction_type:
+ type: string
+ enum: [time, transcript]
+ default: "time"
+ example: "time"
+ extraction_config:
+ type: object
+ properties:
+ time:
+ type: integer
+ example: 10
+ frame_count:
+ type: integer
+ example: 5
+ prompt:
+ type: string
+ example: "Describe the scene"
+ model_name:
+ type: string
+ example: "GPT4o"
+ model_config:
+ type: object
+ name:
+ type: string
+ example: "My Scene Index"
+ ws_connection_id:
+ type: string
+ example: "conn-123"
+ responses:
+ '200':
+ description: Scene index created
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ rtstream_index_id:
+ type: string
+ example: "scene-idx-12345"
+ extraction_type:
+ type: string
+ example: "time"
+ status:
+ type: string
+ example: "running"
+ prompt:
+ type: string
+ example: "Describe the scene"
+ name:
+ type: string
+ example: "My Scene Index"
+
+ get:
+ summary: List RTStream scene indexes
+ tags:
+ - RTStream
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: stream_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "rts-12345"
+ responses:
+ '200':
+ description: List of scene indexes
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ scene_indexes:
+ type: array
+ items:
+ type: object
+ properties:
+ rtstream_index_id:
+ type: string
+ example: "scene-idx-12345"
+ extraction_type:
+ type: string
+ example: "time"
+ status:
+ type: string
+ example: "running"
+ prompt:
+ type: string
+ name:
+ type: string
+
+ /rtstream/{stream_id}/index/{scene_index_id}:
+ get:
+ summary: Get RTStream scene index details
+ tags:
+ - RTStream
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: stream_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "rts-12345"
+ - name: scene_index_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "scene-idx-12345"
+ responses:
+ '200':
+ description: Scene index details
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ rtstream_index_id:
+ type: string
+ example: "scene-idx-12345"
+ extraction_type:
+ type: string
+ example: "time"
+ extraction_config:
+ type: object
+ status:
+ type: string
+ example: "running"
+ prompt:
+ type: string
+ name:
+ type: string
+
+ /rtstream/{stream_id}/index/scene/{scene_index_id}:
+ get:
+ summary: Get RTStream scene records
+ tags:
+ - RTStream
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: stream_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "rts-12345"
+ - name: scene_index_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "scene-idx-12345"
+ - name: page
+ in: query
+ schema:
+ type: integer
+ default: 1
+ example: 1
+ - name: page_size
+ in: query
+ schema:
+ type: integer
+ default: 100
+ example: 100
+ - name: start
+ in: query
+ description: Filter by start timestamp
+ schema:
+ type: number
+ example: 1700000000
+ - name: end
+ in: query
+ description: Filter by end timestamp
+ schema:
+ type: number
+ example: 1700003600
+ responses:
+ '200':
+ description: Scene records
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ scene_index_records:
+ type: array
+ items:
+ type: object
+ properties:
+ start:
+ type: number
+ example: 1700000000
+ end:
+ type: number
+ example: 1700000010
+ description:
+ type: string
+ example: "Scene description"
+ next_page:
+ type: boolean
+ example: false
+
+ patch:
+ summary: Update RTStream scene index prompt
+ tags:
+ - RTStream
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: stream_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "rts-12345"
+ - name: scene_index_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "scene-idx-12345"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - prompt
+ properties:
+ prompt:
+ type: string
+ example: "Updated scene description prompt"
+ responses:
+ '200':
+ description: Scene index prompt updated
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/SuccessResponse'
+
+ /rtstream/{stream_id}/index/scene/{scene_index_id}/status:
+ patch:
+ summary: Update RTStream scene index status
+ tags:
+ - RTStream
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: stream_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "rts-12345"
+ - name: scene_index_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "scene-idx-12345"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - action
+ properties:
+ action:
+ type: string
+ enum: [start, stop]
+ example: "stop"
+ responses:
+ '200':
+ description: Scene index status updated
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/SuccessResponse'
+
+ /rtstream/{stream_id}/search:
+ post:
+ summary: Search RTStream scene index
+ tags:
+ - RTStream
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: stream_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "rts-12345"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - query
+ - scene_index_id
+ properties:
+ query:
+ type: string
+ example: "person walking"
+ scene_index_id:
+ type: string
+ example: "scene-idx-12345"
+ result_threshold:
+ type: integer
+ default: 10
+ example: 10
+ score_threshold:
+ type: number
+ example: 0.5
+ dynamic_score_percentage:
+ type: integer
+ default: 20
+ example: 20
+ stitch:
+ type: boolean
+ default: true
+ example: true
+ filter:
+ type: array
+ items:
+ type: object
+ rerank:
+ type: boolean
+ default: false
+ example: false
+ rerank_params:
+ type: object
+ responses:
+ '200':
+ description: Search results
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ results:
+ type: array
+ items:
+ type: object
+ properties:
+ start:
+ type: number
+ example: 1700000000
+ end:
+ type: number
+ example: 1700000010
+ text:
+ type: string
+ example: "matching scene text"
+ score:
+ type: number
+ example: 0.95
+ scene_index_id:
+ type: string
+ example: "scene-idx-12345"
+
+ /rtstream/{stream_id}/stream:
+ get:
+ summary: Get RTStream playback URL
+ tags:
+ - RTStream
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: stream_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "rts-12345"
+ - name: start
+ in: query
+ description: Start unix timestamp for time-based retrieval
+ schema:
+ type: number
+ example: 1700000000
+ - name: end
+ in: query
+ description: End unix timestamp for time-based retrieval
+ schema:
+ type: number
+ example: 1700003600
+ - name: original_frame_rate
+ in: query
+ schema:
+ type: integer
+ default: 1
+ example: 1
+ - name: frame_rate
+ in: query
+ schema:
+ type: integer
+ default: 1
+ example: 1
+ responses:
+ '200':
+ description: Stream URL
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ stream_url:
+ type: string
+ example: "https://stream.videodb.io/rts/12345"
+
+ /rtstream/{stream_id}/transcription/:
+ get:
+ summary: Get RTStream transcription data
+ tags:
+ - RTStream
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: stream_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "rts-12345"
+ - name: engine
+ in: query
+ schema:
+ type: string
+ default: "default"
+ example: "default"
+ - name: page
+ in: query
+ schema:
+ type: integer
+ default: 1
+ example: 1
+ - name: page_size
+ in: query
+ schema:
+ type: integer
+ default: 100
+ maximum: 1000
+ example: 100
+ - name: start
+ in: query
+ description: Filter by start timestamp
+ schema:
+ type: number
+ example: 1700000000
+ - name: end
+ in: query
+ description: Filter by end timestamp
+ schema:
+ type: number
+ example: 1700003600
+ - name: since
+ in: query
+ description: Get only entries newer than this timestamp (for polling)
+ schema:
+ type: number
+ example: 1700000000
+ responses:
+ '200':
+ description: Transcription data
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ transcription_records:
+ type: array
+ items:
+ type: object
+ properties:
+ start:
+ type: number
+ example: 1700000000
+ end:
+ type: number
+ example: 1700000005
+ text:
+ type: string
+ example: "transcribed text"
+ word_timestamps:
+ type: array
+ items:
+ type: object
+ next_page:
+ type: boolean
+ example: false
+ total_count:
+ type: integer
+ example: 50
+ page:
+ type: integer
+ example: 1
+ page_size:
+ type: integer
+ example: 100
+
+ post:
+ summary: Start or stop RTStream transcription
+ tags:
+ - RTStream
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: stream_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "rts-12345"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - action
+ properties:
+ action:
+ type: string
+ enum: [start, stop]
+ example: "start"
+ engine:
+ type: string
+ default: "default"
+ example: "default"
+ ws_connection_id:
+ type: string
+ example: "conn-123"
+ responses:
+ '200':
+ description: Transcription status updated
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ status:
+ type: string
+ enum: [running, stopped]
+ example: "running"
+ engine:
+ type: string
+ example: "default"
+ updated_at:
+ type: string
+ format: date-time
+
+ /rtstream/{stream_id}/transcription/status:
+ get:
+ summary: Get RTStream transcription status
+ tags:
+ - RTStream
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: stream_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "rts-12345"
+ - name: engine
+ in: query
+ schema:
+ type: string
+ default: "default"
+ example: "default"
+ responses:
+ '200':
+ description: Transcription status
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ status:
+ type: string
+ enum: [running, stopped, not_configured]
+ example: "running"
+ engine:
+ type: string
+ example: "default"
+ language:
+ type: string
+ example: "en"
+ created_at:
+ type: string
+ format: date-time
+ updated_at:
+ type: string
+ format: date-time
+
+ /rtstream/event:
+ post:
+ summary: Create collection event
+ tags:
+ - RTStream
+ security:
+ - ApiKeyAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - event_prompt
+ - label
+ properties:
+ event_prompt:
+ type: string
+ example: "Detect when a person enters the room"
+ label:
+ type: string
+ example: "person-entry"
+ responses:
+ '200':
+ description: Event created
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ event_id:
+ type: string
+ example: "event-12345"
+
+ get:
+ summary: List collection events
+ tags:
+ - RTStream
+ security:
+ - ApiKeyAuth: []
+ responses:
+ '200':
+ description: List of events
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ events:
+ type: array
+ items:
+ type: object
+ properties:
+ event_id:
+ type: string
+ example: "event-12345"
+ event_prompt:
+ type: string
+ example: "Detect when a person enters the room"
+ label:
+ type: string
+ example: "person-entry"
+
+ /rtstream/{stream_id}/index/{scene_index_id}/alert:
+ post:
+ summary: Create RTStream alert
+ tags:
+ - RTStream
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: stream_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "rts-12345"
+ - name: scene_index_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "scene-idx-12345"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - event_id
+ properties:
+ event_id:
+ type: string
+ example: "event-12345"
+ callback_url:
+ type: string
+ example: "https://webhook.example.com/alert"
+ ws_connection_id:
+ type: string
+ example: "conn-123"
+ responses:
+ '200':
+ description: Alert created
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ alert_id:
+ type: string
+ example: "alert-12345"
+
+ get:
+ summary: List RTStream alerts
+ tags:
+ - RTStream
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: stream_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "rts-12345"
+ - name: scene_index_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "scene-idx-12345"
+ responses:
+ '200':
+ description: List of alerts
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ example: true
+ data:
+ type: object
+ properties:
+ alerts:
+ type: array
+ items:
+ type: object
+ properties:
+ alert_id:
+ type: string
+ example: "alert-12345"
+ event_id:
+ type: string
+ example: "event-12345"
+ prompt:
+ type: string
+ example: "Detect when a person enters the room"
+ label:
+ type: string
+ example: "person-entry"
+ callback_url:
+ type: string
+ example: "https://webhook.example.com/alert"
+ status:
+ type: string
+ enum: [enabled, disabled]
+ example: "enabled"
+
+ /rtstream/{stream_id}/index/{scene_index_id}/alert/{alert_id}/status:
+ patch:
+ summary: Update RTStream alert status
+ tags:
+ - RTStream
+ security:
+ - ApiKeyAuth: []
+ parameters:
+ - name: stream_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "rts-12345"
+ - name: scene_index_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "scene-idx-12345"
+ - name: alert_id
+ in: path
+ required: true
+ schema:
+ type: string
+ example: "alert-12345"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required:
+ - status
+ properties:
+ status:
+ type: string
+ enum: [enabled, disabled]
+ example: "disabled"
+ responses:
+ '200':
+ description: Alert status updated
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/SuccessResponse'
+
+tags:
+ - name: Authentication
+ description: User authentication and API key management
+ - name: Collections
+ description: Collection management operations
+ - name: Videos
+ description: Video upload, processing, and management
+ - name: Audio
+ description: Audio management operations
+ - name: Images
+ description: Image management operations
+ - name: Search
+ description: Content search and indexing
+ - name: AI Generation
+ description: AI-powered content generation
+ - name: Billing
+ description: Billing and usage management
+ - name: RTStream
+ description: Real-time streaming operations
+ - name: Utilities
+ description: Utility endpoints
+ - name: Meeting
+ description: Meeting recording and management
+ - name: Capture
+ description: Capture session management for recording streams
+ - name: Editor
+ description: Timeline editor operations
+ - name: Transcode
+ description: Media transcoding operations
+ - name: Assets
+ description: Cross-collection asset listing
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index 1159ddd..705f032 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,4 @@
requests==2.31.0
backoff==2.2.1
tqdm==4.66.1
+websockets>=12.0
diff --git a/setup.py b/setup.py
index db2f955..5862a57 100644
--- a/setup.py
+++ b/setup.py
@@ -28,13 +28,19 @@
long_description=long_description,
long_description_content_type="text/markdown",
url=about["__url__"],
- packages=find_packages(exclude=["tests", "tests.*"]),
+ packages=find_packages(
+ exclude=["tests", "tests.*", "capture_bin", "videodb_capture_bin"]
+ ),
python_requires=">=3.8",
install_requires=[
"requests>=2.25.1",
"backoff>=2.2.1",
"tqdm>=4.66.1",
+ "websockets>=11.0.3",
],
+ extras_require={
+ "capture": ["videodb-capture-bin>=0.3.1"],
+ },
classifiers=[
"Intended Audience :: Developers",
"Programming Language :: Python :: 3",
diff --git a/videodb/__about__.py b/videodb/__about__.py
index 407daa2..b4566c7 100644
--- a/videodb/__about__.py
+++ b/videodb/__about__.py
@@ -2,7 +2,7 @@
-__version__ = "0.2.17"
+__version__ = "0.4.5"
__title__ = "videodb"
__author__ = "videodb"
__email__ = "contact@videodb.io"
diff --git a/videodb/__init__.py b/videodb/__init__.py
index 41244dc..238e4ae 100644
--- a/videodb/__init__.py
+++ b/videodb/__init__.py
@@ -4,10 +4,13 @@
import logging
from typing import Optional
-from videodb._utils._video import play_stream
+from videodb._utils._video import play_stream, build_iframe_embed_code
from videodb._constants import (
VIDEO_DB_API,
+ AgenticStreamRunStatus,
IndexType,
+ IndexCapability,
+ FieldGroup,
SceneExtractionType,
MediaType,
SearchType,
@@ -20,8 +23,19 @@
ResizeMode,
VideoConfig,
AudioConfig,
+ ReframeMode,
+ SegmentationType,
+ RTStreamChannelType,
)
from videodb.client import Connection
+from videodb.search import AskResponse, SearchResponse, SearchResult
+from videodb.understanding import Understanding, UnderstandingAnalyzer
+from videodb.agentic_stream import AgenticStream, AgenticStreamRun
+from videodb.schedule import Schedule
+from videodb.capture_session import CaptureSession
+from videodb.websocket_client import WebSocketConnection
+from videodb.capture import CaptureClient, Channel, AudioChannel, VideoChannel, Channels, ChannelList
+
from videodb.exceptions import (
VideodbError,
AuthenticationError,
@@ -33,12 +47,33 @@
__all__ = [
+ "connect",
+ "AgenticStream",
+ "AgenticStreamRun",
+ "AgenticStreamRunStatus",
+ "Schedule",
+ "CaptureSession",
+ "WebSocketConnection",
+ "CaptureClient",
+ "Channel",
+ "AudioChannel",
+ "VideoChannel",
+ "Channels",
+ "ChannelList",
"VideodbError",
"AuthenticationError",
"InvalidRequestError",
"IndexType",
+ "IndexCapability",
+ "FieldGroup",
"SearchError",
+ "SearchResult",
+ "SearchResponse",
+ "AskResponse",
+ "Understanding",
+ "UnderstandingAnalyzer",
"play_stream",
+ "build_iframe_embed_code",
"MediaType",
"SearchType",
"SubtitleAlignment",
@@ -51,11 +86,15 @@
"ResizeMode",
"VideoConfig",
"AudioConfig",
+ "ReframeMode",
+ "SegmentationType",
+ "RTStreamChannelType",
]
def connect(
api_key: str = None,
+ session_token: str = None,
base_url: Optional[str] = VIDEO_DB_API,
log_level: Optional[int] = logging.INFO,
**kwargs,
@@ -63,6 +102,7 @@ def connect(
"""A client for interacting with a videodb via REST API
:param str api_key: The api key to use for authentication
+ :param str session_token: The session token to use for authentication (alternative to api_key)
:param str base_url: (optional) The base url to use for the api
:param int log_level: (optional) The log level to use for the logger
:return: A connection object
@@ -70,11 +110,14 @@ def connect(
"""
logger.setLevel(log_level)
- if api_key is None:
+
+ # Determine which token to use
+ if api_key is None and session_token is None:
api_key = os.environ.get("VIDEO_DB_API_KEY")
- if api_key is None:
+
+ if api_key is None and session_token is None:
raise AuthenticationError(
- "No API key provided. Set an API key either as an environment variable (VIDEO_DB_API_KEY) or pass it as an argument."
+ "No authentication provided. Set an API key (VIDEO_DB_API_KEY) or provide api_key/session_token as an argument."
)
- return Connection(api_key, base_url, **kwargs)
+ return Connection(api_key=api_key, session_token=session_token, base_url=base_url, **kwargs)
diff --git a/videodb/_constants.py b/videodb/_constants.py
index e62a70d..7f93cb3 100644
--- a/videodb/_constants.py
+++ b/videodb/_constants.py
@@ -1,20 +1,32 @@
"""Constants used in the videodb package."""
+from enum import Enum
from typing import Union
from dataclasses import dataclass
VIDEO_DB_API: str = "https://api.videodb.io"
-class MediaType:
+class MediaType(str, Enum):
video = "video"
audio = "audio"
image = "image"
+class RTStreamChannelType:
+ mic = "mic"
+ screen = "screen"
+ system_audio = "system_audio"
+
+
class SearchType:
semantic = "semantic"
keyword = "keyword"
+
+
+class _InternalSearchType:
+ """Search types used internally by the SDK."""
+
scene = "scene"
llm = "llm"
@@ -24,18 +36,43 @@ class IndexType:
scene = "scene"
+class IndexCapability:
+ """Retrieval capabilities an index can be built for (``use_for``)."""
+
+ semantic = "semantic"
+ query = "query"
+ aggregate = "aggregate"
+
+
+class FieldGroup:
+ """Field groups that map artifact fields to retrieval capabilities."""
+
+ semantic = "semantic"
+ text = "text"
+ filter = "filter"
+ aggregate = "aggregate"
+ sort = "sort"
+
+
class SceneExtractionType:
shot_based = "shot"
time_based = "time"
+ transcript = "transcript"
class Workflows:
add_subtitles = "add_subtitles"
+class ReframeMode:
+ simple = "simple"
+ smart = "smart"
+
+
class SemanticSearchDefaultValues:
result_threshold = 5
score_threshold = 0.2
+ sort_docs_on = None
class Segmenter:
@@ -44,6 +81,11 @@ class Segmenter:
sentence = "sentence"
+class SegmentationType:
+ sentence = "sentence"
+ llm = "llm"
+
+
class ApiPath:
collection = "collection"
upload = "upload"
@@ -56,7 +98,14 @@ class ApiPath:
upload_url = "upload_url"
transcription = "transcription"
index = "index"
+ indexes = "indexes"
+ records = "records"
+ understand = "understand"
search = "search"
+ ask = "ask"
+ semantic_search = "semantic-search"
+ query = "query"
+ aggregate = "aggregate"
compile = "compile"
workflow = "workflow"
timeline = "timeline"
@@ -85,6 +134,19 @@ class ApiPath:
meeting = "meeting"
record = "record"
editor = "editor"
+ reframe = "reframe"
+ clip = "clip"
+ capture = "capture"
+ session = "session"
+ token = "token"
+ websocket = "websocket"
+ export = "export"
+ agentic_stream = "agentic_stream"
+ runs = "runs"
+ schedule = "schedule"
+ templates = "templates"
+ events = "events"
+ logs = "logs"
class Status:
@@ -95,9 +157,19 @@ class Status:
class MeetingStatus:
initializing = "initializing"
processing = "processing"
+ joined = "joined"
done = "done"
+class AgenticStreamRunStatus:
+ queued = "queued"
+ running = "running"
+ completed = "completed"
+ failed = "failed"
+ cancelled = "cancelled"
+ terminal = {"completed", "failed", "cancelled"}
+
+
class HttpClientDefaultValues:
max_retries = 1
timeout = 30
diff --git a/videodb/_upload.py b/videodb/_upload.py
index 399d527..0291abe 100644
--- a/videodb/_upload.py
+++ b/videodb/_upload.py
@@ -29,6 +29,7 @@ def upload(
callback_url: Optional[str] = None,
file_path: Optional[str] = None,
url: Optional[str] = None,
+ collection_id: Optional[str] = None,
) -> dict:
"""Upload a file or URL.
@@ -40,9 +41,12 @@ def upload(
:param str callback_url: URL to receive the callback (optional)
:param str file_path: Path to the file to be uploaded
:param str url: URL of the file to be uploaded
+ :param str collection_id: ID of the collection to upload to (optional)
:return: Dictionary containing upload response data
:rtype: dict
"""
+ collection_id = collection_id or _connection.collection_id
+
if source and (file_path or url):
raise VideodbError("source cannot be used with file_path or url")
@@ -66,9 +70,9 @@ def upload(
if file_path:
try:
- name = file_path.split("/")[-1].split(".")[0] if not name else name
+ name = os.path.splitext(os.path.basename(file_path))[0] if not name else name
upload_url_data = _connection.get(
- path=f"{ApiPath.collection}/{_connection.collection_id}/{ApiPath.upload_url}",
+ path=f"{ApiPath.collection}/{collection_id}/{ApiPath.upload_url}",
params={"name": name},
)
upload_url = upload_url_data.get("upload_url")
@@ -85,7 +89,7 @@ def upload(
raise VideodbError("Error while uploading file", cause=e)
upload_data = _connection.post(
- path=f"{ApiPath.collection}/{_connection.collection_id}/{ApiPath.upload}",
+ path=f"{ApiPath.collection}/{collection_id}/{ApiPath.upload}",
data={
"url": url,
"name": name,
diff --git a/videodb/_utils/_video.py b/videodb/_utils/_video.py
index 9cdb012..502367e 100644
--- a/videodb/_utils/_video.py
+++ b/videodb/_utils/_video.py
@@ -1,7 +1,62 @@
import webbrowser as web
+
PLAYER_URL: str = "https://console.videodb.io/player"
+def player_url_to_embed_url(player_url: str) -> str:
+ """Convert a /watch player URL to an /embed URL.
+
+ :param str player_url: The player URL (e.g., https://player.videodb.io/watch?v=slug)
+ :return: The embed URL (e.g., https://player.videodb.io/embed?v=slug)
+ :rtype: str
+ :raises ValueError: If the URL format is invalid or missing the 'v' parameter
+ """
+ if not player_url:
+ raise ValueError("player_url is required to generate embed URL")
+
+ if "/watch?" not in player_url:
+ raise ValueError("player_url must contain '/watch?' path")
+
+ if "v=" not in player_url.split("/watch?", 1)[1]:
+ raise ValueError("player_url must contain a 'v' query parameter")
+
+ return player_url.replace("/watch?", "/embed?", 1)
+
+
+def build_iframe_embed_code(
+ player_url: str,
+ width: str = "100%",
+ height: int = 405,
+ title: str = "VideoDB Player",
+ allow_fullscreen: bool = True,
+) -> str:
+ """Build an iframe embed HTML string from a player URL.
+
+ :param str player_url: The player URL to embed
+ :param str width: Width of the iframe (default: "100%")
+ :param int height: Height of the iframe in pixels (default: 405)
+ :param str title: Title attribute for the iframe (default: "VideoDB Player")
+ :param bool allow_fullscreen: Whether to allow fullscreen (default: True)
+ :return: HTML iframe string
+ :rtype: str
+ :raises ValueError: If player_url is empty or height is not positive
+ """
+ if not player_url:
+ raise ValueError("player_url is required to generate embed code")
+
+ if height <= 0:
+ raise ValueError("height must be a positive integer")
+
+ embed_url = player_url_to_embed_url(player_url)
+ fullscreen_attr = " allowfullscreen" if allow_fullscreen else ""
+
+ return (
+ f''
+ )
+
+
def play_stream(url: str):
"""Play a stream url in the browser/ notebook
diff --git a/videodb/agentic_stream.py b/videodb/agentic_stream.py
new file mode 100644
index 0000000..f7cdc79
--- /dev/null
+++ b/videodb/agentic_stream.py
@@ -0,0 +1,333 @@
+import time
+
+from typing import List, Optional
+
+from videodb._constants import AgenticStreamRunStatus, ApiPath
+from videodb.exceptions import VideodbError
+
+
+class AgenticStreamRun:
+ """AgenticStreamRun class representing a single execution of an agentic stream.
+
+ :ivar str id: Unique identifier for the run
+ :ivar str agentic_stream_id: ID of the parent agentic stream
+ :ivar str collection_id: ID of the collection
+ :ivar str status: Current status (queued/running/completed/failed/cancelled)
+ :ivar dict config: Immutable config snapshot captured at run creation
+ :ivar dict progress: Current progress ``{stage, percent, message}``
+ :ivar str video_id: ID of the produced video (populated on completion)
+ :ivar str stream_url: Stream URL of the produced video
+ :ivar str player_url: Player URL of the produced video
+ :ivar str thumbnail_url: Thumbnail URL of the produced video
+ :ivar float duration: Duration of the produced video in seconds
+ :ivar list events: Compiled clip list (populated on completion)
+ :ivar str error: Error message when the run failed
+ """
+
+ def __init__(
+ self, _connection, id: str, agentic_stream_id: str, collection_id: str, **kwargs
+ ) -> None:
+ self._connection = _connection
+ self.id = id
+ self.agentic_stream_id = agentic_stream_id
+ self.collection_id = collection_id
+ self._update_attributes(kwargs)
+
+ def __repr__(self) -> str:
+ return (
+ f"AgenticStreamRun("
+ f"id={self.id}, "
+ f"agentic_stream_id={self.agentic_stream_id}, "
+ f"collection_id={self.collection_id}, "
+ f"status={self.status}, "
+ f"progress={self.progress}, "
+ f"config={self.config}, "
+ f"video_id={self.video_id}, "
+ f"stream_url={self.stream_url}, "
+ f"player_url={self.player_url}, "
+ f"thumbnail_url={self.thumbnail_url}, "
+ f"duration={self.duration}, "
+ f"events={self.events}, "
+ f"error={self.error}, "
+ f"callback_url={self.callback_url}, "
+ f"callback_data={self.callback_data}, "
+ f"created_at={self.created_at}, "
+ f"updated_at={self.updated_at}, "
+ f"completed_at={self.completed_at})"
+ )
+
+ def _update_attributes(self, data: dict) -> None:
+ self.status = data.get("status")
+ self.config = data.get("config")
+ self.progress = data.get("progress")
+ self.video_id = data.get("video_id")
+ self.stream_url = data.get("stream_url")
+ self.player_url = data.get("player_url")
+ self.thumbnail_url = data.get("thumbnail_url")
+ self.duration = data.get("duration")
+ self.events = data.get("events")
+ self.error = data.get("error")
+ self.callback_url = data.get("callback_url")
+ self.callback_data = data.get("callback_data")
+ self.created_at = data.get("created_at")
+ self.updated_at = data.get("updated_at")
+ self.completed_at = data.get("completed_at")
+
+ @property
+ def _base_path(self) -> str:
+ return (
+ f"{ApiPath.collection}/{self.collection_id}/"
+ f"{ApiPath.agentic_stream}/{self.agentic_stream_id}/"
+ f"{ApiPath.runs}/{self.id}"
+ )
+
+ @property
+ def is_complete(self) -> bool:
+ """True if the run has reached a terminal status."""
+ return self.status in AgenticStreamRunStatus.terminal
+
+ @property
+ def is_successful(self) -> bool:
+ """True if the run completed successfully."""
+ return self.status == AgenticStreamRunStatus.completed
+
+ def refresh(self) -> "AgenticStreamRun":
+ """Refresh run data from the server.
+
+ :return: The updated run instance
+ :rtype: AgenticStreamRun
+ """
+ response = self._connection.get(path=self._base_path)
+ if response:
+ self._update_attributes(response)
+ else:
+ raise VideodbError(f"Failed to refresh run {self.id}")
+ return self
+
+ def wait(self, timeout: int = 3600, poll_interval: int = 5) -> "AgenticStreamRun":
+ """Block until the run reaches a terminal status.
+
+ :param int timeout: Maximum seconds to wait (default 3600)
+ :param int poll_interval: Seconds between polls (default 5)
+ :return: The run instance in a terminal state
+ :rtype: AgenticStreamRun
+ :raises VideodbError: If the run failed or the timeout is exceeded
+ """
+ start_time = time.time()
+ while time.time() - start_time < timeout:
+ self.refresh()
+ if self.is_complete:
+ if self.status == AgenticStreamRunStatus.failed:
+ raise VideodbError(f"Run {self.id} failed: {self.error}")
+ return self
+ time.sleep(poll_interval)
+ raise VideodbError(f"Run {self.id} did not complete within {timeout}s")
+
+ def cancel(self) -> "AgenticStreamRun":
+ """Cancel a queued or running run.
+
+ :return: The updated run instance
+ :rtype: AgenticStreamRun
+ """
+ response = self._connection.patch(
+ path=f"{self._base_path}/{ApiPath.status}",
+ data={"status": AgenticStreamRunStatus.cancelled},
+ )
+ if response:
+ self._update_attributes(response)
+ return self
+
+ def get_events(self) -> List[dict]:
+ """Get the user-facing event timeline of this run.
+
+ :return: List of event dicts
+ :rtype: List[dict]
+ """
+ response = self._connection.get(path=f"{self._base_path}/{ApiPath.events}")
+ return (response or {}).get("events", [])
+
+ def get_logs(self) -> List[dict]:
+ """Get the debug logs of this run.
+
+ :return: List of log dicts
+ :rtype: List[dict]
+ """
+ response = self._connection.get(path=f"{self._base_path}/{ApiPath.logs}")
+ return (response or {}).get("logs", [])
+
+
+class AgenticStream:
+ """AgenticStream class representing a persistent agent video pipeline.
+
+ Note: Users should not initialize this class directly.
+ Instead use :meth:`Collection.create_agentic_stream() `
+
+ :ivar str id: Unique identifier for the agentic stream
+ :ivar str collection_id: ID of the collection this stream belongs to
+ :ivar str name: Human-readable name
+ :ivar str template_id: ID of the template this stream uses
+ :ivar str prompt: Content instructions for the agent
+ :ivar list sources: Keywords/URLs used for research each run
+ """
+
+ def __init__(self, _connection, id: str, collection_id: str, **kwargs) -> None:
+ self._connection = _connection
+ self.id = id
+ self.collection_id = collection_id
+ self._update_attributes(kwargs)
+
+ def __repr__(self) -> str:
+ return (
+ f"AgenticStream("
+ f"id={self.id}, "
+ f"collection_id={self.collection_id}, "
+ f"name={self.name}, "
+ f"template_id={self.template_id}, "
+ f"prompt={self.prompt}, "
+ f"sources={self.sources}, "
+ f"max_duration={self.max_duration}, "
+ f"voice={self.voice}, "
+ f"language={self.language}, "
+ f"aspect_ratio={self.aspect_ratio}, "
+ f"captions={self.captions}, "
+ f"model={self.model}, "
+ f"callback_url={self.callback_url}, "
+ f"callback_data={self.callback_data}, "
+ f"status={self.status}, "
+ f"created_at={self.created_at}, "
+ f"updated_at={self.updated_at})"
+ )
+
+ def _update_attributes(self, data: dict) -> None:
+ self.name = data.get("name")
+ self.template_id = data.get("template_id")
+ self.prompt = data.get("prompt")
+ self.sources = data.get("sources")
+ self.max_duration = data.get("max_duration")
+ self.voice = data.get("voice")
+ self.language = data.get("language")
+ self.aspect_ratio = data.get("aspect_ratio")
+ self.captions = data.get("captions")
+ self.model = data.get("model")
+ self.callback_url = data.get("callback_url")
+ self.callback_data = data.get("callback_data")
+ self.status = data.get("status")
+ self.created_at = data.get("created_at")
+ self.updated_at = data.get("updated_at")
+
+ @property
+ def _base_path(self) -> str:
+ return (
+ f"{ApiPath.collection}/{self.collection_id}/"
+ f"{ApiPath.agentic_stream}/{self.id}"
+ )
+
+ def refresh(self) -> "AgenticStream":
+ """Refresh stream data from the server.
+
+ :return: The updated stream instance
+ :rtype: AgenticStream
+ """
+ response = self._connection.get(path=f"{self._base_path}/")
+ if response:
+ self._update_attributes(response)
+ else:
+ raise VideodbError(f"Failed to refresh agentic stream {self.id}")
+ return self
+
+ def update(self, **kwargs) -> "AgenticStream":
+ """Update stream fields.
+
+ :param kwargs: Fields to update (name, template_id, prompt, sources, max_duration,
+ voice, language, aspect_ratio, captions, model, callback_url,
+ callback_data)
+ :return: The updated stream instance
+ :rtype: AgenticStream
+ """
+ response = self._connection.patch(path=f"{self._base_path}/", data=kwargs)
+ if response:
+ self._update_attributes(response)
+ return self
+
+ def delete(self) -> None:
+ """Delete the agentic stream.
+
+ Existing runs, output videos, and schedules are not deleted.
+ Delete any associated schedules separately.
+
+ :return: None
+ :rtype: None
+ """
+ self._connection.delete(path=f"{self._base_path}/")
+
+ def run_now(
+ self,
+ callback_url: Optional[str] = None,
+ callback_data: Optional[dict] = None,
+ ) -> AgenticStreamRun:
+ """Trigger an on-demand run of this stream.
+
+ :param str callback_url: (optional) Per-run webhook override
+ :param dict callback_data: (optional) Per-run callback data override
+ :return: The created run
+ :rtype: AgenticStreamRun
+ """
+ data = {}
+ if callback_url is not None:
+ data["callback_url"] = callback_url
+ if callback_data is not None:
+ data["callback_data"] = callback_data
+ response = self._connection.post(
+ path=f"{self._base_path}/{ApiPath.runs}", data=data
+ )
+ if not response:
+ raise VideodbError("Failed to trigger run")
+ return AgenticStreamRun(
+ self._connection,
+ id=response.get("id"),
+ agentic_stream_id=self.id,
+ collection_id=self.collection_id,
+ **{k: v for k, v in response.items()
+ if k not in ("id", "agentic_stream_id", "collection_id")},
+ )
+
+ def list_runs(self) -> List[AgenticStreamRun]:
+ """List runs of this stream, most recent first.
+
+ :return: List of runs
+ :rtype: List[AgenticStreamRun]
+ """
+ response = self._connection.get(path=f"{self._base_path}/{ApiPath.runs}")
+ runs = (response or {}).get("runs", [])
+ return [
+ AgenticStreamRun(
+ self._connection,
+ id=run.get("id"),
+ agentic_stream_id=self.id,
+ collection_id=self.collection_id,
+ **{k: v for k, v in run.items()
+ if k not in ("id", "agentic_stream_id", "collection_id")},
+ )
+ for run in runs
+ ]
+
+ def get_run(self, run_id: str) -> AgenticStreamRun:
+ """Get a run by its ID.
+
+ :param str run_id: ID of the run
+ :return: The run
+ :rtype: AgenticStreamRun
+ """
+ response = self._connection.get(
+ path=f"{self._base_path}/{ApiPath.runs}/{run_id}"
+ )
+ if not response:
+ raise VideodbError(f"Run {run_id} not found")
+ return AgenticStreamRun(
+ self._connection,
+ id=response.get("id"),
+ agentic_stream_id=self.id,
+ collection_id=self.collection_id,
+ **{k: v for k, v in response.items()
+ if k not in ("id", "agentic_stream_id", "collection_id")},
+ )
diff --git a/videodb/asset.py b/videodb/asset.py
index 6061b4b..805a862 100644
--- a/videodb/asset.py
+++ b/videodb/asset.py
@@ -37,8 +37,8 @@ def __init__(
end: Optional[float] = None,
) -> None:
super().__init__(asset_id)
- self.start: int = start
- self.end: Union[int, None] = end
+ self.start: Optional[float] = start
+ self.end: Optional[float] = end
def to_json(self) -> dict:
return copy.deepcopy(self.__dict__)
@@ -63,8 +63,8 @@ def __init__(
fade_out_duration: Optional[Union[int, float]] = 0,
):
super().__init__(asset_id)
- self.start: int = start
- self.end: Union[int, None] = end
+ self.start: Optional[float] = start
+ self.end: Optional[float] = end
self.disable_other_tracks: bool = disable_other_tracks
self.fade_in_duration: Union[int, float] = validate_max_supported(
fade_in_duration, MaxSupported.fade_duration, "fade_in_duration"
diff --git a/videodb/audio.py b/videodb/audio.py
index 21c250c..9dd6987 100644
--- a/videodb/audio.py
+++ b/videodb/audio.py
@@ -1,7 +1,11 @@
+from typing import Dict, List, Union
from videodb._constants import (
ApiPath,
+ Segmenter,
)
+_VALID_SEGMENTERS = {Segmenter.word, Segmenter.sentence, Segmenter.time}
+
class Audio:
"""Audio class to interact with the Audio
@@ -10,6 +14,8 @@ class Audio:
:ivar str collection_id: ID of the collection this audio belongs to
:ivar str name: Name of the audio file
:ivar float length: Duration of the audio in seconds
+ :ivar list transcript: Timestamped transcript segments
+ :ivar str transcript_text: Full transcript text
"""
def __init__(
@@ -19,7 +25,9 @@ def __init__(
self.id = id
self.collection_id = collection_id
self.name = kwargs.get("name", None)
- self.length = kwargs.get("length", None)
+ self.length = float(kwargs.get("length", 0.0))
+ self.transcript = kwargs.get("transcript", None)
+ self.transcript_text = kwargs.get("transcript_text", None)
def __repr__(self) -> str:
return (
@@ -43,6 +51,119 @@ def generate_url(self) -> str:
)
return url_data.get("signed_url", None)
+ def _fetch_transcript(
+ self,
+ start: int = None,
+ end: int = None,
+ segmenter: str = Segmenter.word,
+ length: int = 1,
+ force: bool = None,
+ ) -> None:
+ if segmenter not in _VALID_SEGMENTERS:
+ raise ValueError(
+ f"Invalid segmenter '{segmenter}'. "
+ f"Must be one of: {', '.join(sorted(_VALID_SEGMENTERS))}"
+ )
+ if start is not None and start < 0:
+ raise ValueError(f"start must be non-negative, got {start}")
+ if end is not None and end < 0:
+ raise ValueError(f"end must be non-negative, got {end}")
+ if start is not None and end is not None and start > end:
+ raise ValueError(
+ f"start ({start}) must be less than or equal to end ({end})"
+ )
+ if self.transcript and not force and not start and not end:
+ return
+ transcript_data = self._connection.get(
+ path=f"{ApiPath.audio}/{self.id}/{ApiPath.transcription}",
+ params={
+ "start": start,
+ "end": end,
+ "segmenter": segmenter,
+ "length": length,
+ "force": "true" if force else "false",
+ },
+ show_progress=True,
+ )
+ self.transcript = transcript_data.get("word_timestamps", [])
+ self.transcript_text = transcript_data.get("text", "")
+
+ def get_transcript(
+ self,
+ start: int = None,
+ end: int = None,
+ segmenter: Segmenter = Segmenter.word,
+ length: int = 1,
+ force: bool = None,
+ ) -> List[Dict[str, Union[float, str]]]:
+ """Get timestamped transcript segments for the audio.
+
+ :param int start: Start time in seconds (must be >= 0 and <= end)
+ :param int end: End time in seconds (must be >= 0 and >= start)
+ :param Segmenter segmenter: How to split the transcript into segments.
+ Must be one of :attr:`Segmenter.word` (default, one segment per word),
+ :attr:`Segmenter.sentence` (one segment per sentence), or
+ :attr:`Segmenter.time` (fixed-duration segments controlled by *length*)
+ :param int length: Duration in seconds for each segment when
+ *segmenter* is :attr:`Segmenter.time` (default 1)
+ :param bool force: Force re-fetch transcript from the server,
+ bypassing the local cache
+ :raises ValueError: If *segmenter* is not a valid value, or if
+ *start*/*end* are negative or *start* > *end*
+ :return: List of dicts with keys: start (float), end (float), text (str)
+ :rtype: List[Dict[str, Union[float, str]]]
+ """
+ self._fetch_transcript(
+ start=start, end=end, segmenter=segmenter, length=length, force=force
+ )
+ return self.transcript
+
+ def get_transcript_text(
+ self,
+ start: int = None,
+ end: int = None,
+ ) -> str:
+ """Get plain text transcript for the audio.
+
+ :param int start: Start time in seconds to get transcript from
+ :param int end: End time in seconds to get transcript until
+ :param bool force: Force fetch new transcript
+ :return: Full transcript text as string
+ :rtype: str
+ """
+ self._fetch_transcript(start=start, end=end)
+ return self.transcript_text
+
+ def generate_transcript(
+ self,
+ force: bool = None,
+ language_code: str = None,
+ ) -> dict:
+ """Generate transcript for the audio.
+
+ :param bool force: Force generate new transcript
+ :param str language_code: Language code for transcription.
+ Use ISO 639-1 codes (e.g., "en", "hi", "fr") or regional
+ variants with underscores (e.g., "en_us", "en_uk", "en_au").
+ Defaults to "en_us" if not specified.
+ :return: Success dict if transcript generated or already exists
+ :rtype: dict
+ """
+ transcript_data = self._connection.post(
+ path=f"{ApiPath.audio}/{self.id}/{ApiPath.transcription}",
+ data={
+ "force": True if force else False,
+ "language_code": language_code,
+ },
+ )
+ transcript = transcript_data.get("word_timestamps", [])
+ if transcript:
+ return {
+ "success": True,
+ "message": "Transcript generated successfully",
+ }
+ return transcript_data
+
def delete(self) -> None:
"""Delete the audio.
diff --git a/videodb/capture.py b/videodb/capture.py
new file mode 100644
index 0000000..dca7e59
--- /dev/null
+++ b/videodb/capture.py
@@ -0,0 +1,426 @@
+import logging
+import asyncio
+import json
+import uuid
+import os
+import warnings
+from typing import Optional, Dict, List, Any
+
+from videodb._constants import VIDEO_DB_API
+
+logger = logging.getLogger(__name__)
+
+def get_capture_binary_path():
+ """
+ Attempts to find the path to the capture binary.
+ If the optional 'videodb-capture-bin' package is not installed,
+ it raises a RuntimeError with instructions.
+ """
+ try:
+ import videodb_capture_bin
+ return videodb_capture_bin.get_binary_path()
+ except ImportError:
+ error_msg = (
+ "Capture runtime not found.\n"
+ "To use capture features, please install the capture dependencies:\n"
+ "pip install 'videodb[capture]'"
+ )
+ logger.error(error_msg)
+ raise RuntimeError(error_msg)
+ except Exception as e:
+ logger.error(f"Failed to resolve capture binary path: {e}")
+ raise
+
+
+class Channel:
+ """Base class for capture channels."""
+
+ def __init__(
+ self,
+ id: str,
+ name: str,
+ type: str,
+ client: Optional["CaptureClient"] = None,
+ ):
+ """Object representing a capture channel.
+
+ :param str id: The unique ID of the channel.
+ :param str name: The display name of the channel.
+ :param str type: The type of the channel (audio/video).
+ :param CaptureClient client: Reference to the capture client.
+ """
+ self.id = id
+ self.name = name
+ self.type = type
+ self._client = client
+ self.store = False
+ self.is_primary = False
+
+ def __repr__(self):
+ return f"Channel(id={self.id}, name={self.name}, type={self.type})"
+
+ async def pause(self) -> None:
+ """Pause recording for this channel."""
+ if not self._client:
+ raise RuntimeError("Channel not bound to a CaptureClient")
+
+ track_map = {
+ "audio": "mic" if "mic" in self.id else "system_audio",
+ "video": "screen",
+ }
+ track = track_map.get(self.type)
+ if track:
+ await self._client._send_command("pauseTracks", {"tracks": [track]})
+
+ async def resume(self) -> None:
+ """Resume recording for this channel."""
+ if not self._client:
+ raise RuntimeError("Channel not bound to a CaptureClient")
+
+ track_map = {
+ "audio": "mic" if "mic" in self.id else "system_audio",
+ "video": "screen",
+ }
+ track = track_map.get(self.type)
+ if track:
+ await self._client._send_command("resumeTracks", {"tracks": [track]})
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return dictionary representation of the channel."""
+ return {
+ "channel_id": self.id,
+ "type": self.type,
+ "name": self.name,
+ "store": self.store,
+ "is_primary": self.is_primary,
+ }
+
+
+class AudioChannel(Channel):
+ """Represents an audio source channel."""
+
+ def __init__(self, id: str, name: str, client: Optional["CaptureClient"] = None):
+ super().__init__(id, name, type="audio", client=client)
+
+ def __repr__(self):
+ return f"AudioChannel(id={self.id}, name={self.name})"
+
+
+class VideoChannel(Channel):
+ """Represents a video source channel."""
+
+ def __init__(self, id: str, name: str, client: Optional["CaptureClient"] = None):
+ super().__init__(id, name, type="video", client=client)
+
+ def __repr__(self):
+ return f"VideoChannel(id={self.id}, name={self.name})"
+
+
+class ChannelList(list):
+ """List subclass with a default property for channel collections."""
+
+ @property
+ def default(self) -> Optional[Channel]:
+ """Get the first (default) channel, or None if empty."""
+ return self[0] if self else None
+
+
+class Channels:
+ """Container for available channels, grouped by type."""
+
+ def __init__(
+ self,
+ mics: List[AudioChannel] = None,
+ displays: List[VideoChannel] = None,
+ system_audio: List[AudioChannel] = None,
+ cameras: List[VideoChannel] = None,
+ ):
+ self.mics: ChannelList = ChannelList(mics or [])
+ self.displays: ChannelList = ChannelList(displays or [])
+ self.system_audio: ChannelList = ChannelList(system_audio or [])
+ self.cameras: ChannelList = ChannelList(cameras or [])
+
+ def __repr__(self):
+ return (
+ f"Channels("
+ f"mics={len(self.mics)}, "
+ f"displays={len(self.displays)}, "
+ f"system_audio={len(self.system_audio)}, "
+ f"cameras={len(self.cameras)})"
+ )
+
+ def all(self) -> List[Channel]:
+ """Return a flat list of all capturable channels (excludes cameras)."""
+ return list(self.mics) + list(self.displays) + list(self.system_audio)
+
+
+class CaptureClient:
+ """Client for managing local capture sessions."""
+
+ def __init__(
+ self,
+ client_token: str,
+ base_url: Optional[str] = None,
+ ):
+ """Initialize the capture client.
+
+ :param str client_token: Client token for the capture session.
+ :param str base_url: VideoDB API endpoint URL.
+ """
+ self.client_token = client_token
+ self.base_url = base_url or os.environ.get("VIDEO_DB_API", VIDEO_DB_API)
+ self._session_id: Optional[str] = None
+ self._proc = None
+ self._futures: Dict[str, asyncio.Future] = {}
+ self._binary_path = get_capture_binary_path()
+ self._event_queue = asyncio.Queue()
+
+ def __repr__(self) -> str:
+ return f"CaptureClient(base_url={self.base_url})"
+
+ async def _ensure_process(self):
+ """Ensure the capture binary is running."""
+ if self._proc is not None and self._proc.returncode is None:
+ return
+
+ self._proc = await asyncio.create_subprocess_exec(
+ self._binary_path,
+ stdin=asyncio.subprocess.PIPE,
+ stdout=asyncio.subprocess.PIPE,
+ stderr=asyncio.subprocess.PIPE,
+ )
+
+ asyncio.create_task(self._read_stdout_loop())
+ asyncio.create_task(self._read_stderr_loop())
+
+ await self._send_command("init", {"apiUrl": self.base_url})
+
+
+ async def _send_command(
+ self, command: str, params: Optional[Dict[str, Any]] = None
+ ) -> Dict[str, Any]:
+ """Send a command to the capture binary and await response.
+
+ :param str command: Command name.
+ :param dict params: Command parameters.
+ :return: Response result.
+ :rtype: dict
+ """
+ await self._ensure_process()
+
+ command_id = str(uuid.uuid4())
+ payload = {
+ "command": command,
+ "commandId": command_id,
+ "params": params or {},
+ }
+
+ # IPC protocol framing: videodb_recorder|\n
+ message = f"videodb_recorder|{json.dumps(payload)}\n"
+ self._proc.stdin.write(message.encode("utf-8"))
+ await self._proc.stdin.drain()
+
+ # Create future to await response
+ loop = asyncio.get_running_loop()
+ future = loop.create_future()
+ self._futures[command_id] = future
+
+ try:
+ return await future
+ finally:
+ self._futures.pop(command_id, None)
+
+ async def _read_stdout_loop(self):
+ """Loop to read stdout and process messages."""
+ while True:
+ line = await self._proc.stdout.readline()
+ if not line:
+ break
+
+ line_str = line.decode("utf-8", errors="replace").strip()
+ if not line_str.startswith("videodb_recorder|"):
+ continue
+
+ try:
+ json_part = line_str.split("|", 1)[1]
+ data = json.loads(json_part)
+
+ msg_type = data.get("type")
+ if msg_type == "response":
+ cmd_id = data.get("commandId")
+ if cmd_id in self._futures:
+ if data.get("status") == "success":
+ self._futures[cmd_id].set_result(data.get("result"))
+ else:
+ self._futures[cmd_id].set_exception(
+ RuntimeError(data.get("result", "Unknown error"))
+ )
+ elif msg_type == "event":
+ await self._event_queue.put(data)
+
+ except Exception as e:
+ logger.error(f"Failed to parse capture message: {e}")
+
+ async def _read_stderr_loop(self):
+ """Loop to read stderr and log messages."""
+ while True:
+ line = await self._proc.stderr.readline()
+ if not line:
+ break
+ logger.debug(f"[Capture Binary]: {line.decode('utf-8', errors='replace').strip()}")
+
+ async def shutdown(self):
+ """Cleanly terminate the capture binary process."""
+ if self._proc:
+ try:
+ # Try graceful shutdown command first
+ await self._send_command("shutdown")
+ except Exception:
+ pass
+
+ try:
+ self._proc.terminate()
+ await self._proc.wait()
+ except Exception:
+ pass
+ self._proc = None
+
+ # Valid permission types
+ VALID_PERMISSIONS = {"microphone", "screen_capture"}
+
+ async def request_permission(self, kind: str) -> bool:
+ """Request system permissions.
+
+ :param str kind: One of "microphone", "screen_capture"
+ :return: True if granted, False if denied
+ :raises ValueError: If kind is not a valid permission type
+ """
+ # Validate permission type
+ if kind not in self.VALID_PERMISSIONS:
+ raise ValueError(
+ f"Invalid permission type: '{kind}'. "
+ f"Valid types: {', '.join(sorted(self.VALID_PERMISSIONS))}"
+ )
+
+ # Map python-friendly names to binary-expected names
+ # e.g. "screen_capture" -> "screen-capture"
+ permission_map = {
+ "screen_capture": "screen-capture",
+ }
+ binary_kind = permission_map.get(kind, kind)
+ result = await self._send_command("requestPermission", {"permission": binary_kind})
+
+ # Binary returns {"requested": True} to confirm the request was initiated
+ # or may return {"status": "granted"} if already granted.
+ if result.get("requested") is True:
+ return True
+
+ status = result.get("status")
+ if status == "granted":
+ return True
+ elif status == "denied":
+ logger.warning(f"Permission '{kind}' was denied.")
+ return False
+
+ return False
+
+ async def list_channels(self) -> Channels:
+ """Query the system for available audio and video channels.
+
+ :return: Channels object with grouped collections (mics, displays, system_audio).
+ :rtype: Channels
+ """
+ response = await self._send_command("getChannels")
+ raw_channels = response.get("channels", [])
+
+ mics = []
+ displays = []
+ system_audio = []
+ cameras = []
+
+ for ch in raw_channels:
+ c_type = ch.get("type")
+ c_id = ch.get("channel_id") or ch.get("id")
+ c_name = ch.get("name", "")
+
+ if not c_id:
+ logger.warning(f"Skipping channel with missing ID: {ch}")
+ continue
+
+ # Categorize based on channel ID prefix
+ if c_id.startswith("mic:"):
+ mics.append(AudioChannel(id=c_id, name=c_name, client=self))
+ elif c_id.startswith("display:") or c_id.startswith("screen:"):
+ displays.append(VideoChannel(id=c_id, name=c_name, client=self))
+ elif c_id.startswith("system_audio:"):
+ system_audio.append(AudioChannel(id=c_id, name=c_name, client=self))
+ elif c_id.startswith("camera:"):
+ cameras.append(VideoChannel(id=c_id, name=c_name, client=self))
+ elif c_type == "audio":
+ mics.append(AudioChannel(id=c_id, name=c_name, client=self))
+ elif c_type == "video":
+ displays.append(VideoChannel(id=c_id, name=c_name, client=self))
+ else:
+ logger.debug(f"Unknown channel type '{c_type}' for channel '{c_name}'")
+
+ return Channels(mics=mics, displays=displays, system_audio=system_audio, cameras=cameras)
+
+ async def start_session(
+ self,
+ capture_session_id: str,
+ channels: List[Channel],
+ primary_video_channel_id: Optional[str] = None,
+ ) -> None:
+ """Start the recording session.
+
+ :param str capture_session_id: The ID of the capture session.
+ :param list[Channel] channels: List of Channel objects to record.
+ Set channel.is_primary = True on the desired video channel.
+ :param str primary_video_channel_id: Deprecated. Set
+ channel.is_primary = True on the desired video channel instead.
+ :raises ValueError: If no channels are specified.
+ """
+ if not channels:
+ raise ValueError("At least one channel must be specified for capture.")
+
+ if primary_video_channel_id is not None:
+ warnings.warn(
+ "primary_video_channel_id is deprecated. "
+ "Set channel.is_primary = True on the desired video channel instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ for ch in channels:
+ if ch.id == primary_video_channel_id:
+ ch.is_primary = True
+ break
+
+ self._session_id = capture_session_id
+
+ payload = {
+ "sessionId": capture_session_id,
+ "uploadToken": self.client_token,
+ "channels": [ch.to_dict() for ch in channels],
+ }
+
+ await self._send_command("startRecording", payload)
+
+ async def stop_session(self) -> None:
+ """Stop the current recording session."""
+ if not self._session_id:
+ raise RuntimeError("No active capture session to stop.")
+ await self._send_command("stopRecording", {"sessionId": self._session_id})
+
+ async def events(self):
+ """Async generator that yields events from the capture binary."""
+ while True:
+ try:
+ # Use a timeout so we can check if the process is still alive
+ event = await asyncio.wait_for(self._event_queue.get(), timeout=1.0)
+ yield event
+ except asyncio.TimeoutError:
+ if self._proc is None or self._proc.returncode is not None:
+ break
+ continue
+ except Exception:
+ break
diff --git a/videodb/capture_session.py b/videodb/capture_session.py
new file mode 100644
index 0000000..10acc81
--- /dev/null
+++ b/videodb/capture_session.py
@@ -0,0 +1,117 @@
+from typing import List, Optional
+from videodb._constants import ApiPath
+from videodb.capture import VideoChannel, AudioChannel
+from videodb.rtstream import RTStream
+
+
+class CaptureSession:
+ """CaptureSession class representing a capture session.
+
+ :ivar str id: Unique identifier for the session
+ :ivar str collection_id: ID of the collection this session belongs to
+ :ivar str end_user_id: ID of the end user
+ :ivar str client_id: Client-provided session ID
+ :ivar str status: Current status of the session
+ :ivar list channels: List of channel dicts with id, name, type, is_primary
+ :ivar str primary_video_channel_id: Channel ID of the primary video source
+ :ivar str export_status: Current export status (exporting, exported, failed)
+ :ivar dict exported_videos: Mapping of channel_id to exported video_id
+ """
+
+ def __init__(self, _connection, id: str, collection_id: str, **kwargs) -> None:
+ self._connection = _connection
+ self.id = id
+ self.collection_id = collection_id
+ self._update_attributes(kwargs)
+
+ def __repr__(self) -> str:
+ return (
+ f"CaptureSession("
+ f"id={self.id}, "
+ f"status={getattr(self, 'status', None)}, "
+ f"collection_id={self.collection_id}, "
+ f"end_user_id={getattr(self, 'end_user_id', None)})"
+ )
+
+ def _update_attributes(self, data: dict) -> None:
+ """Update instance attributes from API response data."""
+ self.end_user_id = data.get("end_user_id")
+ self.client_id = data.get("client_id")
+ self.status = data.get("status")
+ self.callback_url = data.get("callback_url")
+ self.exported_video_id = data.get("exported_video_id")
+ self.metadata = data.get("metadata", {})
+ self.channels = []
+ for ch_data in data.get("channels", []):
+ ch_type = ch_data.get("type")
+ ch_id = ch_data.get("channel_id", "")
+ ch_name = ch_data.get("name", "")
+ if ch_type == "video":
+ ch = VideoChannel(id=ch_id, name=ch_name)
+ else:
+ ch = AudioChannel(id=ch_id, name=ch_name)
+ ch.store = ch_data.get("store", False)
+ ch.is_primary = ch_data.get("is_primary", False)
+ self.channels.append(ch)
+
+ self.primary_video_channel_id = data.get("primary_video_channel_id")
+ self.export_status = data.get("export_status")
+
+ self.rtstreams = []
+ for rts_data in data.get("rtstreams", []):
+ if not isinstance(rts_data, dict):
+ continue
+ stream = RTStream(self._connection, **rts_data)
+ self.rtstreams.append(stream)
+
+ @property
+ def displays(self) -> List[VideoChannel]:
+ """Video channels in the session.
+
+ :return: List of VideoChannel objects
+ :rtype: list[VideoChannel]
+ """
+ return [ch for ch in self.channels if isinstance(ch, VideoChannel)]
+
+ def get_rtstream(self, category: str) -> List[RTStream]:
+ """Get list of RTStreams by category.
+
+ :param str category: Category to filter by. Use :class:`RTStreamChannelType` constants:
+ ``RTStreamChannelType.mic``, ``RTStreamChannelType.screen``, ``RTStreamChannelType.system_audio``.
+ :return: List of :class:`RTStream ` objects
+ :rtype: List[:class:`videodb.rtstream.RTStream`]
+ """
+ filtered_streams = []
+
+ for stream in self.rtstreams:
+ channel_id = getattr(stream, "channel_id", "") or ""
+ if str(channel_id).lower() == category.lower():
+ filtered_streams.append(stream)
+
+ return filtered_streams
+
+ def export(self, video_channel_id: Optional[str] = None, ws_connection_id: Optional[str] = None) -> dict:
+ """Trigger export for this capture session.
+
+ Call repeatedly to poll for completion. Returns ``export_status``
+ of ``"exporting"`` while in progress, ``"exported"`` with
+ ``video_id``, ``stream_url``, and ``player_url`` when done.
+
+ :param str video_channel_id: Optional channel ID of the video to export.
+ Defaults to the primary video channel.
+ :param str ws_connection_id: WebSocket connection ID for push
+ notification when export completes (optional).
+ :return: Export response with session_id, video_channel_id,
+ export_status, and video_id/stream_url/player_url when exported.
+ :rtype: dict
+ """
+ data = {}
+ if video_channel_id:
+ data["video_channel_id"] = video_channel_id
+ if ws_connection_id:
+ data["connection_id"] = ws_connection_id
+
+ return self._connection.post(
+ path=f"{ApiPath.collection}/{self.collection_id}/{ApiPath.capture}/{ApiPath.session}/{self.id}/{ApiPath.export}",
+ data=data,
+ )
diff --git a/videodb/client.py b/videodb/client.py
index a01d10c..9a322db 100644
--- a/videodb/client.py
+++ b/videodb/client.py
@@ -19,6 +19,10 @@
from videodb.audio import Audio
from videodb.image import Image
from videodb.meeting import Meeting
+from videodb.agentic_stream import AgenticStream
+from videodb.schedule import Schedule
+from videodb.capture_session import CaptureSession
+from videodb.websocket_client import WebSocketConnection
from videodb._upload import (
upload,
@@ -30,23 +34,28 @@
class Connection(HttpClient):
"""Connection class to interact with the VideoDB"""
- def __init__(self, api_key: str, base_url: str, **kwargs) -> "Connection":
+ def __init__(self, api_key: str = None, session_token: str = None, base_url: str = None, **kwargs) -> "Connection":
"""Initializes a new instance of the Connection class with specified API credentials.
Note: Users should not initialize this class directly.
Instead use :meth:`videodb.connect() `
:param str api_key: API key for authentication
+ :param str session_token: Session token for authentication (alternative to api_key)
:param str base_url: Base URL of the VideoDB API
- :raise ValueError: If the API key is not provided
+ :raise ValueError: If neither API key nor session token is provided
:return: :class:`Connection ` object, to interact with the VideoDB
:rtype: :class:`videodb.client.Connection`
"""
+ # Use whichever token is provided
+ access_token = api_key or session_token
+
self.api_key = api_key
+ self.session_token = session_token
self.base_url = base_url
self.collection_id = "default"
super().__init__(
- api_key=api_key, base_url=base_url, version=__version__, **kwargs
+ api_key=access_token, base_url=base_url, version=__version__, **kwargs
)
def get_collection(self, collection_id: Optional[str] = "default") -> Collection:
@@ -347,3 +356,260 @@ def get_meeting(self, meeting_id: str) -> Meeting:
meeting = Meeting(self, id=meeting_id, collection_id="default")
meeting.refresh()
return meeting
+
+ def create_capture_session(
+ self,
+ end_user_id: str,
+ collection_id: str = "default",
+ callback_url: str = None,
+ ws_connection_id: str = None,
+ metadata: dict = None,
+ ) -> CaptureSession:
+ """Create a capture session.
+
+ :param str end_user_id: ID of the end user
+ :param str collection_id: ID of the collection (default: "default")
+ :param str callback_url: URL to receive callback (optional)
+ :param str ws_connection_id: WebSocket connection ID (optional)
+ :param dict metadata: Custom metadata (optional)
+ :return: :class:`CaptureSession ` object
+ :rtype: :class:`videodb.capture_session.CaptureSession`
+ """
+ data = {
+ "end_user_id": end_user_id,
+ }
+ if callback_url:
+ data["callback_url"] = callback_url
+ if ws_connection_id:
+ data["ws_connection_id"] = ws_connection_id
+ if metadata:
+ data["metadata"] = metadata
+
+ response = self.post(
+ path=f"{ApiPath.collection}/{collection_id}/{ApiPath.capture}/{ApiPath.session}",
+ data=data,
+ )
+ # Extract id and collection_id from response to avoid duplicate arguments
+ session_id = response.pop("session_id", None) or response.pop("id", None)
+ response_collection_id = response.pop("collection_id", collection_id)
+ return CaptureSession(
+ self, id=session_id, collection_id=response_collection_id, **response
+ )
+
+ def get_capture_session(
+ self, session_id: str, collection_id: str = "default"
+ ) -> CaptureSession:
+ """Get a capture session by its ID.
+
+ :param str session_id: ID of the capture session
+ :param str collection_id: ID of the collection (default: "default")
+ :return: :class:`CaptureSession ` object
+ :rtype: :class:`videodb.capture_session.CaptureSession`
+ """
+ response = self.get(
+ path=f"{ApiPath.collection}/{collection_id}/{ApiPath.capture}/{ApiPath.session}/{session_id}"
+ )
+
+ # If response is wrapped in 'data', extract it
+ if "data" in response and isinstance(response["data"], dict):
+ response = response["data"]
+
+ # Normalize rtstreams before passing to CaptureSession
+ for rts in response.get("rtstreams", []):
+ if isinstance(rts, dict):
+ if "rtstream_id" in rts and "id" not in rts:
+ rts["id"] = rts.pop("rtstream_id")
+ if "collection_id" not in rts:
+ rts["collection_id"] = collection_id
+
+ # Extract id and collection_id from response to avoid duplicate arguments
+ response.pop("id", None) # Remove id from response
+ response.pop("collection_id", None) # Remove collection_id from response
+
+ return CaptureSession(
+ self, id=session_id, collection_id=collection_id, **response
+ )
+
+ def list_capture_sessions(
+ self,
+ collection_id: str = "default",
+ status: str = None,
+ ) -> list[CaptureSession]:
+ """List capture sessions.
+
+ :param str collection_id: ID of the collection (default: "default")
+ :param str status: Filter sessions by status (optional)
+ :return: List of :class:`CaptureSession ` objects
+ :rtype: list[:class:`videodb.capture_session.CaptureSession`]
+ """
+ params = {}
+ if status:
+ params["status"] = status
+
+ response = self.get(
+ path=f"{ApiPath.collection}/{collection_id}/{ApiPath.capture}/{ApiPath.session}",
+ params=params,
+ )
+
+ sessions = []
+ for session_data in response.get("sessions", []):
+ session_id = session_data.pop("id", None) or session_data.pop(
+ "session_id", None
+ )
+ # Normalize rtstreams
+ for rts in session_data.get("rtstreams", []):
+ if isinstance(rts, dict):
+ if "rtstream_id" in rts and "id" not in rts:
+ rts["id"] = rts.pop("rtstream_id")
+ if "collection_id" not in rts:
+ rts["collection_id"] = collection_id
+ # Remove collection_id from data
+ session_data.pop("collection_id", None)
+ sessions.append(
+ CaptureSession(
+ self, id=session_id, collection_id=collection_id, **session_data
+ )
+ )
+ return sessions
+
+ def connect_websocket(self, collection_id: str = "default") -> WebSocketConnection:
+ """Connect to the VideoDB WebSocket service.
+
+ :param str collection_id: ID of the collection (default: "default")
+ :return: :class:`WebSocketConnection ` object
+ :rtype: :class:`videodb.websocket_client.WebSocketConnection`
+ """
+ path = f"{ApiPath.collection}/{collection_id}/{ApiPath.websocket}"
+ response = self.get(path=path)
+ websocket_url = response.get("websocket_url")
+ return WebSocketConnection(url=websocket_url)
+
+ def generate_client_token(self, expires_in: int = 86400) -> str:
+ """Generate a client token for capture operations.
+
+ :param int expires_in: Expiration time in seconds (default: 86400)
+ :return: Client token string
+ :rtype: str
+ """
+ response = self.post(
+ path=f"{ApiPath.capture}/{ApiPath.session}/{ApiPath.token}",
+ data={"expires_in": expires_in},
+ )
+ return response.get("token")
+
+ def create_agentic_stream(
+ self,
+ name: str,
+ template_id: str,
+ prompt: str,
+ sources: Optional[List[str]] = None,
+ max_duration: Optional[int] = None,
+ voice: Optional[str] = None,
+ language: Optional[str] = None,
+ aspect_ratio: Optional[str] = None,
+ captions: Optional[bool] = None,
+ model: Optional[str] = None,
+ callback_url: Optional[str] = None,
+ callback_data: Optional[dict] = None,
+ ) -> AgenticStream:
+ """Create an agentic stream in the default collection.
+
+ Shortcut for ``conn.get_collection().create_agentic_stream(...)``.
+
+ :param str name: Human-readable name for the stream
+ :param str template_id: Template ID from
+ :meth:`Collection.list_agentic_stream_templates() `
+ :param str prompt: Content instructions for the agent
+ :param list sources: (optional) Keywords and/or website URLs for research
+ :param int max_duration: (optional) Target output duration in seconds
+ :param str voice: (optional) TTS voice name for narration
+ :param str language: (optional) Output language
+ :param str aspect_ratio: (optional) "16:9", "9:16", or "1:1"
+ :param bool captions: (optional) Burn captions into the output
+ :param str model: (optional) Quality tier: "basic" or "pro"
+ :param str callback_url: (optional) Default webhook for all runs
+ :param dict callback_data: (optional) Data echoed in webhook payloads
+ :return: The created agentic stream
+ :rtype: :class:`AgenticStream `
+ """
+ return self.get_collection().create_agentic_stream(
+ name=name,
+ template_id=template_id,
+ prompt=prompt,
+ sources=sources,
+ max_duration=max_duration,
+ voice=voice,
+ language=language,
+ aspect_ratio=aspect_ratio,
+ captions=captions,
+ model=model,
+ callback_url=callback_url,
+ callback_data=callback_data,
+ )
+
+ def create_schedule(
+ self,
+ target: AgenticStream,
+ trigger: str,
+ timezone: Optional[str] = None,
+ callback_url: Optional[str] = None,
+ callback_data: Optional[dict] = None,
+ ) -> Schedule:
+ """Create a recurring schedule that triggers runs of an agentic stream.
+
+ :param AgenticStream target: The agentic stream to trigger
+ :param str trigger: EventBridge cron expression, e.g. ``"0 9 * * ? *"``
+ :param str timezone: (optional) IANA timezone name, default UTC
+ :param str callback_url: (optional) Webhook for each scheduled run
+ :param dict callback_data: (optional) Data echoed in webhook payloads
+ :return: The created schedule
+ :rtype: :class:`Schedule `
+ """
+ data = {
+ "target_type": "agentic_stream",
+ "target_id": target.id,
+ "trigger": trigger,
+ }
+ if timezone is not None:
+ data["timezone"] = timezone
+ if callback_url is not None:
+ data["callback_url"] = callback_url
+ if callback_data is not None:
+ data["callback_data"] = callback_data
+ response = self.post(path=f"{ApiPath.schedule}/", data=data)
+ return Schedule(
+ self,
+ id=response.get("id"),
+ **{k: v for k, v in response.items() if k != "id"},
+ )
+
+ def get_schedule(self, schedule_id: str) -> Schedule:
+ """Get a schedule by its ID.
+
+ :param str schedule_id: ID of the schedule
+ :return: The schedule
+ :rtype: :class:`Schedule `
+ """
+ response = self.get(path=f"{ApiPath.schedule}/{schedule_id}/")
+ return Schedule(
+ self,
+ id=response.get("id"),
+ **{k: v for k, v in response.items() if k != "id"},
+ )
+
+ def list_schedules(self) -> List[Schedule]:
+ """List all schedules for the authenticated user.
+
+ :return: List of schedules
+ :rtype: List[:class:`Schedule `]
+ """
+ response = self.get(path=f"{ApiPath.schedule}/")
+ schedules = (response or {}).get("schedules", [])
+ return [
+ Schedule(
+ self,
+ id=sched.get("id"),
+ **{k: v for k, v in sched.items() if k != "id"},
+ )
+ for sched in schedules
+ ]
diff --git a/videodb/collection.py b/videodb/collection.py
index 994cda6..89e08a8 100644
--- a/videodb/collection.py
+++ b/videodb/collection.py
@@ -1,20 +1,24 @@
import logging
-from typing import Optional, Union, List, Dict, Any, Literal
+from typing import Optional, Union, List, Dict, Any, Literal, Tuple
from videodb._upload import (
upload,
)
from videodb._constants import (
ApiPath,
IndexType,
+ MediaType,
SearchType,
+ _InternalSearchType,
)
from videodb.video import Video
from videodb.audio import Audio
from videodb.image import Image
from videodb.meeting import Meeting
-from videodb.rtstream import RTStream
-from videodb.search import SearchFactory, SearchResult
+from videodb.agentic_stream import AgenticStream
+from videodb.capture_session import CaptureSession
+from videodb.rtstream import RTStream, RTStreamSearchResult, RTStreamShot
+from videodb.search import AskResponse, SearchFactory, SearchResponse, SearchResult, warn_legacy_search_once
logger = logging.getLogger(__name__)
@@ -167,23 +171,56 @@ def delete_image(self, image_id: str) -> None:
)
def connect_rtstream(
- self, url: str, name: str, sample_rate: int = None
+ self,
+ url: str,
+ name: str,
+ media_types: List[str] = None,
+ sample_rate: int = None,
+ store: bool = None,
+ enable_transcript: bool = None,
+ ws_connection_id: str = None,
) -> RTStream:
"""Connect to an rtstream.
:param str url: URL of the rtstream
:param str name: Name of the rtstream
- :param int sample_rate: Sample rate of the rtstream (optional)
+ :param list media_types: List of media types to capture (default: [MediaType.video]).
+ Valid values: :attr:`MediaType.audio`, :attr:`MediaType.video`
+ :param int sample_rate: Sample rate of the rtstream (optional, server default: 30)
+ :param bool store: Enable recording storage (optional, default: False).
+ When True, the stream recording is stored and can be exported via :meth:`RTStream.export`.
+ :param bool enable_transcript: Enable real-time transcription (optional)
+ :param str ws_connection_id: WebSocket connection ID for receiving events (optional)
:return: :class:`RTStream ` object
"""
+ if media_types is None:
+ media_types = [MediaType.video]
+
+ valid = {MediaType.audio, MediaType.video}
+ invalid = set(media_types) - valid
+ if invalid or not media_types:
+ raise ValueError(
+ f"Invalid media_types: {invalid}. Valid values: {MediaType.audio}, {MediaType.video}"
+ )
+
+ data = {
+ "collection_id": self.id,
+ "url": url,
+ "name": name,
+ "media_types": media_types,
+ }
+ if sample_rate is not None:
+ data["sample_rate"] = sample_rate
+ if store is not None:
+ data["store"] = store
+ if enable_transcript is not None:
+ data["enable_transcript"] = enable_transcript
+ if ws_connection_id is not None:
+ data["ws_connection_id"] = ws_connection_id
+
rtstream_data = self._connection.post(
path=f"{ApiPath.rtstream}",
- data={
- "collection_id": self.id,
- "url": url,
- "name": name,
- "sample_rate": sample_rate,
- },
+ data=data,
)
return RTStream(self._connection, **rtstream_data)
@@ -199,14 +236,34 @@ def get_rtstream(self, id: str) -> RTStream:
)
return RTStream(self._connection, **rtstream_data)
- def list_rtstreams(self) -> List[RTStream]:
+ def list_rtstreams(
+ self,
+ limit: Optional[int] = None,
+ offset: Optional[int] = None,
+ status: Optional[str] = None,
+ name: Optional[str] = None,
+ ordering: Optional[str] = None,
+ ) -> List[RTStream]:
"""List all rtstreams in the collection.
+ :param int limit: Number of rtstreams to return (optional)
+ :param int offset: Number of rtstreams to skip (optional)
+ :param str status: Filter by status (optional)
+ :param str name: Filter by name (optional)
+ :param str ordering: Order results by field (optional)
:return: List of :class:`RTStream ` objects
:rtype: List[:class:`videodb.rtstream.RTStream`]
"""
+ params = {
+ "limit": limit,
+ "offset": offset,
+ "status": status,
+ "name": name,
+ "ordering": ordering,
+ }
rtstreams_data = self._connection.get(
path=f"{ApiPath.rtstream}",
+ params={key: value for key, value in params.items() if value is not None},
)
return [
RTStream(self._connection, **rtstream)
@@ -388,7 +445,9 @@ def dub_video(
"""Dub a video.
:param str video_id: ID of the video to dub
- :param str language_code: Language code to dub the video to
+ :param str language_code: Language code to dub the video to.
+ Use ISO 639-1 codes (e.g., "en", "hi", "fr") or regional
+ variants with underscores (e.g., "en_us", "en_uk", "en_au").
:param str callback_url: URL to receive the callback (optional)
:return: :class:`Video