Initial project scaffold - Phase 1 MVP structure

Core modules:
- cli.py: Command-line interface with Click
- pipeline.py: Main orchestrator
- video_processor.py: Frame extraction with ffmpeg
- audio_analyzer.py: Whisper transcription
- vision_analyzer.py: Component detection (placeholder)
- doc_generator.py: Markdown + PDF output

Also includes:
- pyproject.toml with uv/hatch config
- Prompts for AI analysis
- Basic tests
- ROADMAP.md with 4-week plan
This commit is contained in:
Mario Lavoie
2026-01-27 20:05:34 +00:00
parent 621234cbdf
commit 1e94a98e5b
16 changed files with 1062 additions and 1 deletions

86
src/cad_documenter/cli.py Normal file
View File

@@ -0,0 +1,86 @@
"""CAD-Documenter CLI - Main entry point."""
import click
from pathlib import Path
from rich.console import Console
from .pipeline import DocumentationPipeline
console = Console()
@click.command()
@click.argument("video", type=click.Path(exists=True, path_type=Path))
@click.option("-o", "--output", type=click.Path(path_type=Path), help="Output directory")
@click.option("--frames-only", is_flag=True, help="Only extract frames, skip documentation")
@click.option("--atomizer-hints", is_flag=True, help="Generate Atomizer FEA hints")
@click.option("--bom", is_flag=True, help="Generate Bill of Materials")
@click.option("--pdf", is_flag=True, help="Generate PDF via Atomaste Report Standard")
@click.option("--frame-interval", default=2.0, help="Seconds between frame extractions")
@click.option("--whisper-model", default="base", help="Whisper model size (tiny/base/small/medium/large)")
@click.version_option()
def main(
video: Path,
output: Path | None,
frames_only: bool,
atomizer_hints: bool,
bom: bool,
pdf: bool,
frame_interval: float,
whisper_model: str,
):
"""
Generate engineering documentation from a CAD walkthrough video.
VIDEO: Path to the video file (.mp4, .mov, .avi, etc.)
"""
console.print(f"[bold blue]CAD-Documenter[/bold blue] v0.1.0")
console.print(f"Processing: [cyan]{video}[/cyan]")
# Default output directory
if output is None:
output = video.parent / f"{video.stem}_docs"
output.mkdir(parents=True, exist_ok=True)
# Run pipeline
pipeline = DocumentationPipeline(
video_path=video,
output_dir=output,
frame_interval=frame_interval,
whisper_model=whisper_model,
)
if frames_only:
console.print("[yellow]Extracting frames only...[/yellow]")
pipeline.extract_frames()
console.print(f"[green]✓[/green] Frames saved to {output / 'frames'}")
return
# Full pipeline
console.print("[yellow]Step 1/4:[/yellow] Extracting frames...")
frames = pipeline.extract_frames()
console.print(f" [green]✓[/green] Extracted {len(frames)} frames")
console.print("[yellow]Step 2/4:[/yellow] Transcribing audio...")
transcript = pipeline.transcribe_audio()
console.print(f" [green]✓[/green] Transcribed {len(transcript.segments)} segments")
console.print("[yellow]Step 3/4:[/yellow] Analyzing components...")
analysis = pipeline.analyze_components(frames, transcript)
console.print(f" [green]✓[/green] Identified {len(analysis.components)} components")
console.print("[yellow]Step 4/4:[/yellow] Generating documentation...")
doc_path = pipeline.generate_documentation(analysis, atomizer_hints=atomizer_hints, bom=bom)
console.print(f" [green]✓[/green] Documentation saved to {doc_path}")
if pdf:
console.print("[yellow]Generating PDF...[/yellow]")
pdf_path = pipeline.generate_pdf(doc_path)
console.print(f" [green]✓[/green] PDF saved to {pdf_path}")
console.print(f"\n[bold green]Done![/bold green] Output: {output}")
if __name__ == "__main__":
main()