Spaces:
Paused
Paused
| import os | |
| import gradio as gr | |
| import shutil | |
| import tempfile | |
| import uuid | |
| from pathlib import Path | |
| import json | |
| import base64 | |
| from PIL import Image | |
| import fitz # PyMuPDF for PDF handling | |
| # Constants | |
| TEMP_DIR = "temp" | |
| UPLOAD_DIR = os.path.join(TEMP_DIR, "uploads") | |
| OUTPUT_DIR = os.path.join(TEMP_DIR, "output") | |
| THUMBS_DIR = os.path.join(OUTPUT_DIR, "thumbs") | |
| HTML_DIR = os.path.join(OUTPUT_DIR, "html") | |
| # Ensure directories exist | |
| for dir_path in [TEMP_DIR, UPLOAD_DIR, OUTPUT_DIR, THUMBS_DIR, HTML_DIR]: | |
| os.makedirs(dir_path, exist_ok=True) | |
| def create_thumbnail(image_path, output_path, size=(300, 300)): | |
| """Create a thumbnail from an image.""" | |
| try: | |
| with Image.open(image_path) as img: | |
| img.thumbnail(size, Image.LANCZOS) | |
| img.save(output_path) | |
| return output_path | |
| except Exception as e: | |
| print(f"Error creating thumbnail: {e}") | |
| return None | |
| def process_pdf(pdf_path, session_id): | |
| """Extract pages from a PDF and save as images with thumbnails.""" | |
| pages_info = [] | |
| output_folder = os.path.join(OUTPUT_DIR, session_id) | |
| thumbs_folder = os.path.join(THUMBS_DIR, session_id) | |
| os.makedirs(output_folder, exist_ok=True) | |
| os.makedirs(thumbs_folder, exist_ok=True) | |
| try: | |
| # Open the PDF | |
| pdf_document = fitz.open(pdf_path) | |
| # Process each page | |
| for page_num, page in enumerate(pdf_document): | |
| # Render page to an image with a higher resolution | |
| pix = page.get_pixmap(matrix=fitz.Matrix(2, 2)) | |
| image_path = os.path.join(output_folder, f"page_{page_num + 1}.png") | |
| pix.save(image_path) | |
| # Create thumbnail | |
| thumb_path = os.path.join(thumbs_folder, f"thumb_{page_num + 1}.png") | |
| create_thumbnail(image_path, thumb_path) | |
| # Add simple interactive content to first page | |
| html_content = "" | |
| if page_num == 0: # First page example | |
| html_content = """ | |
| <div style="position: absolute; top: 50px; left: 50px; background-color: rgba(255,255,255,0.7); padding: 10px; border-radius: 5px;"> | |
| <div style="color: #333; font-size: 18px; font-weight: bold;">์ธํฐ๋ํฐ๋ธ ํ๋ฆฝ๋ถ ์์ </div> | |
| <div style="color: #666; margin-top: 5px;">์ด ํ์ด์ง๋ ์ธํฐ๋ํฐ๋ธ ์ปจํ ์ธ ๊ธฐ๋ฅ์ ๋ณด์ฌ์ค๋๋ค.</div> | |
| </div> | |
| """ | |
| # Get relative web paths for the HTML file | |
| rel_image_path = os.path.relpath(image_path, HTML_DIR).replace("\\", "/") | |
| rel_thumb_path = os.path.relpath(thumb_path, HTML_DIR).replace("\\", "/") | |
| # Add page info with interactive content | |
| pages_info.append({ | |
| "src": rel_image_path, | |
| "thumb": rel_thumb_path, | |
| "title": f"ํ์ด์ง {page_num + 1}", | |
| "htmlContent": html_content if html_content else None | |
| }) | |
| print(f"Processed PDF page {page_num+1}: {rel_image_path}") | |
| return pages_info | |
| except Exception as e: | |
| print(f"Error processing PDF: {e}") | |
| return [] | |
| def process_images(image_paths, session_id): | |
| """Process uploaded images and create thumbnails.""" | |
| pages_info = [] | |
| output_folder = os.path.join(OUTPUT_DIR, session_id) | |
| thumbs_folder = os.path.join(THUMBS_DIR, session_id) | |
| os.makedirs(output_folder, exist_ok=True) | |
| os.makedirs(thumbs_folder, exist_ok=True) | |
| for i, img_path in enumerate(image_paths): | |
| try: | |
| # Copy original image to output folder | |
| dest_path = os.path.join(output_folder, f"image_{i + 1}.png") | |
| shutil.copy(img_path, dest_path) | |
| # Create thumbnail | |
| thumb_path = os.path.join(thumbs_folder, f"thumb_{i + 1}.png") | |
| create_thumbnail(img_path, thumb_path) | |
| # Add interactive content as simple text overlays to avoid compatibility issues | |
| html_content = "" | |
| if i == 0: # First image example with HTML content | |
| html_content = """ | |
| <div style="position: absolute; top: 50px; left: 50px; background-color: rgba(255,255,255,0.7); padding: 10px; border-radius: 5px;"> | |
| <div style="color: #333; font-size: 18px; font-weight: bold;">์ด๋ฏธ์ง ๊ฐค๋ฌ๋ฆฌ</div> | |
| <div style="color: #666; margin-top: 5px;">๊ฐค๋ฌ๋ฆฌ์ ์ฒซ ๋ฒ์งธ ์ด๋ฏธ์ง์ ๋๋ค.</div> | |
| </div> | |
| """ | |
| elif i == 1: # Second image | |
| html_content = """ | |
| <div style="position: absolute; top: 50px; left: 50px; background-color: rgba(255,255,255,0.7); padding: 10px; border-radius: 5px;"> | |
| <div style="color: #333; font-size: 18px; font-weight: bold;">๋ ๋ฒ์งธ ์ด๋ฏธ์ง</div> | |
| <div style="color: #666; margin-top: 5px;">ํ์ด์ง๋ฅผ ๋๊ธฐ๊ฑฐ๋ ๋ชจ์๋ฆฌ๋ฅผ ๋๋๊ทธํ์ฌ ์ด๋ฏธ์ง๋ฅผ ํ์ํ ์ ์์ต๋๋ค.</div> | |
| </div> | |
| """ | |
| # Get relative web paths for the HTML file | |
| rel_image_path = os.path.relpath(dest_path, HTML_DIR).replace("\\", "/") | |
| rel_thumb_path = os.path.relpath(thumb_path, HTML_DIR).replace("\\", "/") | |
| # Create a simpler page structure to avoid potential compatibility issues | |
| page_info = { | |
| "src": rel_image_path, | |
| "thumb": rel_thumb_path, | |
| "title": f"์ด๋ฏธ์ง {i + 1}", | |
| "htmlContent": html_content if html_content else None | |
| } | |
| pages_info.append(page_info) | |
| print(f"Processed image {i+1}: {rel_image_path}") | |
| except Exception as e: | |
| print(f"Error processing image {img_path}: {e}") | |
| return pages_info | |
| def create_flipbook_from_pdf(pdf_file, view_mode="2d", skin="light"): | |
| """Create a flipbook from uploaded PDF.""" | |
| try: | |
| session_id = str(uuid.uuid4()) | |
| pages_info = [] | |
| debug_info = "" | |
| if pdf_file is not None: | |
| # In Gradio, pdf_file is a file path string, not the actual content | |
| pdf_path = pdf_file.name # Get the file path | |
| debug_info += f"PDF path: {pdf_path}\n" | |
| # Process PDF using the file path directly | |
| pages_info = process_pdf(pdf_path, session_id) | |
| debug_info += f"Number of pages processed: {len(pages_info)}\n" | |
| else: | |
| return """<div style="color: red; padding: 20px;">PDF ํ์ผ์ ์ ๋ก๋ํด์ฃผ์ธ์.</div>""", "No file uploaded" | |
| if not pages_info: | |
| return """<div style="color: red; padding: 20px;">PDF ํ์ผ ์ฒ๋ฆฌ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. ๋ค์ ์๋ํด์ฃผ์ธ์.</div>""", "No pages processed" | |
| # Generate HTML file and return iframe HTML | |
| iframe_html = generate_flipbook_html(pages_info, session_id, view_mode, skin) | |
| debug_info += f"HTML file generated with view mode: {view_mode}, skin: {skin}\n" | |
| return iframe_html, debug_info | |
| except Exception as e: | |
| error_msg = f"Error creating flipbook from PDF: {e}" | |
| print(error_msg) | |
| return f"""<div style="color: red; padding: 20px;">์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: {str(e)}</div>""", error_msg | |
| def create_flipbook_from_images(images, view_mode="2d", skin="light"): | |
| """Create a flipbook from uploaded images.""" | |
| try: | |
| session_id = str(uuid.uuid4()) | |
| pages_info = [] | |
| debug_info = "" | |
| if images is not None and len(images) > 0: | |
| # Process images using file paths | |
| image_paths = [img.name for img in images] | |
| debug_info += f"Image paths: {image_paths}\n" | |
| pages_info = process_images(image_paths, session_id) | |
| debug_info += f"Number of images processed: {len(pages_info)}\n" | |
| else: | |
| return """<div style="color: red; padding: 20px;">์ต์ ํ ๊ฐ ์ด์์ ์ด๋ฏธ์ง๋ฅผ ์ ๋ก๋ํด์ฃผ์ธ์.</div>""", "No images uploaded" | |
| if not pages_info: | |
| return """<div style="color: red; padding: 20px;">์ด๋ฏธ์ง ์ฒ๋ฆฌ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. ๋ค์ ์๋ํด์ฃผ์ธ์.</div>""", "No images processed" | |
| # Generate HTML file and return iframe HTML | |
| iframe_html = generate_flipbook_html(pages_info, session_id, view_mode, skin) | |
| debug_info += f"HTML file generated with view mode: {view_mode}, skin: {skin}\n" | |
| return iframe_html, debug_info | |
| except Exception as e: | |
| error_msg = f"Error creating flipbook from images: {e}" | |
| print(error_msg) | |
| return f"""<div style="color: red; padding: 20px;">์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: {str(e)}</div>""", error_msg | |
| def generate_flipbook_html(pages_info, session_id, view_mode, skin): | |
| """Generate a standalone HTML file for the flipbook and return iframe HTML.""" | |
| # Clean up pages_info to remove None values for JSON serialization | |
| for page in pages_info: | |
| if "htmlContent" in page and page["htmlContent"] is None: | |
| del page["htmlContent"] | |
| if "items" in page and page["items"] is None: | |
| del page["items"] | |
| # Convert pages_info to JSON for JavaScript | |
| pages_json = json.dumps(pages_info) | |
| # Create a unique filename for this session | |
| html_filename = f"flipbook_{session_id}.html" | |
| html_path = os.path.join(HTML_DIR, html_filename) | |
| # Create the full HTML file content | |
| html_content = f""" | |
| <!DOCTYPE html> | |
| <html lang="ko"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>3D ํ๋ฆฝ๋ถ</title> | |
| <link rel="stylesheet" type="text/css" href="../../../flipbook.css"> | |
| <style> | |
| body, html {{ | |
| margin: 0; | |
| padding: 0; | |
| height: 100%; | |
| overflow: hidden; | |
| }} | |
| #flipbook-container {{ | |
| width: 100%; | |
| height: 100%; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| }} | |
| .loading {{ | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| text-align: center; | |
| font-family: Arial, sans-serif; | |
| }} | |
| .loading .spinner {{ | |
| width: 50px; | |
| height: 50px; | |
| border: 5px solid #f3f3f3; | |
| border-top: 5px solid #3498db; | |
| border-radius: 50%; | |
| animation: spin 1s linear infinite; | |
| margin: 0 auto 20px; | |
| }} | |
| @keyframes spin {{ | |
| 0% {{ transform: rotate(0deg); }} | |
| 100% {{ transform: rotate(360deg); }} | |
| }} | |
| </style> | |
| <script src="../../../flipbook.js"></script> | |
| <script src="../../../flipbook.webgl.js"></script> | |
| <script src="../../../flipbook.swipe.js"></script> | |
| <script src="../../../flipbook.scroll.js"></script> | |
| <script src="../../../flipbook.book3.js"></script> | |
| </head> | |
| <body> | |
| <div id="flipbook-container"></div> | |
| <div id="loading" class="loading"> | |
| <div class="spinner"></div> | |
| <div>ํ๋ฆฝ๋ถ ๋ก๋ฉ ์ค...</div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() {{ | |
| // Hide loading when everything is ready | |
| function hideLoading() {{ | |
| document.getElementById('loading').style.display = 'none'; | |
| }} | |
| try {{ | |
| const options = {{ | |
| pages: {pages_json}, | |
| viewMode: '{view_mode}', | |
| skin: '{skin}', | |
| responsiveView: true, | |
| singlePageMode: false, | |
| singlePageModeIfMobile: true, | |
| pageFlipDuration: 1, | |
| sound: true, | |
| backgroundMusic: false, | |
| thumbnailsOnStart: true, | |
| btnThumbs: {{ enabled: true }}, | |
| btnPrint: {{ enabled: true }}, | |
| btnDownloadPages: {{ enabled: true }}, | |
| btnDownloadPdf: {{ enabled: true }}, | |
| btnShare: {{ enabled: true }}, | |
| btnSound: {{ enabled: true }}, | |
| btnExpand: {{ enabled: true }}, | |
| rightToLeft: false, | |
| autoplayOnStart: false, | |
| autoplayInterval: 3000 | |
| }}; | |
| const container = document.getElementById('flipbook-container'); | |
| if (container) {{ | |
| console.log('Initializing flipbook...'); | |
| new FlipBook(container, options); | |
| setTimeout(hideLoading, 1000); // Give it time to render | |
| }} else {{ | |
| console.error('Flipbook container not found'); | |
| alert('์ค๋ฅ: ํ๋ฆฝ๋ถ ์ปจํ ์ด๋๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.'); | |
| }} | |
| }} catch (error) {{ | |
| console.error('Error initializing flipbook:', error); | |
| alert('ํ๋ฆฝ๋ถ ์ด๊ธฐํ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: ' + error.message); | |
| document.getElementById('loading').innerHTML = '<div>์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.</div>'; | |
| }} | |
| }}); | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| # Write the HTML file | |
| with open(html_path, 'w', encoding='utf-8') as f: | |
| f.write(html_content) | |
| # Return iframe HTML to embed in Gradio | |
| iframe_height = 700 | |
| iframe_html = f""" | |
| <div style="width:100%; height:{iframe_height}px; border:1px solid #ddd; border-radius:5px; overflow:hidden;"> | |
| <iframe src="file/{html_path}" width="100%" height="100%" frameborder="0" allowfullscreen></iframe> | |
| </div> | |
| <div style="margin-top:15px; padding:15px; background-color:#f5f5f5; border-radius:5px; line-height:1.5;"> | |
| <h3 style="margin-top:0; color:#333;">์ฌ์ฉ ํ:</h3> | |
| <ul style="margin:10px 0; padding-left:20px;"> | |
| <li>ํ์ด์ง ๋ชจ์๋ฆฌ๋ฅผ ๋๋๊ทธํ์ฌ ๋๊ธธ ์ ์์ต๋๋ค.</li> | |
| <li>ํ๋จ ํด๋ฐ์ ์์ด์ฝ์ ์ฌ์ฉํ์ฌ ๋ค์ํ ๊ธฐ๋ฅ์ ํ์ฉํ์ธ์.</li> | |
| <li>์ ์ฒดํ๋ฉด ๋ฒํผ์ ํด๋ฆญํ์ฌ ๋ ํฐ ํ๋ฉด์ผ๋ก ๋ณผ ์ ์์ต๋๋ค.</li> | |
| </ul> | |
| <div style="margin-top:10px; padding:10px; background-color:#e8f4fd; border-left:4px solid #2196F3; border-radius:2px;"> | |
| <strong>์ฐธ๊ณ :</strong> ํ๋ฆฝ๋ถ์ด ๋ณด์ด์ง ์๋ ๊ฒฝ์ฐ <a href="{html_path}" target="_blank">์ฌ๊ธฐ๋ฅผ ํด๋ฆญ</a>ํ์ฌ ์ ์ฐฝ์์ ์ด์ด๋ณด์ธ์. | |
| </div> | |
| </div> | |
| """ | |
| return iframe_html | |
| # Define the Gradio interface | |
| with gr.Blocks(title="3D Flipbook Viewer") as demo: | |
| gr.Markdown("# 3D Flipbook Viewer") | |
| gr.Markdown(""" | |
| ## 3D ํ๋ฆฝ๋ถ ๋ทฐ์ด | |
| PDF ํ์ผ์ด๋ ์ฌ๋ฌ ์ด๋ฏธ์ง๋ฅผ ์ ๋ก๋ํ์ฌ ์ธํฐ๋ํฐ๋ธ 3D ํ๋ฆฝ๋ถ์ ๋ง๋ค ์ ์์ต๋๋ค. | |
| ### ํน์ง: | |
| - ํ์ด์ง ๋๊น ํจ๊ณผ์ ํจ๊ป ์ธํฐ๋ํฐ๋ธํ ๊ธฐ๋ฅ ์ ๊ณต | |
| - ์ฒซ ํ์ด์ง์๋ ์์๋ก ์ธํฐ๋ํฐ๋ธ ์์๊ฐ ํฌํจ๋จ | |
| - ํด๋ฐ๋ฅผ ์ฌ์ฉํ๊ฑฐ๋ ํ์ด์ง ๋ชจ์๋ฆฌ๋ฅผ ๋๋๊ทธํ์ฌ ํ์ | |
| - ์ธ๋ค์ผ ๋ณด๊ธฐ๋ก ๋น ๋ฅธ ํ์ ๊ฐ๋ฅ | |
| - ์ ์ฒด ํ๋ฉด์ผ๋ก ์ ํํ์ฌ ๋ ๋์ ๋ณด๊ธฐ ๊ฒฝํ | |
| """) | |
| with gr.Tabs(): | |
| with gr.TabItem("PDF ์ ๋ก๋"): | |
| pdf_file = gr.File(label="PDF ํ์ผ ์ ๋ก๋", file_types=[".pdf"]) | |
| with gr.Accordion("๊ณ ๊ธ ์ค์ ", open=False): | |
| pdf_view_mode = gr.Radio( | |
| choices=["webgl", "3d", "2d", "swipe"], | |
| value="2d", # Changed default to 2d for better compatibility | |
| label="๋ทฐ ๋ชจ๋", | |
| info="WebGL: ์ต๊ณ ํ์ง, 2D: ๊ฐ์ฅ ์์ ์ , 3D: ์ค๊ฐ, Swipe: ๋ชจ๋ฐ์ผ์ฉ" | |
| ) | |
| pdf_skin = gr.Radio( | |
| choices=["light", "dark", "gradient"], | |
| value="light", | |
| label="์คํจ", | |
| info="light: ๋ฐ์ ํ ๋ง, dark: ์ด๋์ด ํ ๋ง, gradient: ๊ทธ๋ผ๋ฐ์ด์ ํ ๋ง" | |
| ) | |
| pdf_create_btn = gr.Button("PDF์์ ํ๋ฆฝ๋ถ ๋ง๋ค๊ธฐ", variant="primary", size="lg") | |
| pdf_debug = gr.Textbox(label="๋๋ฒ๊ทธ ์ ๋ณด", visible=False) | |
| pdf_output = gr.HTML(label="ํ๋ฆฝ๋ถ ๊ฒฐ๊ณผ๋ฌผ") | |
| # Set up PDF event handler | |
| pdf_create_btn.click( | |
| fn=create_flipbook_from_pdf, | |
| inputs=[pdf_file, pdf_view_mode, pdf_skin], | |
| outputs=[pdf_output, pdf_debug] | |
| ) | |
| with gr.TabItem("์ด๋ฏธ์ง ์ ๋ก๋"): | |
| images = gr.File(label="์ด๋ฏธ์ง ํ์ผ ์ ๋ก๋", file_types=["image"], file_count="multiple") | |
| with gr.Accordion("๊ณ ๊ธ ์ค์ ", open=False): | |
| img_view_mode = gr.Radio( | |
| choices=["webgl", "3d", "2d", "swipe"], | |
| value="2d", # Changed default to 2d for better compatibility | |
| label="๋ทฐ ๋ชจ๋", | |
| info="WebGL: ์ต๊ณ ํ์ง, 2D: ๊ฐ์ฅ ์์ ์ , 3D: ์ค๊ฐ, Swipe: ๋ชจ๋ฐ์ผ์ฉ" | |
| ) | |
| img_skin = gr.Radio( | |
| choices=["light", "dark", "gradient"], | |
| value="light", | |
| label="์คํจ", | |
| info="light: ๋ฐ์ ํ ๋ง, dark: ์ด๋์ด ํ ๋ง, gradient: ๊ทธ๋ผ๋ฐ์ด์ ํ ๋ง" | |
| ) | |
| img_create_btn = gr.Button("์ด๋ฏธ์ง์์ ํ๋ฆฝ๋ถ ๋ง๋ค๊ธฐ", variant="primary", size="lg") | |
| img_debug = gr.Textbox(label="๋๋ฒ๊ทธ ์ ๋ณด", visible=False) | |
| img_output = gr.HTML(label="ํ๋ฆฝ๋ถ ๊ฒฐ๊ณผ๋ฌผ") | |
| # Set up image event handler | |
| img_create_btn.click( | |
| fn=create_flipbook_from_images, | |
| inputs=[images, img_view_mode, img_skin], | |
| outputs=[img_output, img_debug] | |
| ) | |
| gr.Markdown(""" | |
| ### ์ฌ์ฉ๋ฒ: | |
| 1. ์ปจํ ์ธ ์ ํ์ ๋ฐ๋ผ ํญ์ ์ ํํ์ธ์ (PDF ๋๋ ์ด๋ฏธ์ง) | |
| 2. ํ์ผ์ ์ ๋ก๋ํ์ธ์ | |
| 3. ํ์์ ๋ฐ๋ผ ๊ณ ๊ธ ์ค์ ์์ ๋ทฐ ๋ชจ๋์ ์คํจ์ ์กฐ์ ํ์ธ์ | |
| 4. ํ๋ฆฝ๋ถ ๋ง๋ค๊ธฐ ๋ฒํผ์ ํด๋ฆญํ์ธ์ | |
| 5. ์ถ๋ ฅ ์์ญ์์ ํ๋ฆฝ๋ถ๊ณผ ์ํธ์์ฉํ์ธ์ | |
| ### ์ฐธ๊ณ : | |
| - ์ฒ์ ํ์ด์ง์๋ ์์๋ก ์ธํฐ๋ํฐ๋ธ ์์์ ๋งํฌ๊ฐ ํฌํจ๋์ด ์์ต๋๋ค | |
| - ์ต์์ ๊ฒฐ๊ณผ๋ฅผ ์ํด ์ ๋ช ํ ํ ์คํธ์ ์ด๋ฏธ์ง๊ฐ ์๋ PDF๋ฅผ ์ฌ์ฉํ์ธ์ | |
| - ์ง์๋๋ ์ด๋ฏธ์ง ํ์: JPG, PNG, GIF ๋ฑ | |
| - ํ๋ฆฝ๋ถ์ด ๋ณด์ด์ง ์๋ ๊ฒฝ์ฐ, 2D ๋ชจ๋๋ฅผ ์ ํํ๊ณ ๋ค์ ์๋ํด๋ณด์ธ์ | |
| """) | |
| # Launch the app | |
| if __name__ == "__main__": | |
| demo.launch(share=True) # Set share=True to create a public link |