26 KiB
Video Library Manager
Documentation Status
- Last synchronized: 2026-04-02
- Human-in-the-loop workflow includes
vlm apply-reviewfor syncing manual plan edits from CSV back intoplan.json. - Scanner now detects
ffprobeavailability and degrades gracefully. - JSON artifacts (
identities.json,analysis.json,plan.json) are schema-validated on load/save. - Plan generation separates logical intent from environment-derived validation snapshots, improving reproducibility.
A Python-based CLI tool for managing personal video collections with a safety-first, human-in-the-loop approach.
Features
- Safety-First Design: All file operations are reversible with rollback support
- Human-in-the-Loop: Explicit confirmation required before making any changes. New: Full review-apply cycle for manual plan adjustments via CSV.
- Comprehensive Analysis: Detect episode gaps and duplicate files
- Rich Metadata: Extract video resolution, codec, duration, and bitrate (with graceful fallback if
ffprobeis missing) - Flexible Organization: Customizable directory structure and naming templates
- Metadata Enrichment: Add bilingual titles and reputation signals (TMDB + optional AI fallback)
- Incremental Performance: SQLite-backed cache avoids repeated metadata lookups
- State Tracking: Track file status throughout the workflow
- Detailed Reporting: Generate inventory, completeness, and duplicate reports
- Plan–Analysis Integration:
vlm plan --analysisapplies duplicate resolution and adds a Chinese human summary - Artifact Validation: JSON artifacts are validated early to catch malformed inputs before later stages run
- Deterministic Planning: Plan generation records live filesystem checks as validation snapshots instead of mixing them into core plan facts
Installation
This project uses uv for Python package management. To install:
# Install dependencies
uv pip install -e .
# Install with development dependencies
uv pip install -e ".[dev]"
Quick Start
1. Initialize Configuration
First, create a configuration file:
vlm config init
This creates ~/.vlm/config.yaml. Edit it to set your library root:
library_root: "/mnt/Downloads" # Change this to your video library path
workspace_dir: "artifacts" # Default workspace for generated files
2. Scan Your Library
Discover all video files in your library:
vlm scan
This creates artifacts/inventory.csv with all discovered files and their metadata.
What happens:
- Discovers video files using the system
findcommand - Extracts file metadata (size, modification time)
- Categorizes files based on directory structure (movie/series/anime/other)
- Extracts video metadata using ffprobe (if available)
- Saves results to
artifacts/inventory.csv
3. Parse Filenames
Extract titles, years, seasons, and episodes from filenames:
vlm parse
For accurate duplicate resolution by quality, embed video metadata:
vlm parse --inventory artifacts/inventory.csv
This creates artifacts/identities.json with parsed information. When using --inventory, video metadata (size, resolution, codec) is embedded, enabling accurate quality comparison during duplicate analysis.
What it extracts:
- Movies: Title and year (e.g., "Inception (2010)")
- Series: Title, season, and episode numbers (e.g., "Breaking Bad S01E01")
- Confidence scores: Indicates parsing reliability
- Video metadata (with
--inventory): Size, resolution, codec, duration, bitrate
4. Enrich Titles and Reputation (Optional but Recommended)
Add translation and reputation metadata to artifacts/identities.json:
vlm enrich
This updates artifacts/identities.json in place and adds fields like:
title_zh,title_en,display_titlereputation_score,reputation_votes,reputation_sourcereview_status,enrichment_confidence- summary metrics including
api_calls,cache_hits, andskip_reasons
To refresh all records instead of using incremental cache:
vlm enrich --refresh-all
If enrichment cannot run for some records, CLI shows grouped reasons, for example:
Skip reasons: no_key=4632
5. Analyze Your Library
Detect episode gaps and duplicates:
vlm analyze
This creates artifacts/analysis.json with:
- Series with missing episodes
- Duplicate files with quality comparison
6. Generate Execution Plan
Create a reviewable plan of file operations:
vlm plan
To let the plan automatically resolve duplicate groups (keep one file per group by reputation, quarantine the rest), pass the analysis file:
vlm plan --analysis artifacts/analysis.json
This creates artifacts/plan.json with:
- Proposed operations (move, rename, quarantine, no-op)
- Summary: counts by operation type and by reason
- Human summary (中文): short narrative for quick review
- Metadata: when using
--analysis, duplicate groups considered and completeness gaps
Safety guards in planning:
- Titles used in path templates are sanitized (path separators/control chars/
..stripped) - Any destination outside
library_rootis rejected asno-op
Duplicate keep strategy is configurable in ~/.vlm/config.yaml under plan.duplicate_keep:
by_quality- Prefer highest quality (resolution > source > codec > file size). Best for automatic duplicate resolution.by_reputation- Prefer external rating (TMDB); when rating ties/missing, fallback to quality (source-first: BluRay > WEB-DL), then first-seen. Default.by_reputation_quality_time- Prefer external rating first, then quality, then newer modified time.first_seen- Keep the first file in each duplicate group.manual- Do not generate quarantine operations; duplicates are listed in analysis only.
Review the plan in one of three ways:
- Open
artifacts/plan.jsonin your editor - Run
vlm review-planto get a terminal preview (summary + high-risk operation preview) - Run
vlm executeto see the same plan summary in dry-run mode
You can still edit plan.json directly when needed.
7. Execute (Dry-Run First)
Preview what will happen without making changes:
vlm execute
Before running, the CLI prints the plan’s human summary (or a short summary from counts) so you can confirm at a glance. When ready to actually move/rename/quarantine files:
vlm execute --confirm
Important: This creates a rollback log in ~/.vlm/rollback/ for reverting changes.
Execution safeguards:
- Even if a manually edited
artifacts/plan.jsoncontains an unsafe destination, execution rejects paths outsidelibrary_root - Summary counters treat conflict skips separately from real failures (
failed/skippedare mutually exclusive)
8. Rollback (If Needed)
If you need to undo the operations:
vlm rollback
This uses the most recent rollback log to restore files to their original locations.
Complete Workflow Example
Here's a complete workflow from start to finish:
# 1. Initialize configuration
vlm config init
# Edit ~/.vlm/config.yaml to set your library_root
# 2. Scan your library
vlm scan
# Output: artifacts/inventory.csv with 1234 files discovered
# 3. Parse filenames with metadata embedding (recommended for duplicate resolution)
vlm parse --inventory artifacts/inventory.csv
# Output: artifacts/identities.json with parsed titles, episodes, and embedded video metadata (v2 schema)
# 4. Enrich identities (translation + reputation)
vlm enrich
# Output: artifacts/identities.json updated in place (incremental cache enabled)
# 5. Analyze for gaps and duplicates
vlm analyze
# Output: artifacts/analysis.json with 5 series with gaps, 12 duplicate groups (accurate quality comparison)
# 6. Generate execution plan (optionally use analysis for duplicate handling)
vlm plan --analysis artifacts/analysis.json
# Output: artifacts/plan.json with operations, human summary, and duplicate quarantine decisions
# 7. Review the plan in terminal (summary + high-risk preview)
vlm review-plan
# Optional: control preview size
vlm review-plan --preview-limit 20
# Optional: show every high-risk operation in terminal
vlm review-plan --show-all
# Edit artifacts/plan_manual_review.csv in Excel/Numbers
# Sync your manual decisions back to artifacts/plan.json
vlm apply-review
# Output: Successfully updated plan saved to: artifacts/plan.json
# 8. Dry-run to preview
vlm execute
# Shows what would happen without making changes (respecting your manual edits)
# 9. Execute with confirmation
vlm execute --confirm
# Actually performs the file operations
# 10. If needed, rollback
vlm rollback
Command Reference
Configuration
# Initialize config file
vlm config init
# Show current configuration
vlm config show
# Validate configuration
vlm config validate
Scanning
# Scan with default output (artifacts/inventory.csv)
vlm scan
# Scan with custom output file
vlm scan --output my_library.csv
Scan behavior:
- Hidden paths are skipped (any component starting with
.) - File matching uses configured
video_extensions(default:.mp4,.mkv,.avi,.mov,.wmv,.flv,.webm,.m4v) - If
ffprobeis available, resolution/codec/duration/bitrate are included - ffprobe extraction uses controlled concurrency (
enrichment.max_concurrency) - Timestamps stored in UTC format (
YYYY-MM-DDTHH:MM:SS)
Parsing
# Parse with default files (v1 schema - no metadata embedding)
vlm parse
# Parse with metadata embedding (v2 schema - enables quality comparison)
vlm parse --inventory artifacts/inventory.csv
# Parse with custom input/output
vlm parse --input my_inventory.csv --output my_identities.json
# Parse with metadata from custom inventory
vlm parse --input my_inventory.csv --output my_identities.json --inventory my_inventory.csv
Schema Versions:
- v1 (without
--inventory): Lightweight identities, suitable for basic organization - v2 (with
--inventory): Embeds video metadata, required for accurate duplicate resolution by quality
Enrichment
# Enrich identities in place (default: artifacts/identities.json)
vlm enrich
# Enrich custom file and write to another file
vlm enrich --input my_identities.json --output enriched_identities.json
# Refresh changed records only (explicit incremental mode)
vlm enrich --refresh-changed-only
# Force full refresh (ignore cache for all records)
vlm enrich --refresh-all
# Tune request behavior
vlm enrich --timeout 6 --retries 2
Analysis
# Analyze with default files
vlm analyze
# Analyze with custom files
vlm analyze --input my_identities.json --output my_analysis.json
Planning
# Generate plan with default files
vlm plan
# Use analysis so duplicate groups become "keep one + quarantine rest" (by_reputation by default)
vlm plan --analysis artifacts/analysis.json
# Custom input/output
vlm plan --input my_identities.json --output my_plan.json
vlm plan --input my_identities.json --analysis my_analysis.json --output my_plan.json
Manual Plan Review
# Export high-risk operations and preview them in terminal
vlm review-plan
# Preview first N high-risk operations in terminal (default: 10)
vlm review-plan --preview-limit 20
# Show all high-risk operations in terminal preview
vlm review-plan --show-all
# Apply edited CSV decisions back to plan.json
vlm apply-review
Execution
# Dry-run (preview only, no changes)
vlm execute
# Execute with confirmation (actually makes changes)
vlm execute --confirm
# Use custom plan file
vlm execute --plan my_plan.json --confirm
Rollback
# Rollback using most recent log
vlm rollback
# Rollback using specific log
vlm rollback --log ~/.vlm/rollback/rollback_<uuid>.json
Quarantine Management
# List quarantined files
vlm quarantine list
# List by category
vlm quarantine list --category movie
# Quarantine a file
vlm quarantine add /path/to/file.mkv --reason "duplicate"
# Restore from quarantine
vlm quarantine restore /path/to/.quarantine/file.mkv
Reporting
# Generate inventory report (text format)
vlm report inventory
# Generate in different formats
vlm report inventory --format csv
vlm report inventory --format json --output inventory_report.json
# Generate completeness report (series with gaps)
vlm report completeness
# Include plan content summary in the report (human_summary from artifacts/plan.json)
vlm report completeness --plan artifacts/plan.json
# Generate duplicates report
vlm report duplicates
vlm report duplicates --plan artifacts/plan.json
# Generate summary statistics
vlm report summary
State Management
# Show state for a file
vlm state show /path/to/file.mkv
# Set state for a file
vlm state set /path/to/file.mkv --status reviewed
vlm state set /path/to/file.mkv --status ignored --reason "duplicate"
# Query files by status
vlm state query --status ignored
# Clear state for a file
vlm state clear /path/to/file.mkv
Configuration File
The configuration file (~/.vlm/config.yaml) controls VLM's behavior:
# Required: Root directory of your video library
library_root: "/mnt/nas/videos"
# Workspace directory for generated artifacts
workspace_dir: "artifacts"
# Video file extensions to recognize
video_extensions:
- .mp4
- .mkv
- .avi
- .mov
- .wmv
- .flv
- .webm
- .m4v
# Directory structure templates
templates:
movie_dir: "movie/{title} ({year})/"
series_dir: "series/{title}/Season {season:02d}/"
movie_filename: "{title} ({year}){ext}"
series_filename: "S{season:02d}E{episode:02d}{ext}"
# Quarantine directory name (relative to category root)
quarantine_dir: ".quarantine"
# Logging level (DEBUG, INFO, WARNING, ERROR)
log_level: "INFO"
# Plan behavior (e.g. when using vlm plan --analysis)
plan:
# Duplicate keep strategy: "by_quality", "by_reputation" (default),
# "by_reputation_quality_time", "first_seen", or "manual"
# by_quality: resolution > source (BluRay > WEB-DL) > codec (x265 > x264) > file size
duplicate_keep: "by_reputation"
# Category mappings (directory name to category)
categories:
movie: [movie, movies, films]
series: [series, tv, shows]
anime: [anime]
# Enrichment settings (`enrich` alias is also supported)
enrichment:
enabled: true
incremental: true
refresh_mode: "manual"
providers: [tmdb]
cache_db: "~/.vlm/enrichment_cache.db"
max_concurrency: 6 # used by enrichment requests and scan-time ffprobe workers
min_match_score: 0.75
translation:
mode: "bidirectional"
fallback_machine: true
api_keys:
# Preferred: TMDB v4 Bearer token
tmdb_bearer: null
# Backward-compatible fallback (legacy query api_key)
tmdb: null
openai: null
tmdb:
language: "zh-CN"
region: null
include_adult: false
reputation:
min_votes: 50
low_score_threshold: 6.0
policy: "flag_for_review"
naming:
title_format: "{title_zh} {title_en}"
TMDB Enrichment Setup
vlm enrich works best with TMDB Bearer auth (recommended by TMDB). Legacy tmdb api key is still supported for compatibility.
Minimal config:
enrichment:
providers: [tmdb]
api_keys:
tmdb_bearer: "YOUR_TMDB_BEARER_TOKEN"
Optional TMDB query tuning:
enrichment:
tmdb:
language: "zh-CN" # localized title language
region: "US" # affects regional release/search behavior
include_adult: false
Validation flow:
# 1) run small incremental pass
vlm enrich --input artifacts/identities.json --refresh-changed-only
# 2) then full refresh if output looks correct
vlm enrich --input artifacts/identities.json --refresh-all
Template Variables
Movies:
{title}- Movie title{year}- Release year{ext}- File extension
Series:
{title}- Series title{season}- Season number{episode}- Episode number{ext}- File extension
Format specifiers are supported (e.g., {season:02d} for zero-padded numbers).
File Categorization
VLM categorizes files based on the top-level directory in your library, matched against configured category mappings. By default:
movie/→ "movie"series/→ "series"anime/→ "anime"- Anything else → "other"
You can customize these mappings to support multiple directory names per category:
categories:
movie: [movie, movies, films]
series: [series, tv, shows, television]
anime: [anime]
This allows directories like /library/movies/ or /library/tv/ to be correctly categorized. Directory matching is case-insensitive.
Example:
/mnt/Downloads/
├── movies/ # Category: movie (plural form)
│ └── Inception (2010).mkv
├── tv/ # Category: series (tv variant)
│ └── Breaking Bad S01E01.mkv
├── anime/
│ └── Attack on Titan E01.mkv # Category: anime
└── documentaries/
└── Planet Earth.mkv # Category: other
Migration Note: If you have existing directories with non-standard names (like "movies" or "tv"), update your categories configuration in config.yaml and re-run vlm scan to fix categorization. No files will be moved.
Common Scenarios
Scenario 1: First Time Setup
# 1. Install
uv pip install -e .
# 2. Initialize config
vlm config init
# Edit ~/.vlm/config.yaml
# 3. Scan library
vlm scan
# 4. Check what was found
vlm report summary
Scenario 2: Finding Missing Episodes
# 1. Scan and parse
vlm scan
vlm parse --inventory artifacts/inventory.csv
vlm enrich
# 2. Analyze completeness
vlm analyze
# 3. View report (optionally include plan summary if you have a plan)
vlm report completeness
vlm report completeness --plan artifacts/plan.json
Scenario 3: Finding and Removing Duplicates
# 1. Scan and parse (with --inventory for accurate quality comparison)
vlm scan
vlm parse --inventory artifacts/inventory.csv
vlm enrich
# 2. Analyze for duplicates
vlm analyze
# 3. View duplicates with quality comparison (now shows actual resolution/codec/size)
vlm report duplicates
# 4. Generate plan with analysis: VLM keeps one file per duplicate group (by reputation) and quarantines the rest
vlm plan --analysis artifacts/analysis.json
# 5. Review plan (human summary in artifacts/plan.json and when you run execute)
vlm execute
vlm execute --confirm
# Alternatively: manual quarantine without plan
vlm quarantine add /path/to/lower/quality/file.mkv --reason "duplicate - lower quality"
# Report with plan context
vlm report duplicates --plan artifacts/plan.json
Scenario 4: Reorganizing Your Library
# 1. Customize templates in config
# Edit ~/.vlm/config.yaml to set your preferred structure
# 2. Scan and parse
vlm scan
vlm parse --inventory artifacts/inventory.csv
vlm enrich
# 3. Generate plan
vlm plan
# 4. Review artifacts/plan.json carefully
# 5. Dry-run to preview
vlm execute
# 6. Execute when satisfied
vlm execute --confirm
# 7. If something goes wrong, rollback
vlm rollback
Logging
VLM logs to ~/.vlm/vlm.log by default. If the log directory is unwritable, it falls back to console logging and continues running.
Troubleshooting
ffprobe Not Found
If you see warnings about ffprobe:
# Install ffmpeg (includes ffprobe)
# Ubuntu/Debian
sudo apt install ffmpeg
# macOS
brew install ffmpeg
# Windows
# Download from https://ffmpeg.org/download.html
Without ffprobe, VLM still works but won't extract video metadata (resolution, codec, etc.).
Configuration Errors
If VLM can't find your config:
# Check config location
ls -la ~/.vlm/config.yaml
# Reinitialize if needed
vlm config init
# Validate current config
vlm config validate
TMDB Auth / Rate Limit / Zero Enriched
Common enrichment outcomes:
-
Error during enrichment: TMDB authentication failed (401/403)
Cause: invalid/missingtmdb_bearer(ortmdb) key.
Action: update~/.vlm/config.yamland rerun. -
Skip reasons: no_key=...
Cause: no TMDB credentials configured for provider.
Action: setenrichment.api_keys.tmdb_bearer(recommended) ortmdb. -
Skip reasons: rate_limited=...
Cause: TMDB rate limit hit (429).
Action: retry later; VLM already applies bounded retry/backoff. -
Enriched now: 0with non-zero records
Cause: oftenno_key,no_match, or provider errors.
Action: checkSkip reasonsandFailure samplein CLI output.
Permission Errors
If you can't access certain files:
# Check permissions
ls -la /path/to/library
# VLM logs errors but continues scanning other files
# Check log file for details
cat ~/.vlm/vlm.log
Rollback Fails
Rollback is best-effort and may fail if:
- Files have been moved/deleted since execution
- Destination paths are occupied
- Permissions have changed
Always keep backups of important files!
Safety Features
- No Permanent Deletion: VLM never deletes files. Use quarantine instead.
- Dry-Run Default: Execute command defaults to dry-run mode
- Explicit Confirmation:
--confirmflag required to make actual changes - Rollback Logs: All operations logged for reversal
- Conflict Detection: Plan generation detects destination conflicts
- Best-Effort Rollback: Attempt to restore files to original locations
Limitations
Version 1.0
- Anime parsing not implemented: Anime files are discovered and categorized but not parsed
- Movie/Series only for quarantine: Only movie and series categories can be quarantined
- Hardcoded parsing patterns: Filename patterns are not user-configurable
- Local files only: Designed for local or mounted network storage
- Best-effort rollback: Rollback may not succeed if files have been modified
Gemini CLI Agent Skills
This project includes built-in expert guidance for the Gemini CLI. These skills co-locate project knowledge with the source code.
Activation
To activate the expert guidance in Gemini CLI, use:
# General expert guidance and knowledge base
activate_skill vlm-expert
# Step-by-step library organization workflow
activate_skill vlm-library-workflow
Features
- Context-Aware Guidance: The Agent understands the VLM safety-first workflow and configuration.
- Risk Warnings: Automatic alerts if a plan contains high-risk operations (>20% quarantine).
- Command Recipes: Instant access to complex command sequences and troubleshooting steps.
- Developer Verification: Built-in test execution recipes for verifying core logic changes.
Development
Running Tests
# Run all tests
pytest
# Run specific test file
pytest tests/test_scanner.py
# Run with verbose output
pytest -v
# Run with coverage
pytest --cov=vlm tests/
Code Style
- Python 3.10+ idioms
- PEP 8 naming conventions
- Type hints for public functions
- 4-space indentation
Project Structure
src/vlm/
├── cli.py # Click-based CLI interface, global options
├── context.py # CLIContext and pass_context for commands
├── commands/ # Command implementations
│ ├── scan.py # Scan command
│ ├── parse.py # Parse command
│ ├── enrich.py # Enrich command
│ ├── analyze.py # Analyze command
│ ├── plan.py # Plan command
│ └── execute.py # Execute/Rollback commands
├── scanner.py # File discovery and metadata extraction
├── parser.py # Filename parsing (titles, years, episodes)
├── enrichment.py # Title/reputation enrichment pipeline
├── cache.py # SQLite cache for incremental enrichment
├── providers/ # External metadata providers (TMDB, etc.)
│ ├── base.py # Provider interface
│ └── tmdb.py # TMDB API client
├── io.py # Unified JSON/CSV I/O helpers (load/save JSON, analysis writer, plan/analysis adapters)
├── utils.py # UTC time, format_size, etc.
├── analysis.py # Completeness and duplicate detection
├── duplicate_resolve.py # Duplicate group keep-index (by_quality, by_reputation, by_reputation_quality_time, first_seen, manual)
├── planner.py # Execution plan generation (optionally consumes analysis)
├── executor.py # File operations and rollback
├── quarantine.py # Quarantine management
├── state.py # File state tracking
├── reports.py # Report generation
├── config.py # Configuration management
├── models.py # Data structures
└── logging_config.py # Logging setup
tests/
├── test_scanner.py # Scanner tests
├── test_parser.py # Parser tests
├── test_enrichment.py # Enrichment tests
├── test_analysis.py # Analysis tests
├── test_planner.py # Planner tests
└── ... # More test files
Requirements
- Python >= 3.10
- uv (Python package manager)
- ffmpeg (optional, for video metadata extraction)
- find command (standard on Unix-like systems)
Contributing
Contributions are welcome! Please:
- Write tests for new features
- Follow existing code style
- Update documentation
- Add clear commit messages
License
[Add your license here]
Acknowledgments
Built with:
- Click - CLI framework
- PyYAML - Configuration parsing
- pytest - Testing framework
- hypothesis - Property-based testing
Support
- Report issues: GitHub Issues
- Documentation: See CLAUDE.md for architecture details
- Logs: Check
~/.vlm/vlm.logfor detailed information