Stop tracking personal workflow artifacts at repo root, add CI and MIT license, align README and agent skills with artifacts/ defaults, and enable Ruff in dev/CI so releases are verifiable without local-only runs. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
97 lines
3.3 KiB
Python
97 lines
3.3 KiB
Python
"""Scan command implementation."""
|
|
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
import click
|
|
|
|
from vlm.context import CLIContext
|
|
from vlm.scanner import load_inventory_csv, save_inventory_csv, scan_library
|
|
from vlm.utils import format_size
|
|
|
|
|
|
def scan_cmd(
|
|
ctx: CLIContext,
|
|
output: Path,
|
|
metadata: bool,
|
|
reuse_from: Optional[Path],
|
|
force_refresh_metadata: bool,
|
|
) -> None:
|
|
"""Run scan: discover video files and save inventory."""
|
|
config = ctx.config
|
|
logger = ctx.logger
|
|
|
|
click.echo(f"Scanning library at: {config.library_root}")
|
|
click.echo("This may take a while for large libraries...")
|
|
click.echo()
|
|
|
|
progress_state: dict[str, Optional[click.ProgressBar]] = {"bar": None}
|
|
progress_position = {"current": 0}
|
|
|
|
def _scan_progress(processed: int, total: int) -> None:
|
|
if total <= 0:
|
|
return
|
|
if progress_state["bar"] is None:
|
|
bar = click.progressbar(
|
|
length=total,
|
|
label="Scanning files",
|
|
show_pos=True,
|
|
)
|
|
progress_state["bar"] = bar.__enter__()
|
|
step = processed - progress_position["current"]
|
|
if step > 0 and progress_state["bar"] is not None:
|
|
progress_state["bar"].update(step)
|
|
progress_position["current"] = processed
|
|
|
|
metadata_cache = None
|
|
cache_source = None
|
|
if not force_refresh_metadata:
|
|
cache_source = reuse_from if reuse_from is not None else (output if output.exists() else None)
|
|
|
|
if metadata and force_refresh_metadata:
|
|
click.echo("Forcing metadata refresh for all files (cache disabled).")
|
|
click.echo()
|
|
|
|
if metadata and cache_source is not None:
|
|
click.echo(f"Loading metadata cache from: {cache_source}")
|
|
try:
|
|
cached_files = load_inventory_csv(cache_source)
|
|
metadata_cache = {str(vf.path): vf for vf in cached_files}
|
|
click.echo(f"Loaded metadata cache entries: {len(metadata_cache)}")
|
|
click.echo()
|
|
except Exception as e:
|
|
click.echo(f"Warning: could not load metadata cache: {e}")
|
|
click.echo("Continuing without cache.")
|
|
click.echo()
|
|
|
|
try:
|
|
video_files = scan_library(
|
|
config.library_root,
|
|
config,
|
|
progress_callback=_scan_progress,
|
|
include_video_metadata=metadata,
|
|
metadata_cache=metadata_cache,
|
|
)
|
|
finally:
|
|
if progress_state["bar"] is not None:
|
|
progress_state["bar"].__exit__(None, None, None)
|
|
click.echo()
|
|
|
|
click.echo("Scan complete!")
|
|
click.echo(f" Total files found: {len(video_files)}")
|
|
categories = {}
|
|
total_size = 0
|
|
for vf in video_files:
|
|
categories[vf.category] = categories.get(vf.category, 0) + 1
|
|
total_size += vf.size_bytes
|
|
click.echo(f" Total size: {format_size(total_size)}")
|
|
click.echo()
|
|
click.echo("Files by category:")
|
|
for category in sorted(categories.keys()):
|
|
click.echo(f" {category}: {categories[category]}")
|
|
click.echo()
|
|
click.echo(f"Saving inventory to: {output}")
|
|
save_inventory_csv(video_files, output, config.library_root)
|
|
click.echo("Inventory saved successfully!")
|
|
logger.info(f"Scan completed: {len(video_files)} files found, saved to {output}")
|