From 253ff6557d62a4e80863ea260e2d7559752fd7da Mon Sep 17 00:00:00 2001 From: marko-kraemer Date: Sat, 23 Aug 2025 02:38:47 -0700 Subject: [PATCH] presentation processing sandbox wip --- backend/sandbox/docker/html_to_pdf_router.py | 323 ++++++++++++++++ .../convert_to_pptx_perfect.sh | 77 ---- .../elon_musk_presentation.pdf | Bin 0 -> 71207 bytes .../html_to_pdf.py | 358 ------------------ ...perfect.py => html_to_pptx_perfect_wip.py} | 0 .../presentation-processing-wip/metadata.json | 30 -- .../presentations}/slide_01.html | 0 .../presentations}/slide_02.html | 0 .../presentations}/slide_03.html | 0 .../presentations}/slide_04.html | 0 .../presentations}/slide_05.html | 0 .../presentations}/slide_06.html | 0 .../presentations}/slide_07.html | 0 .../presentations}/slide_08.html | 0 .../presentations}/slide_09.html | 0 .../presentations}/slide_10.html | 0 .../presentations}/slide_11.html | 0 backend/sandbox/docker/requirements.txt | 4 +- backend/sandbox/docker/server.py | 251 ++++++++++++ ...editor.py => visual_html_editor_router.py} | 310 ++------------- 20 files changed, 614 insertions(+), 739 deletions(-) create mode 100644 backend/sandbox/docker/html_to_pdf_router.py delete mode 100755 backend/sandbox/docker/presentation-processing-wip/convert_to_pptx_perfect.sh create mode 100644 backend/sandbox/docker/presentation-processing-wip/elon_musk_presentation.pdf delete mode 100644 backend/sandbox/docker/presentation-processing-wip/html_to_pdf.py rename backend/sandbox/docker/presentation-processing-wip/{html_to_pptx_perfect.py => html_to_pptx_perfect_wip.py} (100%) delete mode 100644 backend/sandbox/docker/presentation-processing-wip/metadata.json rename backend/sandbox/docker/presentation-processing-wip/{ => workspace/presentations}/slide_01.html (100%) rename backend/sandbox/docker/presentation-processing-wip/{ => workspace/presentations}/slide_02.html (100%) rename backend/sandbox/docker/presentation-processing-wip/{ => workspace/presentations}/slide_03.html (100%) rename backend/sandbox/docker/presentation-processing-wip/{ => workspace/presentations}/slide_04.html (100%) rename backend/sandbox/docker/presentation-processing-wip/{ => workspace/presentations}/slide_05.html (100%) rename backend/sandbox/docker/presentation-processing-wip/{ => workspace/presentations}/slide_06.html (100%) rename backend/sandbox/docker/presentation-processing-wip/{ => workspace/presentations}/slide_07.html (100%) rename backend/sandbox/docker/presentation-processing-wip/{ => workspace/presentations}/slide_08.html (100%) rename backend/sandbox/docker/presentation-processing-wip/{ => workspace/presentations}/slide_09.html (100%) rename backend/sandbox/docker/presentation-processing-wip/{ => workspace/presentations}/slide_10.html (100%) rename backend/sandbox/docker/presentation-processing-wip/{ => workspace/presentations}/slide_11.html (100%) rename backend/sandbox/docker/{presentation-processing-wip/visual-html-editor.py => visual_html_editor_router.py} (84%) diff --git a/backend/sandbox/docker/html_to_pdf_router.py b/backend/sandbox/docker/html_to_pdf_router.py new file mode 100644 index 00000000..65995d9e --- /dev/null +++ b/backend/sandbox/docker/html_to_pdf_router.py @@ -0,0 +1,323 @@ +#!/usr/bin/env python3 +""" +FastAPI HTML Presentation to PDF Converter Router + +Provides PDF conversion endpoints as a FastAPI router that can be included in other applications. +""" + +import json +import asyncio +from pathlib import Path +from typing import Dict, List +import tempfile + +from fastapi import APIRouter, HTTPException +from fastapi.responses import Response +from pydantic import BaseModel, Field + +try: + from playwright.async_api import async_playwright +except ImportError: + raise ImportError("Playwright is not installed. Please install it with: pip install playwright") + +try: + from PyPDF2 import PdfWriter, PdfReader +except ImportError: + raise ImportError("PyPDF2 is not installed. Please install it with: pip install PyPDF2") + + +# Create router +router = APIRouter(prefix="/presentation", tags=["pdf-conversion"]) + +# Create output directory for generated PDFs +output_dir = Path("generated_pdfs") +output_dir.mkdir(exist_ok=True) + + +class ConvertRequest(BaseModel): + presentation_path: str = Field(..., description="Path to the presentation folder containing metadata.json") + download: bool = Field(False, description="If true, returns the PDF file directly. If false, returns JSON with download URL.") + + +class ConvertResponse(BaseModel): + success: bool + message: str + pdf_url: str + filename: str + total_slides: int + + +class PresentationToPDFAPI: + def __init__(self, presentation_dir: str): + """Initialize the converter with presentation directory.""" + self.presentation_dir = Path(presentation_dir).resolve() + self.metadata_path = self.presentation_dir / "metadata.json" + self.metadata = None + self.slides_info = [] + + # Validate inputs + if not self.presentation_dir.exists(): + raise FileNotFoundError(f"Presentation directory not found: {self.presentation_dir}") + + if not self.metadata_path.exists(): + raise FileNotFoundError(f"metadata.json not found in: {self.presentation_dir}") + + def load_metadata(self) -> Dict: + """Load and parse metadata.json""" + try: + with open(self.metadata_path, 'r', encoding='utf-8') as f: + self.metadata = json.load(f) + + # Extract slide information and sort by slide number + slides = self.metadata.get('slides', {}) + self.slides_info = [] + + for slide_num, slide_data in slides.items(): + filename = slide_data.get('filename') + title = slide_data.get('title', f'Slide {slide_num}') + + if filename: + # Treat filename as absolute path only + html_path = Path(filename) + print(f"Using path: {html_path}") + + # Verify the path exists + if html_path.exists(): + self.slides_info.append({ + 'number': int(slide_num), + 'title': title, + 'filename': filename, + 'path': html_path + }) + print(f"Added slide {slide_num}: {html_path}") + else: + print(f"Warning: HTML file does not exist: {html_path}") + + # Sort slides by number + self.slides_info.sort(key=lambda x: x['number']) + + if not self.slides_info: + raise ValueError("No valid slides found in metadata.json") + + return self.metadata + + except json.JSONDecodeError as e: + raise ValueError(f"Invalid JSON in metadata.json: {e}") + except Exception as e: + raise ValueError(f"Error loading metadata: {e}") + + async def render_slide_to_pdf(self, browser, slide_info: Dict, temp_dir: Path) -> Path: + """Render a single HTML slide to PDF using Playwright.""" + html_path = slide_info['path'] + slide_num = slide_info['number'] + + print(f"Rendering slide {slide_num}: {slide_info['title']}") + + # Create new page with exact presentation dimensions + page = await browser.new_page() + + try: + # Set exact viewport to 1920x1080 + await page.set_viewport_size({"width": 1920, "height": 1080}) + await page.emulate_media(media='screen') + + # Override device pixel ratio for exact dimensions + await page.evaluate(""" + () => { + Object.defineProperty(window, 'devicePixelRatio', { + get: () => 1 + }); + } + """) + + # Navigate to the HTML file + file_url = f"file://{html_path.absolute()}" + await page.goto(file_url, wait_until="networkidle", timeout=30000) + + # Wait for fonts and dynamic content to load + await page.wait_for_timeout(3000) + + # Ensure exact slide dimensions + await page.evaluate(""" + () => { + const slideContainer = document.querySelector('.slide-container'); + if (slideContainer) { + slideContainer.style.width = '1920px'; + slideContainer.style.height = '1080px'; + slideContainer.style.transform = 'none'; + slideContainer.style.maxWidth = 'none'; + slideContainer.style.maxHeight = 'none'; + } + + document.body.style.margin = '0'; + document.body.style.padding = '0'; + document.body.style.width = '1920px'; + document.body.style.height = '1080px'; + document.body.style.overflow = 'hidden'; + } + """) + + await page.wait_for_timeout(1000) + + # Generate PDF for this slide + temp_pdf_path = temp_dir / f"slide_{slide_num:02d}.pdf" + + await page.pdf( + path=str(temp_pdf_path), + width="1920px", + height="1080px", + margin={"top": "0", "right": "0", "bottom": "0", "left": "0"}, + print_background=True, + prefer_css_page_size=False + ) + + print(f" āœ“ Slide {slide_num} rendered") + return temp_pdf_path + + except Exception as e: + raise RuntimeError(f"Error rendering slide {slide_num}: {e}") + finally: + await page.close() + + def combine_pdfs(self, pdf_paths: List[Path], output_path: Path) -> None: + """Combine multiple PDF files into a single PDF.""" + print(f"Combining {len(pdf_paths)} PDFs...") + + pdf_writer = PdfWriter() + + try: + for pdf_path in pdf_paths: + if pdf_path.exists(): + with open(pdf_path, 'rb') as pdf_file: + pdf_reader = PdfReader(pdf_file) + for page in pdf_reader.pages: + pdf_writer.add_page(page) + + # Write the combined PDF + with open(output_path, 'wb') as output_file: + pdf_writer.write(output_file) + + print(f"āœ… PDF created: {output_path}") + + except Exception as e: + raise RuntimeError(f"Error combining PDFs: {e}") + + async def convert_to_pdf(self, store_locally: bool = True) -> tuple: + """Main conversion method with concurrent processing.""" + print("šŸš€ Starting concurrent HTML to PDF conversion...") + + # Load metadata + self.load_metadata() + + # Create temporary directory for intermediate files + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + # Launch browser + async with async_playwright() as p: + print("🌐 Launching browser...") + browser = await p.chromium.launch( + headless=True, + args=[ + '--no-sandbox', + '--disable-setuid-sandbox', + '--disable-dev-shm-usage', + '--disable-gpu', + '--force-device-scale-factor=1', + '--disable-background-timer-throttling' + ] + ) + + try: + # Process all slides concurrently using asyncio.gather + print(f"šŸ“„ Processing {len(self.slides_info)} slides concurrently...") + + tasks = [ + self.render_slide_to_pdf(browser, slide_info, temp_path) + for slide_info in self.slides_info + ] + + # Wait for all slides to be processed concurrently + pdf_paths = await asyncio.gather(*tasks) + + finally: + await browser.close() + + # Create output path + presentation_name = self.metadata.get('presentation_name', 'presentation') + temp_output_path = temp_path / f"{presentation_name}.pdf" + + # Combine all PDFs (sort by slide number to maintain order) + sorted_pdf_paths = sorted(pdf_paths, key=lambda p: int(p.stem.split('_')[1])) + self.combine_pdfs(sorted_pdf_paths, temp_output_path) + + if store_locally: + # Store in the static files directory for URL serving + timestamp = int(asyncio.get_event_loop().time()) + filename = f"{presentation_name}_{timestamp}.pdf" + final_output = output_dir / filename + import shutil + shutil.copy2(temp_output_path, final_output) + return final_output, len(self.slides_info) + else: + # For direct download, read file content into memory (no local storage) + with open(temp_output_path, 'rb') as f: + pdf_content = f.read() + return pdf_content, len(self.slides_info), presentation_name + + +@router.post("/convert-to-pdf") +async def convert_presentation_to_pdf(request: ConvertRequest): + """ + Convert HTML presentation to PDF with concurrent processing. + + Takes a presentation folder path and returns either: + - PDF file directly (if download=true) - uses presentation name as filename + - JSON response with download URL (if download=false, default) + """ + try: + print(f"šŸ“„ Received conversion request for: {request.presentation_path}") + + # Create converter + converter = PresentationToPDFAPI(request.presentation_path) + + # If download is requested, don't store locally and return file directly + if request.download: + pdf_content, total_slides, presentation_name = await converter.convert_to_pdf(store_locally=False) + + print(f"✨ Direct download conversion completed for: {presentation_name}") + + return Response( + content=pdf_content, + media_type="application/pdf", + headers={"Content-Disposition": f"attachment; filename=\"{presentation_name}.pdf\""} + ) + + # Otherwise, store locally and return JSON with download URL + pdf_path, total_slides = await converter.convert_to_pdf(store_locally=True) + + print(f"✨ Conversion completed: {pdf_path}") + + pdf_url = f"/downloads/{pdf_path.name}" + + return ConvertResponse( + success=True, + message=f"PDF generated successfully with {total_slides} slides", + pdf_url=pdf_url, + filename=pdf_path.name, + total_slides=total_slides + ) + + except FileNotFoundError as e: + raise HTTPException(status_code=404, detail=str(e)) + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) + except Exception as e: + print(f"āŒ Conversion error: {e}") + raise HTTPException(status_code=500, detail=f"Conversion failed: {str(e)}") + + +@router.get("/health") +async def pdf_health_check(): + """PDF service health check endpoint.""" + return {"status": "healthy", "service": "HTML to PDF Converter"} \ No newline at end of file diff --git a/backend/sandbox/docker/presentation-processing-wip/convert_to_pptx_perfect.sh b/backend/sandbox/docker/presentation-processing-wip/convert_to_pptx_perfect.sh deleted file mode 100755 index e85383ad..00000000 --- a/backend/sandbox/docker/presentation-processing-wip/convert_to_pptx_perfect.sh +++ /dev/null @@ -1,77 +0,0 @@ -#!/bin/bash - -# HTML to PPTX Perfect 1:1 Converter Setup and Execution Script -# This script provides PERFECT 1:1 conversion with complete background capture + editable text - -set -e # Exit on any error - -echo "šŸŽÆ HTML to PPTX Perfect 1:1 Converter" -echo "=====================================" -echo "šŸŽØ Perfect background capture + Editable text overlay" -echo "" - -# Check if Python is available -if ! command -v python3 &> /dev/null; then - echo "āŒ Python 3 is required but not found. Please install Python 3.7+ and try again." - exit 1 -fi - -echo "šŸ”§ Setting up dependencies..." - -# Install Python dependencies -if [ -f "requirements.txt" ]; then - echo "šŸ“¦ Installing Python packages..." - python3 -m pip install -r requirements.txt -else - echo "šŸ“¦ Installing Python packages individually..." - python3 -m pip install playwright python-pptx Pillow beautifulsoup4 lxml -fi - -# Install Playwright browsers -echo "🌐 Installing Playwright browser..." -python3 -m playwright install chromium - -echo "āœ… Dependencies installed successfully!" -echo "" - -# Run the perfect conversion -echo "šŸš€ Starting PERFECT 1:1 HTML to PPTX conversion..." -echo "šŸ“‹ Method: Perfect background capture + Editable text overlay" -echo "" - -if [ $# -eq 0 ]; then - # No arguments, use current directory with perfect naming - python3 html_to_pptx_perfect.py -elif [ $# -eq 1 ]; then - # One argument (presentation directory or output file) - if [[ "$1" == *.pptx ]]; then - # If argument ends with .pptx, treat it as output filename - python3 html_to_pptx_perfect.py . "$1" - else - # Otherwise treat it as presentation directory - python3 html_to_pptx_perfect.py "$1" - fi -elif [ $# -eq 2 ]; then - # Two arguments (presentation directory and output file) - python3 html_to_pptx_perfect.py "$1" "$2" -else - echo "Usage: $0 [presentation_directory] [output_file.pptx]" - echo "" - echo "Examples:" - echo " $0 # Convert current directory (perfect mode)" - echo " $0 my_slides/ # Convert my_slides/ (perfect mode)" - echo " $0 perfect_output.pptx # Convert current directory to perfect_output.pptx" - echo " $0 my_slides/ perfect_output.pptx # Convert my_slides/ to perfect_output.pptx" - echo "" - echo "Perfect 1:1 Mode Features:" - echo " āœ… PERFECT visual fidelity (everything captured exactly)" - echo " āœ… All icons, gradients, decorations preserved" - echo " āœ… Fully editable text elements" - echo " āœ… True 1:1 conversion" - echo " āœ… Simple and reliable approach" - exit 1 -fi - -echo "" -echo "šŸŽ‰ PERFECT 1:1 HTML to PPTX conversion completed!" -echo "✨ Perfect backgrounds + Editable text!" diff --git a/backend/sandbox/docker/presentation-processing-wip/elon_musk_presentation.pdf b/backend/sandbox/docker/presentation-processing-wip/elon_musk_presentation.pdf new file mode 100644 index 0000000000000000000000000000000000000000..1a6b1b6be5048730d69f96cdc988d60bf31a855c GIT binary patch literal 71207 zcmd3O1wd5W_P2zfv~;N;T>~=%0|SWC4N}t0fHcww2nYz0(v5U?w*t~B-6-7%NQvNg z!0Ubb-hKBUU;IBuX3p7X$69Oez0TUdz4oGeBqGKJ261B0Ei4WUV}PkRsH_dlF!=c~ z*p*#u5LE1s^i2?U80^B<4p#P5Pz-iyQzJVnO)5?xqaqa)a`gk_y86+^5D>sXSQ%Yb zaQWb8MUQN)jT{USwp5IdT!7{vOuyuV{*v!UgTngu`WDtE7(ZI3x}-?)mjag!|E%~Q z6k@O|BJ8XkYz+~1RF|{^O&Tg8>;XdAfflLQl@ZSNRP2(L05d{={0jf^D@mn|!7l1- zFRo;-Z;!YtEe@r+;s}FXoSO=EMF6`vn1hP@CoKSN_Vx%{s~>G~Qt|xxKuzAj3}I-0 z^+1h>3jU+88W>Ck{!t|$B6cxrK$e$eig8gvelW-`28K{UezXN-xy&VtFf!E_vUUcT z-~c{gPEHOg9)JpfTw$OBgq6J=74(nN%K8R&RP0KZLbC-P{EO5s+x{svLwzbR2D^|f z6}z0Zt);%jFT%ZS|CekWAo#z@cqxhBGV=VJjF*!6nek_LU5W0J)T@iDR0zPXzP+vK z6|*4jOL94=*f@S%{lT=-6MZ97E0Z6BbFebBH?{uJ0m2+VL&e+U|=xMm6X6h zIv^krCm4Ro86Y?jYfF7otE=qb%X_gu>iJ2893YxY*#qMN7|1|Ads_#@kFP7STiafZ zehAPFY?ohP?#nL-{HpRl2>e->u(gG?t&)wtAux+X5RRsX2t{$B%i4kef6S80;qkk4 z{un

`D#>_E!^74Cti)OT+t;(|?Rb7!|i=ixS+8@7}%k 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

fEAV#(M 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 Dict: - """Load and parse metadata.json""" - try: - with open(self.metadata_path, 'r', encoding='utf-8') as f: - self.metadata = json.load(f) - - # Extract slide information and sort by slide number - slides = self.metadata.get('slides', {}) - self.slides_info = [] - - for slide_num, slide_data in slides.items(): - filename = slide_data.get('filename') - title = slide_data.get('title', f'Slide {slide_num}') - - if filename: - html_path = self.presentation_dir / filename - if html_path.exists(): - self.slides_info.append({ - 'number': int(slide_num), - 'title': title, - 'filename': filename, - 'path': html_path - }) - else: - print(f"Warning: HTML file not found: {html_path}") - - # Sort slides by number - self.slides_info.sort(key=lambda x: x['number']) - - if not self.slides_info: - raise ValueError("No valid slides found in metadata.json") - - # Set default output path if not provided - if not self.output_path: - presentation_name = self.metadata.get('presentation_name', 'presentation') - self.output_path = self.presentation_dir / f"{presentation_name}.pdf" - else: - self.output_path = Path(self.output_path).resolve() - - print(f"Loaded {len(self.slides_info)} slides from metadata") - return self.metadata - - except json.JSONDecodeError as e: - raise ValueError(f"Invalid JSON in metadata.json: {e}") - except Exception as e: - raise ValueError(f"Error loading metadata: {e}") - - async def render_slide_to_pdf(self, browser, slide_info: Dict, temp_dir: Path) -> Path: - """ - Render a single HTML slide to PDF using Playwright. - - Args: - browser: Playwright browser instance - slide_info: Slide information dictionary - temp_dir: Temporary directory for intermediate files - - Returns: - Path to the generated PDF file - """ - html_path = slide_info['path'] - slide_num = slide_info['number'] - - print(f"Rendering slide {slide_num}: {slide_info['title']}") - - # Create new page with exact presentation dimensions - page = await browser.new_page() - - try: - # CRITICAL: Set exact viewport to 1920x1080 - this is the key! - await page.set_viewport_size({"width": 1920, "height": 1080}) - - # Use screen media type for accurate rendering - await page.emulate_media(media='screen') - - # Disable device scale factor to ensure 1:1 pixel mapping - await page.evaluate(""" - () => { - // Override device pixel ratio to ensure exact dimensions - Object.defineProperty(window, 'devicePixelRatio', { - get: () => 1 - }); - } - """) - - # Navigate to the HTML file - file_url = f"file://{html_path.absolute()}" - await page.goto(file_url, wait_until="networkidle", timeout=30000) - - # Wait for fonts and dynamic content to fully load - await page.wait_for_timeout(3000) - - # Ensure the slide container is exactly 1920x1080 - await page.evaluate(""" - () => { - const slideContainer = document.querySelector('.slide-container'); - if (slideContainer) { - slideContainer.style.width = '1920px'; - slideContainer.style.height = '1080px'; - slideContainer.style.transform = 'none'; - slideContainer.style.maxWidth = 'none'; - slideContainer.style.maxHeight = 'none'; - } - - // Ensure body doesn't interfere with dimensions - document.body.style.margin = '0'; - document.body.style.padding = '0'; - document.body.style.width = '1920px'; - document.body.style.height = '1080px'; - document.body.style.overflow = 'hidden'; - } - """) - - # Wait a bit more for the layout adjustments - await page.wait_for_timeout(1000) - - # Generate PDF for this slide with exact dimensions - temp_pdf_path = temp_dir / f"slide_{slide_num:02d}.pdf" - - await page.pdf( - path=str(temp_pdf_path), - width="1920px", - height="1080px", - margin={"top": "0", "right": "0", "bottom": "0", "left": "0"}, - print_background=True, - prefer_css_page_size=False - ) - - print(f" āœ“ Slide {slide_num} rendered at 1920x1080") - return temp_pdf_path - - except Exception as e: - raise RuntimeError(f"Error rendering slide {slide_num}: {e}") - finally: - await page.close() - - def combine_pdfs(self, pdf_paths: List[Path]) -> None: - """ - Combine multiple PDF files into a single PDF. - - Args: - pdf_paths: List of PDF file paths to combine - """ - print(f"Combining {len(pdf_paths)} PDFs into final output...") - - pdf_writer = PdfWriter() - - try: - for pdf_path in pdf_paths: - if not pdf_path.exists(): - print(f"Warning: PDF file not found: {pdf_path}") - continue - - with open(pdf_path, 'rb') as pdf_file: - pdf_reader = PdfReader(pdf_file) - for page in pdf_reader.pages: - pdf_writer.add_page(page) - - # Write the combined PDF - with open(self.output_path, 'wb') as output_file: - pdf_writer.write(output_file) - - print(f"āœ… PDF created successfully: {self.output_path}") - print(f"šŸ“Š Total pages: {len(pdf_writer.pages)}") - - except Exception as e: - raise RuntimeError(f"Error combining PDFs: {e}") - - async def convert_to_pdf(self) -> None: - """Main conversion method""" - print("šŸš€ Starting HTML to PDF conversion...") - - # Load metadata - self.load_metadata() - - # Create temporary directory for intermediate files - with tempfile.TemporaryDirectory() as temp_dir: - temp_path = Path(temp_dir) - pdf_paths = [] - - # Launch browser with exact rendering settings - async with async_playwright() as p: - print("🌐 Launching browser with 1920x1080 configuration...") - browser = await p.chromium.launch( - headless=True, - args=[ - '--no-sandbox', - '--disable-setuid-sandbox', - '--disable-dev-shm-usage', - '--disable-gpu', - '--no-first-run', - '--disable-default-apps', - '--disable-web-security', - '--disable-features=TranslateUI', - '--disable-ipc-flooding-protection', - # Force device scale factor to 1 for exact pixel mapping - '--force-device-scale-factor=1', - '--disable-background-timer-throttling', - '--disable-backgrounding-occluded-windows', - '--disable-renderer-backgrounding' - ] - ) - - try: - # Process each slide - for slide_info in self.slides_info: - pdf_path = await self.render_slide_to_pdf(browser, slide_info, temp_path) - pdf_paths.append(pdf_path) - - finally: - await browser.close() - - # Combine all PDFs - self.combine_pdfs(pdf_paths) - - print("✨ Conversion completed successfully!") - - -def check_dependencies(): - """Check if required dependencies are available""" - missing_deps = [] - - try: - import playwright - except ImportError: - missing_deps.append("playwright (pip install playwright)") - - try: - import PyPDF2 - except ImportError: - missing_deps.append("PyPDF2 (pip install PyPDF2)") - - if missing_deps: - print("āŒ Missing dependencies:") - for dep in missing_deps: - print(f" - {dep}") - print("\nPlease install missing dependencies and try again.") - return False - - # Check if Playwright browsers are installed - try: - result = subprocess.run(['playwright', 'install', '--dry-run'], - capture_output=True, text=True, timeout=10) - if "chromium" not in result.stdout.lower(): - print("āš ļø Playwright browser not found. Please run:") - print(" playwright install chromium") - return False - except (subprocess.TimeoutExpired, subprocess.CalledProcessError, FileNotFoundError): - print("āš ļø Could not verify Playwright installation. You may need to run:") - print(" playwright install chromium") - - return True - - -def main(): - """Main CLI entry point""" - print("šŸ“„ HTML Presentation to PDF Converter") - print("=" * 50) - - # Check dependencies - if not check_dependencies(): - sys.exit(1) - - # Parse command line arguments - if len(sys.argv) < 2: - presentation_dir = "." - output_path = None - elif len(sys.argv) == 2: - presentation_dir = sys.argv[1] - output_path = None - elif len(sys.argv) == 3: - presentation_dir = sys.argv[1] - output_path = sys.argv[2] - else: - print("Usage: python html_to_pdf.py [presentation_directory] [output_pdf_path]") - print("\nExamples:") - print(" python html_to_pdf.py") - print(" python html_to_pdf.py . my_presentation.pdf") - print(" python html_to_pdf.py /path/to/slides output.pdf") - sys.exit(1) - - try: - # Create converter and run - converter = PresentationToPDF(presentation_dir, output_path) - asyncio.run(converter.convert_to_pdf()) - - except KeyboardInterrupt: - print("\nāŒ Conversion cancelled by user") - sys.exit(1) - except Exception as e: - print(f"āŒ Error: {e}") - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/backend/sandbox/docker/presentation-processing-wip/html_to_pptx_perfect.py b/backend/sandbox/docker/presentation-processing-wip/html_to_pptx_perfect_wip.py similarity index 100% rename from backend/sandbox/docker/presentation-processing-wip/html_to_pptx_perfect.py rename to backend/sandbox/docker/presentation-processing-wip/html_to_pptx_perfect_wip.py diff --git a/backend/sandbox/docker/presentation-processing-wip/metadata.json b/backend/sandbox/docker/presentation-processing-wip/metadata.json deleted file mode 100644 index 871477b9..00000000 --- a/backend/sandbox/docker/presentation-processing-wip/metadata.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "presentation_name": "elon_musk", - "title": "Elon Musk: Visionary Entrepreneur", - "description": "", - "slides": { - "1": { - "title": "Title Slide", - "filename": "slide_01.html", - "file_path": "presentations/elon_musk/slide_01.html", - "preview_url": "/workspace/presentations/elon_musk/slide_01.html", - "created_at": "2025-08-20T23:16:46.862281" - }, - "2": { - "title": "Early Life & Background", - "filename": "slide_02.html", - "file_path": "presentations/elon_musk/slide_02.html", - "preview_url": "/workspace/presentations/elon_musk/slide_02.html", - "created_at": "2025-08-20T23:17:02.255166" - }, - "3": { - "title": "First Ventures", - "filename": "slide_03.html", - "file_path": "presentations/elon_musk/slide_03.html", - "preview_url": "/workspace/presentations/elon_musk/slide_03.html", - "created_at": "2025-08-20T23:17:22.873481" - } - }, - "created_at": "2025-08-20T23:16:46.285519", - "updated_at": "2025-08-20T23:20:04.399832" -} \ No newline at end of file diff --git a/backend/sandbox/docker/presentation-processing-wip/slide_01.html b/backend/sandbox/docker/presentation-processing-wip/workspace/presentations/slide_01.html similarity index 100% rename from backend/sandbox/docker/presentation-processing-wip/slide_01.html rename to backend/sandbox/docker/presentation-processing-wip/workspace/presentations/slide_01.html diff --git a/backend/sandbox/docker/presentation-processing-wip/slide_02.html b/backend/sandbox/docker/presentation-processing-wip/workspace/presentations/slide_02.html similarity index 100% rename from backend/sandbox/docker/presentation-processing-wip/slide_02.html rename to backend/sandbox/docker/presentation-processing-wip/workspace/presentations/slide_02.html diff --git a/backend/sandbox/docker/presentation-processing-wip/slide_03.html b/backend/sandbox/docker/presentation-processing-wip/workspace/presentations/slide_03.html similarity index 100% rename from backend/sandbox/docker/presentation-processing-wip/slide_03.html rename to backend/sandbox/docker/presentation-processing-wip/workspace/presentations/slide_03.html diff --git a/backend/sandbox/docker/presentation-processing-wip/slide_04.html b/backend/sandbox/docker/presentation-processing-wip/workspace/presentations/slide_04.html similarity index 100% rename from backend/sandbox/docker/presentation-processing-wip/slide_04.html rename to backend/sandbox/docker/presentation-processing-wip/workspace/presentations/slide_04.html diff --git a/backend/sandbox/docker/presentation-processing-wip/slide_05.html b/backend/sandbox/docker/presentation-processing-wip/workspace/presentations/slide_05.html similarity index 100% rename from backend/sandbox/docker/presentation-processing-wip/slide_05.html rename to backend/sandbox/docker/presentation-processing-wip/workspace/presentations/slide_05.html diff --git a/backend/sandbox/docker/presentation-processing-wip/slide_06.html b/backend/sandbox/docker/presentation-processing-wip/workspace/presentations/slide_06.html similarity index 100% rename from backend/sandbox/docker/presentation-processing-wip/slide_06.html rename to backend/sandbox/docker/presentation-processing-wip/workspace/presentations/slide_06.html diff --git a/backend/sandbox/docker/presentation-processing-wip/slide_07.html b/backend/sandbox/docker/presentation-processing-wip/workspace/presentations/slide_07.html similarity index 100% rename from backend/sandbox/docker/presentation-processing-wip/slide_07.html rename to backend/sandbox/docker/presentation-processing-wip/workspace/presentations/slide_07.html diff --git a/backend/sandbox/docker/presentation-processing-wip/slide_08.html b/backend/sandbox/docker/presentation-processing-wip/workspace/presentations/slide_08.html similarity index 100% rename from backend/sandbox/docker/presentation-processing-wip/slide_08.html rename to backend/sandbox/docker/presentation-processing-wip/workspace/presentations/slide_08.html diff --git a/backend/sandbox/docker/presentation-processing-wip/slide_09.html b/backend/sandbox/docker/presentation-processing-wip/workspace/presentations/slide_09.html similarity index 100% rename from backend/sandbox/docker/presentation-processing-wip/slide_09.html rename to backend/sandbox/docker/presentation-processing-wip/workspace/presentations/slide_09.html diff --git a/backend/sandbox/docker/presentation-processing-wip/slide_10.html b/backend/sandbox/docker/presentation-processing-wip/workspace/presentations/slide_10.html similarity index 100% rename from backend/sandbox/docker/presentation-processing-wip/slide_10.html rename to backend/sandbox/docker/presentation-processing-wip/workspace/presentations/slide_10.html diff --git a/backend/sandbox/docker/presentation-processing-wip/slide_11.html b/backend/sandbox/docker/presentation-processing-wip/workspace/presentations/slide_11.html similarity index 100% rename from backend/sandbox/docker/presentation-processing-wip/slide_11.html rename to backend/sandbox/docker/presentation-processing-wip/workspace/presentations/slide_11.html diff --git a/backend/sandbox/docker/requirements.txt b/backend/sandbox/docker/requirements.txt index dc10dd11..3ebb1cff 100644 --- a/backend/sandbox/docker/requirements.txt +++ b/backend/sandbox/docker/requirements.txt @@ -4,4 +4,6 @@ pyautogui==0.9.54 pillow==10.2.0 pydantic==2.6.1 pytesseract==0.3.13 -pandas==2.3.0 \ No newline at end of file +pandas==2.3.0 +playwright>=1.40.0 +PyPDF2>=3.0.0 \ No newline at end of file diff --git a/backend/sandbox/docker/server.py b/backend/sandbox/docker/server.py index defa5f0a..d51411ea 100644 --- a/backend/sandbox/docker/server.py +++ b/backend/sandbox/docker/server.py @@ -3,6 +3,11 @@ from fastapi.staticfiles import StaticFiles from starlette.middleware.base import BaseHTTPMiddleware import uvicorn import os +from pathlib import Path + +# Import PDF router and Visual HTML Editor router +from html_to_pdf_router import router as pdf_router +from visual_html_editor_router import router as editor_router # Ensure we're serving from the /workspace directory workspace_dir = "/workspace" @@ -18,8 +23,254 @@ class WorkspaceDirMiddleware(BaseHTTPMiddleware): app = FastAPI() app.add_middleware(WorkspaceDirMiddleware) +# Include routers +app.include_router(pdf_router) +app.include_router(editor_router) + +# Create output directory for generated PDFs (needed by PDF router) +output_dir = Path("generated_pdfs") +output_dir.mkdir(exist_ok=True) + +# Mount static files for PDF downloads +app.mount("/downloads", StaticFiles(directory=str(output_dir)), name="downloads") + # Initial directory creation os.makedirs(workspace_dir, exist_ok=True) + +# Add visual HTML editor root endpoint +@app.get("/editor") +async def list_html_files(): + """List all HTML files in the workspace for easy access""" + from fastapi.responses import HTMLResponse + try: + html_files = [f for f in os.listdir(workspace_dir) if f.endswith('.html')] + + html_content = """ + + + + + + Visual HTML Editor + + + +

+

Visual HTML Editor

+

Click-to-edit any HTML file with live preview

+
+ +
+ """ + + if html_files: + for file in sorted(html_files): + html_content += f""" +
+
{file}
+
+ View + Edit +
+
+ """ + else: + html_content += """ +
+

No files found

+

Add .html files to this directory to start editing

+
+ """ + + html_content += """ +
+ +
+

How to use

+
+
+ Edit text: Hover over any text and click the edit icon +
+
+ Delete elements: Click the trash icon to remove content +
+
+ Save changes: Press Ctrl+Enter or click Save +
+
+ Cancel editing: Press Escape or click Cancel +
+
+
+ + + """ + + return HTMLResponse(content=html_content) + + except Exception as e: + print(f"āŒ Error listing HTML files: {e}") + from fastapi import HTTPException + raise HTTPException(status_code=500, detail=str(e)) + +# Serve HTML files directly at root level +@app.get("/{file_name}") +async def serve_html_file(file_name: str): + """Serve HTML files directly for viewing""" + from fastapi import HTTPException + from fastapi.responses import HTMLResponse + + if not file_name.endswith('.html'): + raise HTTPException(status_code=404, detail="File must be .html") + + file_path = os.path.join(workspace_dir, file_name) + if not os.path.exists(file_path): + raise HTTPException(status_code=404, detail="File not found") + + with open(file_path, 'r', encoding='utf-8') as f: + content = f.read() + + return HTMLResponse(content=content) + app.mount('/', StaticFiles(directory=workspace_dir, html=True), name='site') # This is needed for the import string approach with uvicorn diff --git a/backend/sandbox/docker/presentation-processing-wip/visual-html-editor.py b/backend/sandbox/docker/visual_html_editor_router.py similarity index 84% rename from backend/sandbox/docker/presentation-processing-wip/visual-html-editor.py rename to backend/sandbox/docker/visual_html_editor_router.py index ba3c3ac0..5e30b922 100644 --- a/backend/sandbox/docker/presentation-processing-wip/visual-html-editor.py +++ b/backend/sandbox/docker/visual_html_editor_router.py @@ -1,17 +1,26 @@ -from fastapi import FastAPI, Request, HTTPException -from fastapi.staticfiles import StaticFiles -from fastapi.responses import HTMLResponse, JSONResponse -from starlette.middleware.base import BaseHTTPMiddleware -from pydantic import BaseModel -from typing import Optional, Dict, Any -import uvicorn +#!/usr/bin/env python3 +""" +Visual HTML Editor Router + +Provides visual HTML editing endpoints as a FastAPI router that can be included in other applications. +""" + import os -import json import re +from typing import Optional, Dict, Any +from pathlib import Path + +from fastapi import APIRouter, HTTPException +from fastapi.responses import HTMLResponse +from fastapi.staticfiles import StaticFiles +from pydantic import BaseModel from bs4 import BeautifulSoup, NavigableString -# Use current directory as workspace (presentation-example folder) -workspace_dir = os.path.dirname(os.path.abspath(__file__)) +# Create router +router = APIRouter(prefix="/api/html", tags=["visual-editor"]) + +# Use /workspace as the default workspace directory +workspace_dir = "/workspace" # All text elements that should be editable TEXT_ELEMENTS = [ @@ -29,36 +38,28 @@ TEXT_ELEMENTS = [ 'label', 'legend', # Form text ] -class WorkspaceDirMiddleware(BaseHTTPMiddleware): - async def dispatch(self, request: Request, call_next): - # Ensure workspace directory exists - if not os.path.exists(workspace_dir): - print(f"Workspace directory {workspace_dir} not found, recreating...") - os.makedirs(workspace_dir, exist_ok=True) - return await call_next(request) - -app = FastAPI(title="Visual HTML Editor", version="1.0.0") -app.add_middleware(WorkspaceDirMiddleware) - -# ===== VISUAL HTML EDITOR API ===== class EditTextRequest(BaseModel): file_path: str element_selector: str # CSS selector to identify element new_text: str + class DeleteElementRequest(BaseModel): file_path: str element_selector: str + class SaveContentRequest(BaseModel): file_path: str html_content: str + class GetEditableElementsResponse(BaseModel): elements: list[Dict[str, Any]] -@app.get("/api/html/{file_path:path}/editable-elements") + +@router.get("/{file_path:path}/editable-elements") async def get_editable_elements(file_path: str): """Get all editable text elements from an HTML file""" try: @@ -144,7 +145,8 @@ async def get_editable_elements(file_path: str): print(f"Error getting editable elements: {e}") raise HTTPException(status_code=500, detail=str(e)) -@app.post("/api/html/edit-text") + +@router.post("/edit-text") async def edit_text(request: EditTextRequest): """Edit text content of an element in an HTML file""" try: @@ -187,7 +189,8 @@ async def edit_text(request: EditTextRequest): print(f"āŒ Error editing text: {e}") raise HTTPException(status_code=500, detail=str(e)) -@app.post("/api/html/delete-element") + +@router.post("/delete-element") async def delete_element(request: DeleteElementRequest): """Delete an element from an HTML file""" try: @@ -239,7 +242,8 @@ async def delete_element(request: DeleteElementRequest): print(f"āŒ Error deleting element: {e}") raise HTTPException(status_code=500, detail=str(e)) -@app.post("/api/html/save-content") + +@router.post("/save-content") async def save_content(request: SaveContentRequest): """Save the entire HTML content to file""" try: @@ -297,7 +301,8 @@ async def save_content(request: SaveContentRequest): print(f"āŒ Error saving content: {e}") raise HTTPException(status_code=500, detail=str(e)) -@app.get("/api/html/{file_path:path}/editor") + +@router.get("/{file_path:path}/editor") async def get_html_editor(file_path: str): """Serve the visual editor for an HTML file""" try: @@ -317,217 +322,6 @@ async def get_html_editor(file_path: str): print(f"āŒ Error serving editor: {e}") raise HTTPException(status_code=500, detail=str(e)) -@app.get("/") -async def list_html_files(): - """List all HTML files in the workspace for easy access""" - try: - html_files = [f for f in os.listdir(workspace_dir) if f.endswith('.html')] - - html_content = """ - - - - - - Visual HTML Editor - - - -
-

Visual HTML Editor

-

Click-to-edit any HTML file with live preview

-
- -
- """ - - if html_files: - for file in sorted(html_files): - html_content += f""" -
-
{file}
-
- View - Edit -
-
- """ - else: - html_content += """ -
-

No files found

-

Add .html files to this directory to start editing

-
- """ - - html_content += """ -
- -
-

How to use

-
-
- Edit text: Hover over any text and click the edit icon -
-
- Delete elements: Click the trash icon to remove content -
-
- Save changes: Press Ctrl+Enter or click Save -
-
- Cancel editing: Press Escape or click Cancel -
-
-
- - - """ - - return HTMLResponse(content=html_content) - - except Exception as e: - print(f"āŒ Error listing HTML files: {e}") - raise HTTPException(status_code=500, detail=str(e)) def inject_editor_functionality(html_content: str, file_path: str) -> str: """Inject visual editor functionality into existing HTML""" @@ -913,7 +707,7 @@ def inject_editor_functionality(html_content: str, file_path: str) -> str: # Add editor JavaScript editor_js = f"""