2026-06-02 20:24:31 -07:00
2026-06-02 20:24:31 -07:00
2026-05-30 23:29:44 -07:00
2026-06-02 20:24:31 -07:00
2026-05-30 23:29:44 -07:00
2026-05-30 23:29:44 -07:00
2026-05-30 23:29:44 -07:00
2026-05-31 23:30:35 -07:00
2026-05-30 23:29:44 -07:00
2026-05-30 23:29:44 -07:00
2026-06-02 20:24:31 -07:00

Evanescere

Evanescere turns recorded livestreams into suggested, rendered clips:

  1. Poll IIS WebDAV for finished recordings.
  2. Record files visible during the first scan as a manual-only historical baseline.
  3. For files first seen later, wait until file size is unchanged across polling cycles.
  4. Download the stable source file to Framework-local storage.
  5. Remux FLV/H264 to MP4 and extract ASR-ready audio.
  6. Transcribe Mandarin audio through a FunASR-compatible API.
  7. Ask DeepSeek for ranked timeline-aware clip suggestions.
  8. Generate subtitles, render clips, generate thumbnails with optional local image generation and VTuber overlay, and optionally upload.

The Framework Arch server is the intended production host. The Hyper-V Arch VM can be used for development, API testing, and database/control-plane work.

Architecture

Evanescere is split into a static frontend and an API/worker backend.

  • frontend: React/Vite UI. It builds to static files and is served by nginx in Docker.
  • src/evanescere/api.py: FastAPI backend. It serves JSON only.
  • src/evanescere/scheduler.py: polling loop for IIS WebDAV.
  • src/evanescere/jobs.py: Dramatiq queue actors backed by Redis.
  • src/evanescere/pipeline.py: orchestration for media, ASR, LLM, render, thumbnail, and upload stages.
  • src/evanescere/services: adapters for WebDAV, ffmpeg, FunASR, DeepSeek, subtitles, thumbnails, artifacts, and upload.
  • migrations: Alembic migrations for PostgreSQL.

Runtime data flow:

IIS WebDAV recordings
  -> first scan records existing files as manual-only baseline entries
  -> later scans poll new file sizes
  -> PostgreSQL records video state
  -> Redis queues pipeline job
  -> worker downloads source to configured storage.local_root
  -> ffmpeg remuxes/extracts audio
  -> FunASR creates timestamped transcript
  -> DeepSeek creates clip suggestions
  -> ffmpeg renders clips/subtitles/thumbnails
  -> thumbnail service composes frame/generated image + VTuber overlay + title
  -> uploader adapter runs, or noop for testing

The backend tracks durable state in PostgreSQL. Large media artifacts stay on the Framework SSD under [storage].local_root; WebDAV is used for source ingestion and can later be used for pushing final artifacts back to the Windows server.

Configuration

Runtime configuration is TOML-based. Copy the commented example and edit your private config:

cp config.example.toml config.toml

The application looks for config in this order:

  1. /etc/evanescere/config.toml
  2. ./config.toml
  3. Built-in development defaults, only if no config file exists.

For Docker/Compose, mount your config file to:

/etc/evanescere/config.toml

The checked-in docker-compose.yml mounts local ./config.toml there for the API, workers, and scheduler.

Services And Ports

  • Frontend: http://localhost:3000
  • Backend API: http://localhost:8081
  • API docs: http://localhost:8081/docs
  • Redis: localhost:6379 when exposed by Compose
  • PostgreSQL: only started by Compose when using the local-db profile

Test Environment

Create and edit config.toml:

cp config.example.toml config.toml

For a self-contained local test stack, use the Compose PostgreSQL profile:

docker compose --profile local-db up --build

Run database migrations:

docker compose run --rm api alembic upgrade head

The scheduler automatically records files visible during its first scan as existing_done. Those historical recordings appear in the UI but are not automatically processed. To establish that baseline manually before starting the scheduler:

docker compose run --rm api evanescere bootstrap-existing

In the frontend, click Sync WebDAV to perform the same safe first-time baseline import or to discover later files. Select any historical FLV row and click its play button to manually queue a test pipeline run.

Useful test commands:

docker compose run --rm api evanescere scan
docker compose run --rm api evanescere videos
docker compose run --rm api evanescere run-video VIDEO_ID
docker compose logs -f api scheduler worker-media worker-ai

Automated tests are written with pytest. After installing Python dev dependencies:

pytest -q

Without installing dependencies locally, the available lightweight checks are:

python -m compileall src tests migrations
docker compose config --quiet

For safe test/debug runs, set these in config.toml:

[app]
log_level = "DEBUG"

[defaults]
upload_enabled = false

[upload]
adapter = "noop"

adapter = "noop" prevents accidental real uploads. upload_enabled = false lets you inspect rendered artifacts before adding an uploader.

Frontend Development

The frontend reads the backend URL from frontend/public/config.js at runtime:

window.__EVANESCERE_FRONTEND_CONFIG__ = {
  apiBaseUrl: "http://192.168.1.44:8081"
};

Manual frontend development requires npm packages. Install only after you approve package installation:

cd frontend
npm install
npm run dev

The Vite dev server runs on http://localhost:5173. Make sure [app].cors_origins includes that URL.

Static build:

cd frontend
npm run build

The production Compose frontend service builds the static app and serves dist/ through nginx.

Thumbnail Generation

Each rendered clip can produce three thumbnail artifacts:

  • thumbnail_base: a representative frame extracted from the source MP4 near the middle of the clip.
  • thumbnail_generated: optional output from a local image generation command.
  • thumbnail_final: the final composed JPG with optional title and VTuber character overlay.

Default provider:

[thumbnail]
provider = "frame_overlay"

This uses the extracted frame as the background, then programmatically overlays the title and character PNG using Pillow.

To overlay the VTuber character, place transparent PNG variants in a directory. Evanescere picks one randomly for each thumbnail:

[thumbnail]
character_overlay_dir = "/data/evanescere/assets/"
character_scale = 0.42
character_position = "bottom-right"

Supported positions are bottom-right, bottom-left, center-right, and center-left.

For a local image generation model, configure a command provider:

[thumbnail]
provider = "command"
command = "/opt/evanescere-thumbnail/generate-thumbnail"

The command receives JSON on stdin and must create the file named by output_path. This makes it easy to wrap ComfyUI, Stable Diffusion, Flux, or any other local generator. Payload shape:

{
  "video_id": 123,
  "clip_id": 456,
  "source_frame": "/data/evanescere/videos/123/clips/456/thumbnail_base.jpg",
  "output_path": "/data/evanescere/videos/123/clips/456/thumbnail_generated.png",
  "width": 1920,
  "height": 1080,
  "title_zh": "标题",
  "summary_zh": "摘要",
  "reason": "推荐原因",
  "tags": ["tag"],
  "transcript": [
    {"start_sec": 10.0, "end_sec": 15.0, "text": "片段台词"}
  ]
}

After the command writes thumbnail_generated.png, Evanescere still applies the same title and VTuber overlay to produce thumbnail_final.jpg.

Production Deployment

On the Framework Arch server:

  1. Install Docker/Compose.
  2. Copy config.example.toml to config.toml.
  3. Set [database].url to the reachable PostgreSQL server.
  4. Set [redis].url = "redis://redis:6379/0" if using Compose Redis.
  5. Set [storage].local_root = "/data/evanescere" and mount a Framework SSD directory there.
  6. Set [webdav] credentials and base_url.
  7. Set [funasr].base_url to the local FunASR service.
  8. Set [deepseek].api_key.
  9. Keep [upload].adapter = "noop" until the full pipeline has been tested.

Start production services:

docker compose up -d --build
docker compose run --rm api alembic upgrade head
docker compose run --rm api evanescere bootstrap-existing

Then watch logs:

docker compose logs -f api scheduler worker-media worker-ai

If you use your existing PostgreSQL server instead of the Compose local-db profile, do not start the profile. Ensure PostgreSQL accepts connections from the Framework host and that [database].url uses the reachable host/IP.

Config Reference

The full commented example lives in config.example.toml. These are the keys the backend reads:

Section Key Default Meaning
app env dev Free-form environment label included in logs. Use prod on production.
app log_level INFO Python logging level. Use DEBUG for pipeline bring-up.
app log_sql false Enables SQLAlchemy SQL logging and engine echo. Noisy in production.
app cors_origins local frontend URLs Array of frontend origins allowed to call FastAPI.
database url local PostgreSQL URL SQLAlchemy PostgreSQL connection string.
redis url local Redis URL Redis broker URL for Dramatiq jobs.
storage local_root ./storage Root directory for downloaded sources, remuxed MP4s, audio, transcripts, subtitles, clips, and thumbnails.
webdav base_url local placeholder IIS WebDAV directory containing recording files.
webdav username empty WebDAV basic-auth username, if required.
webdav password empty WebDAV basic-auth password, if required.
webdav verify_tls true Whether WebDAV HTTPS certificates are verified.
webdav poll_interval_seconds 60 Scheduler interval. A file is stable after two equal positive size samples.
funasr base_url local FunASR URL FunASR-compatible API base URL. The client calls /audio/transcriptions.
funasr api_key empty Optional bearer token for the FunASR service.
funasr model paraformer-zh Model name sent to FunASR.
deepseek base_url https://api.deepseek.com OpenAI-compatible DeepSeek API base URL.
deepseek api_key empty DeepSeek API key. Required when clip suggestion is enabled.
deepseek model deepseek-v4-pro Model used for clip suggestion.
deepseek temperature 0.2 Sampling temperature for clip suggestion.
llm_prompt system VTuber clip editor prompt Custom system prompt for clip selection. The required JSON schema is appended by code.
llm_prompt user timestamped transcript prompt Custom user prompt template. Supports documented placeholders.
defaults suggest_enabled true Initial setting for automatic LLM clip suggestion.
defaults render_enabled true Initial setting for automatic rendering.
defaults upload_enabled true Initial setting for automatic upload after render. For testing, set false.
defaults preserve_final_artifacts true Whether rendered final artifacts are marked for preservation.
defaults bake_subtitles true Whether rendered clips include hard-baked subtitles by default.
clip min_seconds 30 Minimum LLM clip duration accepted by backend.
clip max_seconds 360 Maximum LLM clip duration accepted by backend.
clip transcript_chunk_seconds 900 Transcript seconds sent to DeepSeek per request.
clip max_candidates_total 20 Maximum suggestions kept across the entire transcript after overlap deduplication.
thumbnail enabled true Enables thumbnail generation during clip render.
thumbnail provider frame_overlay frame_overlay or command.
thumbnail width 1920 Final thumbnail width in pixels.
thumbnail height 1080 Final thumbnail height in pixels.
thumbnail character_overlay_dir /data/evanescere/assets/ Directory of transparent VTuber PNG variants. One is selected randomly per thumbnail.
thumbnail character_scale 0.42 Character overlay height as a fraction of final thumbnail height.
thumbnail character_position bottom-right Character placement.
thumbnail title_enabled true Draws the clip title onto the final thumbnail.
thumbnail command empty Local image generation command used when provider = "command".
upload adapter noop noop or command.
upload command empty Command invoked when adapter = "command"; receives JSON metadata on stdin.

Frontend runtime config:

File Key Meaning
frontend/public/config.js apiBaseUrl URL the static frontend uses to call the backend. Change this when hosting frontend/backend on different hosts.

CLI

evanescere scan
evanescere bootstrap-existing
evanescere videos
evanescere run-video VIDEO_ID
evanescere transcribe VIDEO_ID
evanescere suggest VIDEO_ID
evanescere render CLIP_ID
evanescere upload CLIP_ID

Inside Compose, prefix commands with:

docker compose run --rm api

Useful API endpoints for generated files:

  • POST /webdav/scan
  • GET /videos/{video_id}/artifacts
  • GET /clips/{clip_id}/artifacts
  • GET /artifacts/{artifact_id}

Thumbnail outputs are visible through the artifact endpoints. Upload command payloads include thumbnail when a thumbnail_final artifact exists.

Custom LLM Prompt

Edit [llm_prompt] in your private config.toml to tune clip selection without rebuilding containers:

[llm_prompt]
system = """
Your custom editorial guidance.
"""

user = """
Choose up to {max_candidates} clips between {min_clip_seconds} and {max_clip_seconds} seconds.
Stream range: {chunk_start_sec}-{chunk_end_sec}
Transcript:
{transcript}
"""

Available user-prompt placeholders:

  • {max_candidates}
  • {min_clip_seconds}
  • {max_clip_seconds}
  • {chunk_start_sec}
  • {chunk_end_sec}
  • {transcript}

The backend appends its strict JSON response schema to the system message so custom prompt wording cannot accidentally remove the machine-readable output contract.

Debugging

Set verbose logs in config.toml:

[app]
log_level = "DEBUG"
log_sql = false

Then inspect service logs:

docker compose logs -f scheduler
docker compose logs -f worker-media
docker compose logs -f worker-ai
docker compose logs -f api

What to look for:

  • Scheduler logs webdav propfind done, new webdav file observed, and webdav file stable.
  • On first startup, scheduler logs webdav baseline missing and stores all currently visible recordings as existing_done.
  • Worker logs pipeline start, prepare media, transcription start, clip suggestion start, render start, and upload start.
  • ffmpeg command failures include the failing command and stderr tail.
  • DeepSeek logs include transcript chunk bounds, character counts, candidate counts, and usage when returned by the SDK.
  • Thumbnail logs include frame extraction, local image generation command execution, VTuber overlay path, and final artifact path.
  • API logs include method, path, status code, and elapsed time for each frontend request.

Enable SQL logs only when diagnosing database behavior:

[app]
log_sql = true

This is intentionally noisy and should usually stay off in production.

Notes

  • FunASR/ROCm setup is intentionally outside the main Compose file for now. Benchmark the Framework host manually before binding the project to a specific GPU runtime.
  • The uploader adapter is deliberately small. [upload].adapter = "command" is enough to integrate a Bilibili uploader later without changing the pipeline core.
  • Existing recordings are automatically baselined on the first scheduler scan. The explicit evanescere bootstrap-existing command remains available when you want to establish or refresh the baseline before starting services.
S
Description
No description provided
Readme
188 KiB
Languages
Python 73.9%
TypeScript 15.8%
CSS 6.3%
Shell 3.2%
Dockerfile 0.5%
Other 0.2%