Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
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 |