PDF to Markdown preprocessing pipeline for ski/snowboard instruction manuals. Converts PDF books into enriched Markdown with intelligent image filtering and VLM-powered descriptions.
bookshelf/*.pdf
│
▼
┌──────────────────────────────────────────┐
│ Stage 1 — MinerU PDF Extraction │
│ (VLM-transformer on GPU / Pipeline CPU) │
└──────────────┬───────────────────────────┘
▼
┌──────────────────────────────────────────┐
│ Stage 2 — Size-Based Image Filtering │
│ (min 10 KB, 100 px, aspect ratio < 10) │
└──────────────┬───────────────────────────┘
▼
┌──────────────────────────────────────────┐
│ Stage 3 — VLM Semantic Filtering │
│ (Qwen VL: MEANINGFUL / NOT_MEANINGFUL) │
└──────────────┬───────────────────────────┘
▼
┌──────────────────────────────────────────┐
│ Stage 4 — VLM Description + Assembly │
│ (2-4 sentence image descriptions, │
│ YAML frontmatter, final Markdown) │
└──────────────┬───────────────────────────┘
▼
notes/{pdf_stem}/
├── content.md
└── images/
pip install -e .Copy the example env file and fill in your VLM API key:
cp .env.example .env
# Edit .env with your credentials# Process a single PDF (auto-detects backend: GPU → VLM, CPU → Pipeline)
avicenna process --pdf bookshelf/CASI_GUIDE/simple_en.pdf
# Explicit backend selection
avicenna process --pdf bookshelf/CASI_GUIDE/simple_en.pdf --backend pipeline
# Process all PDFs in bookshelf/
avicenna process
# Force re-process (ignore cached results)
avicenna process --pdf bookshelf/CASI_GUIDE/simple_en.pdf --forceavicenna process [OPTIONS]
--pdf PATH Single PDF to process (relative or absolute)
--backend BACKEND MinerU backend: auto | vlm-transformers | pipeline
(default: auto — VLM if GPU detected, else Pipeline)
--force Force regeneration even if output already exists
--bookshelf DIR Override bookshelf directory
--output DIR Override output directory
avicenna/
├── avicenna/
│ ├── __init__.py # Package init, version
│ ├── __main__.py # python -m avicenna.cli entry
│ ├── cli.py # Argument parsing, command dispatch
│ ├── config.py # Dataclass configs, .env loading
│ ├── pipeline.py # 4-stage orchestration
│ ├── extractor.py # MinerU PDF extraction (VLM / Pipeline)
│ ├── filter.py # Size + VLM semantic image filtering
│ ├── describer.py # VLM image description generation
│ ├── vlm_client.py # Qwen VL API wrapper (LangChain)
│ └── processor.py # Markdown post-processing utilities
├── prompts/
│ ├── filter_image.txt # Image classification prompt
│ └── describe_image.txt # Image description prompt
├── docs/
│ ├── CHANGELOG.md # 工作记录
│ ├── ROADMAP.md # 开发计划
│ └── setup/ # 环境配置指南
│ ├── miniconda.md # Miniconda 安装 & conda 管理
│ └── opencode.md # OpenCode AI 编程助手
├── bookshelf/ # Input PDFs (git-ignored)
├── notes/ # Output Markdown + images (git-ignored)
├── .env # VLM API credentials (git-ignored)
├── .env.example # Env template
├── requirements.txt # pip dependencies
└── pyproject.toml
| Backend | Requires | Speed | Quality |
|---|---|---|---|
vlm-transformers |
CUDA GPU + torch | Fast | Higher (VLM-based layout understanding) |
pipeline |
CPU only | Slower | Good (traditional CV + OCR pipeline) |
auto (default) |
Either | — | Picks vlm-transformers if GPU available, else pipeline |
Each processed PDF produces notes/{org}/{pdf_stem}/content.md:
---
name: LEVEL_2
source_pdf: LEVEL_2.pdf
organization: CASI
language: en
tags:
- ski-instruction
- certification
---
# Chapter Title
Body text extracted from PDF...

> **[Image: img_001.jpg]**
> A skier demonstrates a basic wedge turn on a gentle green slope,
> with arms forward and knees bent into a snowplow position.- mineru >= 2.7 — PDF extraction engine
- langchain-openai >= 1.1 — OpenAI-compatible VLM API client
- langchain-core >= 1.2 — LangChain messaging primitives
- python-dotenv >= 1.0 — Environment variable loading
- loguru >= 0.7 — Structured logging
- Pillow >= 10.0 — Image processing
MIT