BlackVideo Mini Player v2.4.0 — Standalone Mode, Sprite Preview, LLM Captions, Whisper Fix, Progress %, Auto-Updater
Latest✅ What's in This Release
🖥️ Standalone Mode — Open Without a Video
The player can now be launched with no arguments and shows a welcome screen instead of exiting.
Before (v2.3.0):
blackvideo-player.exe ← exits immediately or crashes
blackvideo-player.exe video ← only way to use it
After (v2.4.0):
blackvideo-player.exe ← opens welcome screen
- Welcome screen renders inside the SDL2 window: large
▶icon, "Open a video or drop a file here" prompt, and an Open File button in the centre — all drawn in C viaui_overlay.c - Drag and drop: drop any video file onto the window at any time (welcome screen or during playback) — handled via
SDL_DROPFILEevent, requiresSDL_EventState(SDL_DROPFILE, SDL_ENABLE)at init player.adsgains aNo_Videostate alongsidePlaying/Paused/Stoppedmain.adbno longer exits on missing argument — passes""toPlayer.Runwhich entersNo_Videostate- Right-click Open Video File and the Open button both work from the welcome screen
How it works in Ada + C:
-- player.ads
type Player_State is (No_Video, Stopped, Playing, Paused);
-- player.adb: Run() — no argument guard removed
if Video_File = "" then
State := No_Video;
UI_Overlay.Set_No_Video_Mode (True);
end if;// ui_overlay.c: draw welcome screen when g_no_video = 1
void bv_ui_draw(...) {
if (g_no_video) { draw_welcome_screen(ren, w, h); return; }
...
}🖼️ Sprite Thumbnail Preview on Hover
Hovering the seek bar shows a small thumbnail of the video at that position, with a timestamp label beneath it.
How it works:
When a video opens, a background cmd.exe /c job (same WinExec pattern as Whisper) runs:
ffmpeg -i video.mp4 -vf "fps=1/10,scale=160:-1" -q:v 5
build\cache\videoname_%04d.jpgThis extracts one frame every 10 seconds as 160px-wide JPEG thumbnails and stores them in build\cache\<video_hash>\. On a 90-minute film that is ~540 small JPEGs (~2–3 MB total), generated in roughly 10–20 seconds in the background.
Hover rendering:
ui_overlay.ctracks mouse X over the seek bar and computeshover_pos = duration * (mouse_x - bar_x) / bar_w- Frame index =
hover_pos / 10→ loads the matching JPEG viaSDL_image(orstb_image— no extra DLL) - Renders a 160×90 popup box above the scrubber with the frame and a timestamp label below it
- Popup disappears when the mouse leaves the seek bar area
New files:
src/thumb_cache.ads / .adb— Ada module: hash video path, check cache dir, launch ffmpeg job, map hover position → JPEG filenamecsrc/thumb_preview.c— C:SDL_Textureloading (viastb_image.h, header-only), draw popup box
Cache location: build\cache\<8-char-hash-of-video-path>\frame_%04d.jpg
Cleared by right-click → Clear Thumbnail Cache (new menu item).
⚡ Whisper Performance Fix — Why It Was So Slow
Root cause (the real problem):
whisper_bridge.adb's Generate() function calls C_System() (the C system() function) to run both ffmpeg and whisper-cli. C_System is fully synchronous — it blocks the calling thread until the command exits. But Launch_Whisper in player.adb calls it via WinExec with cmd.exe /c "ffmpeg ... && whisper-cli ...".
WinExec is nominally asynchronous but its behaviour with cmd.exe /c "..." on modern Windows is unreliable — the cmd shell can hold the process slot open or Windows Defender / real-time antivirus scans the newly created .wav and .srt files before releasing them, causing the job to appear stalled for minutes. Additionally, whisper-cli with the ggml-base model runs on CPU only and is genuinely slow: expect ~0.3× real-time on a mid-range CPU (a 60-second video takes ~3–5 minutes, not hours — something else is wrong).
The real culprits causing "hours":
- Windows Defender scans every
.wavand.srtfile whisper-cli writes. On some machines this causes 100% disk hold for the duration. WinExec+cmd.exe /cwith a long quoted string — Windows has a 8191-char limit oncmd.execommand lines. If the video path is long, the command silently truncates and whisper-cli never starts — butWinExecreturns success (≥ 32) andWhisper_RunningstaysTrueforever, showing "generating..." permanently.- The
ggml-basemodel is genuinely slow for long content. A 2-hour film can take 30–60 minutes on CPU.
Fixes in v2.4.0:
- Replace
WinExecwithCreateProcess— proper async process launch with aPROCESS_INFORMATIONhandle we keep alive and poll withGetExitCodeProcess(hProcess, &code); code == STILL_ACTIVE. This is reliable on all Windows versions and not affected by the cmd.exe quoting issues. - Write a sentinel
.donefile — at the end of the whisper batch,echo done > <out>.done.Check_Whisper_Donepolls for the.donefile, not the.srt, so we know the SRT is fully written before loading it. - Path length guard — if the full command string exceeds 7500 chars, the batch is written to a temp
.batfile in%TEMP%andCreateProcessrunscmd.exe /c that.batinstead. No truncation. - Timeout detection — if
Whisper_Runninghas been true for > 30 minutes with no.donefile, the status banner changes toWhisper: stalled? Check Defender / consoleand a[Whisper] TIMEOUTmessage is printed to console. - Better model advice —
WHISPER_SETUP.mdupdated with the Windows Defender exclusion path recommendation.
New Ada/C additions:
-- player.adb
Whisper_H_Process : System.Address := System.Null_Address;
Whisper_Start_Tick : Interfaces.C.unsigned := 0;
function CreateProcess (...) return Boolean
with Import, Convention => C, External_Name => "bv_create_process";
-- csrc/process_helpers.c
int bv_create_process(const char *cmd, HANDLE *hProcess_out);
int bv_process_still_running(HANDLE hProcess);
void bv_close_handle(HANDLE h);📊 Whisper / LLM Progress Percentage
Instead of a static Whisper: generating..., the status banner now shows a live percentage:
Whisper: generating... 23% [████████░░░░░░░░░░░░░░░░░░░░░░]
How whisper-cli outputs progress:
whisper-cli writes progress lines to stderr in the form:
whisper_print_progress_callback: progress = 23%
Implementation:
bv_create_processis replaced withbv_create_process_with_pipewhich creates a read pipe on stderr of the child process (usingCreatePipe+STARTUPINFO.hStdError = write_end)- Each frame,
Check_Whisper_ProgresscallsPeekNamedPipe(non-blocking) to read any available stderr bytes, scans for theprogress = NN%pattern, and callsUI_Overlay.Set_Whisper_Status("Whisper: generating... NN%") - The C overlay draws a mini progress bar beside the text in the banner
Same pipe approach applies to LLM providers that stream token output (see below).
🤖 LLM Cloud Caption Generation
Six cloud providers available from the context menu — right-click → Generate Captions (LLM) ▶ → sub-menu:
| Provider | Model used | Notes |
|---|---|---|
| Claude (Anthropic) | claude-sonnet-4-5 |
Audio via base64 or transcript |
| OpenAI | gpt-4o-audio-preview |
Native audio input |
| Gemini (Google) | gemini-1.5-pro |
Audio file upload |
| DeepSeek | deepseek-chat |
Text transcript → SRT |
| Grok (xAI) | grok-2 |
Text transcript → SRT |
Architecture — two-step approach (no audio upload for text-only providers):
- Run whisper-cli locally for a fast draft transcript (text only, no SRT timing) — takes ~30 seconds for a 60s clip with
ggml-tiny - Send the transcript text + video metadata to the LLM with a prompt: "Convert this transcript to a properly timed SRT subtitle file. Duration: Xs. Speaker count: N."
- The LLM returns a formatted
.srtwhich is auto-loaded into Track 2
For OpenAI and Gemini which support native audio, step 1 is skipped and the WAV is uploaded directly.
API key storage — %APPDATA%\BlackVideo\keys.cfg:
ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-...
GEMINI_API_KEY=AIza...
DEEPSEEK_API_KEY=...
GROK_API_KEY=...First use: context menu shows Set API Key for [Provider] — a small SDL2 text input overlay appears (drawn in C). Keys are written to keys.cfg and reused on subsequent sessions.
HTTP in Ada + C:
csrc/llm_client.c— usesWinINet(available on all Windows, no extra DLL) for HTTPS POST. No libcurl dependency.src/llm_bridge.ads / .adb— Ada wrapper:Send_Request(Provider, Transcript, Duration)→ returns raw SRT string- Streaming responses read via
InternetReadFileloop, status banner updated with token count:Claude: generating... 142 tokens
New context menu layout:
Open Video File...
─────────────────────
Subtitle: Off
Track 1 — filename.srt
Track 2 — (empty)
Track 3 — (empty)
─────────────────────
Generate Captions (Whisper)
Translate to English (Whisper)
─────────────────────
Generate Captions (LLM) ▶
Claude (Anthropic)
OpenAI
Gemini
DeepSeek
Grok
─────────────────────
Clear Thumbnail Cache
─────────────────────
Check for Updates...
🔔 Auto-Updater — GitHub Release Check
On every launch, the player silently checks https://raw.githubusercontent.com/BlackBlazent/blackvideo-mini-player/main/latest.json using WinINet. If the version field is newer than the compiled-in version string, a notification badge appears on the ⋮ menu button.
latest.json format (add to repo root):
{
"version": "2.4.0",
"release_url": "https://github.com/BlackBlazent/blackvideo-mini-player/releases/tag/v2.4.0",
"download_url": "https://sourceforge.net/projects/blackvideo-mini-player/files/blackvideo-mini-player-v2.4.0.win.zip/download",
"notes": "Standalone mode, sprite preview, LLM captions, Whisper fix"
}Context menu → Check for Updates... shows a small SDL2 overlay:
┌─────────────────────────────────┐
│ ✓ Update available: v2.4.0 │
│ "Standalone mode, sprite..." │
│ [Download] [Later] │
└─────────────────────────────────┘
Clicking Download opens the download_url in the default browser via ShellExecuteA.
How version comparison works:
-- src/updater.ads / .adb
function Is_Newer (Remote, Local : String) return Boolean;
-- Splits "2.4.0" → (2, 4, 0) and compares lexicographically.
-- Returns True if Remote > Local.// csrc/llm_client.c — reused for the update check
// bv_http_get(url, out_buf, buf_size) → bytes read
// Uses WinINet: InternetOpen → InternetOpenUrl → InternetReadFile
// Same function used by LLM client, so no extra code.On launch: check is done asynchronously via CreateThread so it never delays startup. Result is stored in a global; the menu badge is drawn on the next frame after the thread completes.
📁 New Files in This Release
| File | Purpose |
|---|---|
src/thumb_cache.ads / .adb |
Video thumbnail strip extraction and cache management |
src/llm_bridge.ads / .adb |
LLM provider API wrapper (Claude, OpenAI, Gemini, DeepSeek, Grok) |
src/updater.ads / .adb |
GitHub release version check, semver comparison |
csrc/thumb_preview.c |
SDL2 thumbnail popup rendering, stb_image.h JPEG load |
csrc/process_helpers.c |
CreateProcess + pipe helpers replacing WinExec |
csrc/llm_client.c |
WinINet HTTPS client (LLM requests + update check) |
include/stb_image.h |
Single-header JPEG/PNG loader (no extra DLL) |
latest.json |
Repo root — current version manifest for auto-updater |
%APPDATA%\BlackVideo\keys.cfg |
Runtime-created API key storage (not in repo) |
🔧 Modified Files
| File | Changes |
|---|---|
src/player.adb |
No_Video state, SDL_DROPFILE, CreateProcess whisper launch, progress pipe polling, updater thread start, LLM dispatch |
src/player.ads |
No_Video state in Player_State enum |
src/main.adb |
Remove argument guard — pass "" on no args |
src/ui_overlay.ads / .adb |
New C imports: welcome screen, thumbnail popup, update badge, LLM sub-menu, key input overlay |
csrc/ui_overlay.c |
Welcome screen, thumbnail popup, progress bar in banner, update badge on menu button, LLM sub-menu, API key input overlay |
src/whisper_bridge.adb |
Generate uses CreateProcess + pipe; sentinel .done file; path-length guard; timeout detection |
scripts/build.bat |
Compile process_helpers.c, thumb_preview.c, llm_client.c; define STB_IMAGE_IMPLEMENTATION |
scripts/build.sh |
Same for Linux/macOS |
blackvideo_player.gpr |
Add new .o files to linker; add -lwininet for Windows |
🎮 Updated Controls
Context Menu
| Item | Action |
|---|---|
| Open Video File... | Open file dialog (works from welcome screen too) |
| Subtitle: Off / Track N | Toggle, shows active track name |
| Track 1 / 2 / 3 | Load or activate subtitle slot |
| Generate Captions (Whisper) | Offline transcription with live % progress |
| Translate to English (Whisper) | Offline translation with live % progress |
| Generate Captions (LLM) ▶ | Sub-menu: Claude / OpenAI / Gemini / DeepSeek / Grok |
| Clear Thumbnail Cache | Delete build\cache\ for this video |
| Check for Updates... | Manual update check overlay |
📦 New Requirements (Windows)
All existing DLL requirements from v2.3.0 remain. No new DLLs required:
stb_image.his header-only — compiled directly intothumb_preview.cWinINetis part of Windows (wininet.dll) — already present on all Windows 10/11CreateProcessis part ofkernel32.dll— no new import
New linker flag added to GPR: -lwininet
Built with Ada 2012 · FFmpeg 8.x · SDL2 · SDL2_ttf · whisper.cpp · WinINet · GNAT Community 2021
Full Changelog: v2.3.0...v2.4.0
