An open API service indexing awesome lists of open source software.

https://github.com/ygit/videodb-demo


https://github.com/ygit/videodb-demo

Last synced: about 1 month ago
JSON representation

Awesome Lists containing this project

README

          

# 🎬 VideoDB Reel Builder

> Turn long-form videos into short, vertical reels β€” paste a URL, write a few topic queries, get clips you can embed and share.

A small Flask playground built on top of the [VideoDB Python SDK](https://github.com/video-db/videodb-python). Designed to be readable in a single sitting and easy to fork.

![Reel builder home page in dark mode](docs/screenshots/home-dark.png)

---

## Table of contents

1. [Features](#-features)
2. [Quick start](#-quick-start)
3. [Walkthrough](#-walkthrough)
4. [Architecture](#-architecture)
5. [Pipeline](#-pipeline)
6. [Project structure](#-project-structure)
7. [Configuration](#%EF%B8%8F-configuration)
8. [Writing good queries](#-writing-good-queries)
9. [Limits & known issues](#-limits--known-issues)
10. [Troubleshooting](#-troubleshooting)
11. [How it works](#-how-it-works-under-the-hood)
12. [Tech stack](#%EF%B8%8F-tech-stack)

---

## ✨ Features

- **Multi-video batches** β€” submit many videos in one form; each becomes its own job, all run in parallel.
- **Per-query reels** β€” one short topic per line β‡’ one reel; up to 5 reels per video reframe concurrently.
- **Multiple languages** β€” English / Hindi / Hinglish for the spoken-word index, so non-English content actually gets transcribed.
- **Configurable output** β€” 9:16 vertical / 1:1 square / 16:9 landscape; smart object-tracking or simple centre crop.
- **Live progress** β€” animated rainbow gradient bar with per-phase milestones; auto-retries reframe once on processing timeouts.
- **Dark mode** β€” auto-follows OS preference, with a toggle that persists per-browser. No flash-of-incorrect-theme.
- **Friendly errors** β€” sentence-style queries are caught before they fail; SDK timeouts surface phase-aware actionable messages.
- **XSS-safe** β€” all dynamic UI is built with `textContent`/`createElement`, never `innerHTML`.

## πŸš€ Quick start

You'll need Python 3.10+ and a VideoDB API key.

```bash
# 1. Clone
git clone https://github.com/ygit/videodb-demo
cd videodb-demo

# 2. Get a free API key (50 free uploads, no credit card)
# https://console.videodb.io
cp .env.example .env
# then edit .env and set VIDEO_DB_API_KEY=your-key-here

# 3. Install
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

# 4. Smoke-test the SDK connection
python main.py
# β†’ Connected to collection: ()
# β†’ Videos: 0

# 5. Run the UI
flask --app app run --host 127.0.0.1 --port 5057
```

Open [http://127.0.0.1:5057](http://127.0.0.1:5057).

> πŸ’‘ **Why port 5057 instead of 5000?** Modern macOS hijacks port 5000 for AirPlay Receiver (Control Center). Pick any free port; 5057 is uncommon enough to almost always be available.

## 🎬 Walkthrough

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 🌈 (rainbow brand bar) ☾ β—€ toggleβ”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β”‚
β”‚ Reel builder ◀── gradient hero title β”‚
β”‚ Generate short reels from one or more long-form videos. β”‚
β”‚ β”‚
β”‚ β–Έ How to write good queries β”‚
β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ Video 1 ──────────────────────────────── Γ— ┐ β”‚
β”‚ β”‚ Video URL β”‚ β”‚
β”‚ β”‚ [ https://youtube.com/watch?v=... ] β”‚ β”‚
β”‚ β”‚ Queries Β· one per line Β· one reel per line β”‚ β”‚
β”‚ β”‚ β“˜ One short topic per line, e.g. `key highlights`. β”‚ β”‚
β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
β”‚ β”‚ β”‚ intro β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ key insight β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ demo β”‚ β”‚ β”‚
β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ + Add another video β”‚
β”‚ β”‚
β”‚ Shared settings β”‚
β”‚ Length: [60] Aspect: β—‰ 9:16 β—― 1:1 β—― 16:9 β”‚
β”‚ Mode: β—‰ smart β—― simple β”‚
β”‚ Lang: β—‰ English β—― Hindi β—― Hinglish β”‚
β”‚ β”‚
β”‚ [ Generate reels ] β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

**The flow:**

1. Paste a video URL β€” YouTube, Vimeo, or any direct video URL VideoDB can ingest.
2. Write **one topic per line** in the queries box. Each line becomes one reel.
3. (Optional) Click **+ Add another video** to bundle multiple sources into one batch.
4. Pick your output settings β€” they apply to every reel in the batch.
5. Submit. You're redirected to a live status page that polls every 1.5s.
6. Each reel shows up inline as it finishes. Click to play, or open in the VideoDB player.

## πŸ—οΈ Architecture

```mermaid
flowchart LR
Browser(["πŸ’» Browser"])

subgraph Flask["Flask app β€” single process"]
Routes["Routes
GET /
POST /generate
GET /jobs/
GET /batches/"]
JobStore[("In-memory
job + batch store
+ video URL cache")]
Worker["Worker thread
(daemon, per job)"]
Pool["ThreadPoolExecutor
per-reel parallelism"]
Routes <--> JobStore
Routes -. spawns .-> Worker
Worker --> Pool
Worker <--> JobStore
Pool <--> JobStore
end

VideoDB[("☁️ VideoDB API")]

Browser -- "form submit" --> Routes
Routes -- "302 β†’ /jobs/" --> Browser
Browser -. "polls /status every 1.5s" .-> Routes
Worker -- "upload, index, search, reframe" --> VideoDB
VideoDB -- "stream URLs" --> Browser
```

**Key pieces:**

- **`app.py`** β€” all routes, the in-memory job store (a `dict` guarded by `threading.Lock`), the per-job worker, and the shared video URL cache (so repeat submits of the same URL skip re-upload).
- **`templates/_base.html`** β€” shared layout: brand bar, hero, theme toggle, footer. Other templates extend this.
- **`static/job.js`** β€” shared rendering for both single-job and batch pages: progress bar, milestones, per-reel cards. Polls in a loop until the job reaches a terminal state.

## πŸ”„ Pipeline

```mermaid
sequenceDiagram
autonumber
participant U as User
participant F as Flask
participant W as Worker
participant V as VideoDB

U->>F: POST /generate (URL + queries)
F->>F: Validate (URL, sentence-detection,
length, aspect, mode, lang)
F->>W: Spawn daemon thread per video
F-->>U: 302 β†’ /jobs/ or /batches/

par Worker pipeline
Note over W,V: phase = uploading
W->>V: collection.upload(url)
V-->>W: video_id
Note over W,V: phase = indexing
W->>V: index_spoken_words(language_code)
V-->>W: indexed
Note over W,V: phase = reeling β€” up to 5 in parallel
loop each query
W->>V: video.search(query)
V-->>W: shots
W->>W: widen window to length seconds
W->>V: video.reframe(start, end, target, mode)
V-->>W: stream_url
opt reframe stuck
W->>V: retry once
V-->>W: stream_url
end
end
and Browser polls
loop every 1.5s until terminal
U->>F: GET /jobs//status
F-->>U: { state, progress%, reels[] }
U->>U: render rainbow bar + reel cards
end
end
```

**Phase model:** `queued β†’ uploading β†’ indexing β†’ reeling β†’ done`

- On error, `failed_phase` is captured so the UI can mark the right milestone with a red βœ—.
- Per-reel state moves through `pending β†’ matching β†’ reframing β†’ ready` (or `skipped` / `error`).

## πŸ“ Project structure

```
videodb-demo/
β”œβ”€β”€ app.py # Flask app: routes, job store, worker pipeline, helpers
β”œβ”€β”€ main.py # Smoke test: connect and list videos
β”œβ”€β”€ requirements.txt # videodb, python-dotenv, flask
β”œβ”€β”€ .env # VIDEO_DB_API_KEY (gitignored)
β”œβ”€β”€ .env.example # template for new clones
β”œβ”€β”€ README.md # this file
β”œβ”€β”€ templates/
β”‚ β”œβ”€β”€ _base.html # shared layout: brand bar, hero, theme toggle, footer
β”‚ β”œβ”€β”€ index.html # form: dynamic video blocks + shared settings
β”‚ β”œβ”€β”€ job.html # single-job status page
β”‚ └── batch.html # multi-video batch status page
└── static/
β”œβ”€β”€ style.css # design tokens, light/dark themes, animations
β”œβ”€β”€ job.js # progress bar, milestones, reel rendering, polling
└── theme.js # dark-mode persistence + OS preference
```

## βš™οΈ Configuration

### Required environment variable

| Variable | Required | Notes |
| -------------------- | -------- | -------------------------------------------------------------- |
| `VIDEO_DB_API_KEY` | yes | Get a free key at[console.videodb.io](https://console.videodb.io) |

The app reads `.env` from the working directory at startup via `python-dotenv`.

### Knobs in `app.py`

| Constant / route field | Default | Purpose |
| ------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `MAX_REEL_CONCURRENCY` | `5` | Cap on parallel reframe calls per job |
| `ASPECT_MAP` | 3 entries | UI labels (`9:16` / `1:1` / `16:9`) β†’ VideoDB reframe presets (`vertical` / `square` / `landscape`) |
| `LANGUAGES` | 3 entries | UI labels β†’`(display_name, language_code)`. `Hinglish` maps to the Hindi code path because VideoDB has no documented code-switched language code. |
| Length input | 60s, range `[5, 300]` | Each reel is widened symmetrically around the matched shot's midpoint |
| `TEMPLATES_AUTO_RELOAD` | `True` | Template edits show on next request without a server restart |

## πŸ’‘ Writing good queries

Each line in the queries box is a **separate semantic search** against the spoken-word transcript. Think about what the speaker would actually *say*, not how you'd describe what you want.

| βœ“ Works well | βœ— Avoid |
| ------------------------ | ---------------------------------------------------------------------------------------- |
| `match highlights` | `give me key highlights, predictions, coach, transfer market & some laughing moments.` |
| `goal celebration` | `show me everything funny in this video` |
| `funny moment` | `make me a fun reel about football transfers` |
| `transfer rumour` | a single line longer than ~12 words |
| `post-match interview` | sentences with `give me`, `show me`, `find me` openers |

The form catches the most common mistake (one long sentence asking for many things) before it ever reaches VideoDB. If your query gets rejected with *"that looks like one long sentence β€” split it"*, that's why.

**Other tips:**

- **Match the speaker's vocabulary.** If commentators say "best moments", `best moments` will hit better than abstract synonyms.
- **Pick the right language.** If the audio is Hindi or Hinglish but you submit with English selected, the transcript will be sparse and English queries won't match. Switch the language radio.
- **Short videos are reliable.** Free-tier processing has time limits β€” see Limits below.

## 🚧 Limits & known issues

| Limit | Notes |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Max video length: 3 hours** | Free-tier upload cap. Longer videos return*"Video is over 3 hours, please reachout to contact@videodb.io to increase the quota"*. Email them or trim before uploading. |
| **50 free uploads** | Resets per-account; check[console.videodb.io](https://console.videodb.io) for current usage. Easy to burn through if you make the URL public β€” see deployment notes below. |
| **Reframe timeout** | The SDK can give up with*"Stuck on processing status"* on long source videos. The app retries once automatically, then surfaces a phase-aware actionable message. |
| **In-memory state** | Job and batch state lives in process memory. Flask restart = jobs in flight are lost. Don't deploy this as-is. |
| **Single-process** | The current design assumes one worker. Multi-worker `gunicorn` would need Redis-backed state β€” out of scope for this playground. |

## 🩺 Troubleshooting

**`Address already in use` on port 5000 (macOS)**
Modern macOS uses port 5000 for AirPlay Receiver. Use a different port (`--port 5057`) or disable AirPlay Receiver in *System Settings β†’ General β†’ AirDrop & Handoff*.

**`AuthenticationError: Authentication failed`**
Your `VIDEO_DB_API_KEY` is missing or wrong. Confirm `.env` exists and contains `VIDEO_DB_API_KEY=...`. Restart the server after editing.

**Job state shows "failed" with no detail**
Check the Flask process logs β€” the full SDK traceback is logged via `log.exception(...)`. The UI message is intentionally generic for non-VideoDB exceptions to avoid leaking internals; the logs have the truth.

**All reels skip with "no match"**
The query didn't semantically match the transcript. See [Writing good queries](#-writing-good-queries). If the audio language is wrong, fix the language radio. If the video has very little speech, no query will match.

**Form rejects my query as "looks like one long sentence"**
That's the friendly server-side guard. Split your topics into multiple lines.

**Job goes to "failed" during reframe**
The SDK couldn't keep up with VideoDB's processing. The app already retries once. If both attempts fail, the message will tell you to try a shorter source video or contact VideoDB support.

## πŸ”§ How it works under the hood

A few non-obvious choices worth pointing out:

- **`_get_or_upload(url)`** caches by URL with a per-URL `threading.Lock` so simultaneous form submits of the same URL don't race and double-upload. The cache only evicts on a "not found" SDK error β€” transient errors keep the cached id intact.
- **`_compute_progress(state, reels, failed_phase)`** computes both the rainbow-bar percent (0–100) and the per-step milestone state (`done` / `active` / `pending` / `error`). When a job errors, the bar fills to *the failing phase's percent* (12 / 30 / 70) and the right step gets a red βœ— β€” instead of the all-grey-with-full-red rendering that earlier versions had.
- **`_looks_like_compound_sentence(qtext)`** catches the most common query mistake: one long sentence containing multiple topics. Three signals: word count > 12, sentence-style opener (`give me`, `show me`, `find me`, etc.), or a separator (`,`, `;`, `&`, `and`) combined with > 5 words.
- **`_is_processing_timeout(exc)`** pattern-matches the SDK's *bare* `Exception("Stuck on processing status")` β€” which isn't a typed `VideodbError` β€” so we can detect it, retry once via `_reframe_with_retry`, and surface `_processing_timeout_msg(phase)` if it persists.
- **Per-reel rendering is XSS-safe.** `static/job.js` builds DOM exclusively with `textContent` and `createElement`. Earlier versions used `innerHTML` interpolation, which would have executed `` tags inside transcript text or query strings.
- **Inline pre-body theme script** in `_base.html` reads `localStorage.theme` *before* `<body>` parses, so users in dark mode never see a flash of light. The toggle button just flips `data-theme` on `<html>` and persists.

## πŸ› οΈ Tech stack

- **Backend**: Python 3.12, Flask 3, `threading` + `concurrent.futures.ThreadPoolExecutor`
- **Frontend**: Vanilla JavaScript (no framework), Inter font, CSS custom properties for theming
- **Video pipeline**: [VideoDB Python SDK](https://github.com/video-db/videodb-python) (`videodb`)

---

## Adding more screenshots

The hero image above lives at `docs/screenshots/home-dark.png`. Drop additional PNGs in the same directory and reference them with `![alt](docs/screenshots/foo.png)` β€” they'll render on GitHub. Fast capture on macOS:

```bash
mkdir -p docs/screenshots
# Cmd+Shift+4 β†’ drag-select β†’ screenshot lands on Desktop
mv ~/Desktop/Screen*.png docs/screenshots/job-running.png
```

---

Built with [VideoDB](https://videodb.io). PRs welcome.