Skip to content

Latest commit

 

History

History
387 lines (296 loc) · 14.7 KB

File metadata and controls

387 lines (296 loc) · 14.7 KB

Getting started

ReelScope uses uv to manage Python, dependencies, and the project virtual environment.

Start from a clean checkout

The project does not require a manually activated virtual environment. Run:

uv run reelscope --help

On the first invocation, uv will:

  1. Create .venv.
  2. Install the versions resolved in uv.lock.
  3. Install ReelScope into the environment.
  4. Run the requested command.

If .env is missing, ReelScope copies the platform template once:

  • Apple Silicon macOS → examples/env/macos.env
  • Linux → examples/env/linux.env

You can still create it manually:

cp examples/env/linux.env .env   # or macos.env

To prepare the environment without running a command:

uv sync

Optional features

On Linux, install the optional speech runtime separately. Transcription is enabled for every video by default:

uv sync --extra speech
uv run reelscope describe-content video.mp4

For Apple Silicon, ReelScope uses LM Studio for VLM inference and direct MLX for embeddings and Whisper; see macOS setup.

Browser automation is also optional:

uv sync --extra browser
uv run playwright install chromium

Then export an Instagram saved collection:

uv run reelscope export-saved \
  --url "https://www.instagram.com/USERNAME/saved/all-posts/"

The exporter writes newly discovered canonical URLs directly to data/reels/state.sqlite3. Known URLs are never inserted twice. New URLs from the current run are printed to the terminal, and scrolling stops after 10 consecutive posts already present in SQLite; change this with --known-streak.

The managed Chromium installation is unnecessary when passing an existing Chrome or Chromium executable through --browser-executable.

Install both optional feature sets with:

uv sync --extra speech --extra browser

Telegram bot

Install the bot dependencies (and browser support if you want /export_saved):

uv sync --extra telegram --extra browser

Fill the bot token in .env before starting the bot:

TELEGRAM_BOT_TOKEN=123456:ABC...
TELEGRAM_ALLOWED_USER_IDS=

TELEGRAM_ALLOWED_USER_IDS is a comma-separated allowlist of numeric Telegram user IDs. When it is empty, the first user who sends /start claims the bot and is written to .env. Start the bot privately and claim it immediately: anyone who knows the bot before that first /start could otherwise claim it. You may also pre-fill the allowlist manually. To learn a user ID, message @userinfobot or a similar identity bot.

Start the bot:

uv run reelscope bot

On /start the bot checks the default cookies path (INSTAGRAM_COOKIES_FILE, usually data/assets/www.instagram.com_cookies.txt). If cookies are missing, it asks you to send a Netscape cookies.txt file or paste its contents. Export cookies while logged into Instagram with Get cookies.txt LOCALLY. Cookies grant access to your Instagram session — do not share them.

After cookies are saved (and again after every /update_cookies), the bot asks for your Instagram username and writes:

INSTAGRAM_SAVED_URL=https://www.instagram.com/<username>/saved/all-posts/

Available bot commands:

  • /export_saved — export unseen saved posts into SQLite
  • /ingest_content [URL ...] — describe and embed URLs or the SQLite queue
  • /search <query> — hybrid search with persisted weights
  • /random — select top-k distinct registry URLs with secrets.SystemRandom and show each available summary
  • /status — registry counts (total / described / vectorized / errors)
  • /logs — last lines of the rotating bot log
  • /settings — view or edit search weights
  • /update_cookies — replace Instagram cookies
  • /add_user — add another numeric Telegram ID to the allowlist

Search top-k and weights are global for the bot process and persisted under data/telegram/search-settings.json. Bot logs rotate at 10 MiB with one backup under data/telegram/reelscope-bot.log; the same operational messages are also printed to stdout while reelscope bot is running.

The bot lazily warms the embedding and reranking models on the first search and shows Loading search models… while they are cold. It keeps their services and clients alive for later searches. Before /ingest_content, it temporarily releases those search resources so the describe/embed pipeline can use the GPU, then restores the warm search runtime. Stopping the bot releases it permanently. The standalone reelscope search CLI keeps its original start-per-command and stop-after-command lifecycle.

When search models are warm, any free text sent outside another dialog is treated as a search query. While models are cold, the bot asks for confirmation with /search and /cancel inline buttons before loading them.

Describe content

The unified command accepts:

  • one video;
  • a directory containing videos;
  • an ordered image directory;
  • a directory containing ordered image directories;
  • an Instagram Reel or post URL;
  • a TXT file containing URLs or paths, one per line.
uv run reelscope describe-content video.mp4
uv run reelscope describe-content ./videos
uv run reelscope describe-content ./carousel
uv run reelscope describe-content ./carousels
uv run reelscope describe-content "https://www.instagram.com/p/POST_ID/"
uv run reelscope describe-content ./inputs.txt

Whisper transcription runs for video items by default. Set WHISPER_ENABLED=false in .env to disable it by default, or pass --no-transcribe to skip it for one run; the CLI flag has priority. Whisper detects the spoken language automatically when WHISPER_LANGUAGE is empty. The Linux default cuda/float16 configuration requires the vLLM container to leave enough GPU memory available; set the language explicitly only when you want faster or more predictable recognition. The transcript is inserted into the Qwen prompt as additional evidence and saved separately in each video item's transcription object.

Instagram media is downloaded into a temporary directory and removed after the run. Successful Instagram result payloads are serialized without indentation and stored directly in SQLite together with a SHA-256 checksum. Use --output when an explicit JSON export is needed. Local inputs and --no-state keep the file-based result behavior.

Each successfully resolved item also has a path-independent content_id derived from a streaming SHA-256 hash of its bytes. Ordered image sets and multi-item inputs receive aggregate IDs that preserve item order. This allows identical content from different paths or URLs to be detected without replacing the source-based result key.

ingest-content runs the full registry pipeline on Linux and macOS:

  1. describe the requested sources (same behavior as describe-content);
  2. release the video/image inference backend to free GPU memory;
  3. embed every described registry row whose embedding_status is pending or error, including backlog from earlier runs.
uv run reelscope ingest-content ./inputs.txt

Local-only inputs use a separate data/local_dirs.sqlite3 registry. A folder of videos creates one described and embedded row per video path. Override this location with REELSCOPE_LOCAL_STORAGE_FILE or --state-database. --no-state still stops after describe; use describe-content when you only need descriptions or a JSON export.

Instagram inputs run in batches of 25 with a 60-second pause between batches. Override these safeguards with --batch-size and --batch-pause. Three consecutive redirects to the Instagram home page pause processing for one hour, reset the redirect counter, and then resume. --instagram-redirect-limit and --instagram-redirect-pause change the threshold and pause duration.

Without positional sources, both commands process SQLite rows whose processing_status is pending or error:

uv run reelscope ingest-content

The two independent lifecycle fields are:

  • processing_status: pending, error, or described;
  • embedding_status: not_ready, pending, error, or embedded.

Inspect state or print matching URLs:

uv run reelscope state status
uv run reelscope state list --processing-status error

To restart the entire pipeline in a fresh database while keeping only the discovered Instagram links, copy them from the old database:

uv run reelscope migrate-links \
  ./data/reels/state.sqlite3 \
  ./data/reels/final_state.sqlite3

The destination database is created with the latest schema. Only canonical post URLs are copied; descriptions, errors, embeddings, and processing state are not transferred. Every copied URL starts as pending / not_ready.

Embed and search

On Linux, ingest-content, reelscope embed, and vector/rerank search stages start the required vLLM services automatically and stop them afterward. You can still manage Docker manually when REELSCOPE_SKIP_VLM_BOOTSTRAP=1 is set.

Manual startup remains useful for debugging:

cp infra/vllm/.env.example infra/vllm/.env
docker compose -f infra/vllm/docker-compose.yml up -d vllm-embed vllm-rerank

On macOS both models run in-process through MLX instead; no server is needed and the weights are downloaded on first use.

Describe output is always structured JSON. Existing summary-only descriptions remain embeddable, but category, mood, topics, actions, objects, visible text, captions, and transcripts are present only when their source pipeline produced them.

Generate indexes for every described URL whose embedding_status is pending or error:

uv run reelscope embed
uv run reelscope embed --limit 100 --batch-size 16
uv run reelscope embed --url "https://www.instagram.com/reel/POST_ID/"

The vector document contains only VLM description fields. FTS5 stores transcript, caption, hashtags, and visible text separately. Hashtags are extracted from captions, normalized without #, and ranked with their own BM25 weight.

Search accepts Russian or English queries. Qwen adds the retrieval instruction only to query embeddings; documents are embedded without an instruction:

uv run reelscope search "женщина готовит кофе"
uv run reelscope search "morning coffee tutorial" --top-k 5
uv run reelscope search "coffee" --lexical-only --json
uv run reelscope search "coffee" --mode embedder
uv run reelscope search "coffee" --embedder-weight 1.5 --lexical-weight 0.7
uv run reelscope search "coffee" --bm25-caption-weight 1.2 --bm25-hashtags-weight 0.8
uv run reelscope search "coffee" --output results/search.json
uv run reelscope search "coffee" --no-rerank
uv run reelscope search "coffee" --rerank-threshold 0.5 --rerank-pool-k 40

Hybrid search retrieves vector and FTS5 candidates independently and combines their ranks with reciprocal rank fusion. Select --mode hybrid, --mode lexical, or --mode embedder; --lexical-only, --embedder-only, and the legacy --vector-only alias are available for convenience. Use --rrf-k, --embedder-weight, and --lexical-weight to tune fusion for one request. Every FTS5 field can be tuned independently with the --bm25-*-weight options. JSON output is a versioned object containing the effective request parameters and the hits array. result_checksum is stored as diagnostic metadata; no index cleanup, backfill, or search filtering is tied to it.

Reranking is enabled by default and adds a second stage on top of fusion: the fused ranking only assembles a candidate pool of --rerank-pool-k URLs, then Qwen/Qwen3-Reranker-0.6B reads each query/description pair together and rescores it with the probability that the description matches the query. The final order, the reported score, and the rerank_score field all come from the reranker. Because scores are absolute probabilities rather than ranks, --rerank-threshold can drop weak hits entirely — a search with no real matches returns fewer than top_k results instead of noise. Disable the stage for one request with --no-rerank or persistently with REELSCOPE_RERANK_ENABLED=false. The rerank judging instruction is configurable through VLLM_RERANK_INSTRUCTION.

See SQLite query cookbook for read-only sqlite3 examples: schema inspection, lifecycle counts, unique categories/topics, weighted FTS5 queries, vector metadata, and CSV export.

Both commands display overall and per-item progress bars. Disable them for CI or redirected logs with --no-progress.

The default response budget is 1,024 tokens and can be overridden with --max-tokens. The pipeline keeps at most two local sources in a bounded prefetch window. Instagram preparation is serialized, and gallery-dl jobs run through its Python API in the application process instead of spawning a new process for every URL.

Every result includes download_seconds, hash_seconds, preprocess_seconds, inference_seconds, and total_seconds. SQLite-backed Instagram results are committed one at a time. Explicit file exports use the existing .partial.jsonl recovery journal and atomic final JSON publication.

Video items also include normalized source metadata when an adjacent .json, .mp4.json, or .info.json sidecar is available. Instagram downloads request metadata sidecars from gallery-dl automatically. The result keeps useful identifiers, captions, publication time, uploader, engagement counts, and duration while excluding transport headers and other sensitive fields.

An individual download, hash, transcription, or model failure does not stop the remaining inputs. Instagram processing failures update processing_status and processing_error; diagnostic details are also appended to results/describe/error.log.

Why use uv run

uv run uses the project interpreter and dependencies from .venv. This avoids accidentally running a system Python with missing or incompatible packages.

The following commands are equivalent after the environment is synchronized:

uv run reelscope --help
.venv/bin/python -m reelscope --help

The first form is preferred because it also verifies that the environment matches pyproject.toml and uv.lock.

Dependency files

  • pyproject.toml declares supported Python versions, direct dependencies, optional features, and the reelscope command.
  • uv.lock records exact resolved dependency versions for reproducible setup.
  • .venv contains the local environment and can be deleted and recreated at any time.

Do not edit uv.lock manually. Update dependencies through uv and commit the resulting lockfile.

Verification

After recreating the environment, run:

uv run ruff format --check .
uv run ruff check .
uv run pytest
uv run reelscope --help