Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
import os | |
import gradio as gr | |
import shutil | |
import uuid | |
from pathlib import Path | |
import json | |
import logging | |
import traceback | |
from PIL import Image | |
import fitz # PyMuPDF for PDF handling | |
# ββββββββββββββββββββββββββββββββ | |
# Logging μ€μ | |
# ββββββββββββββββββββββββββββββββ | |
logging.basicConfig( | |
level=logging.INFO, | |
format="%(asctime)s [%(levelname)s] %(message)s", | |
filename="app.log", # μ€ν λλ ν°λ¦¬μ app.log νμΌ μ μ₯ | |
filemode="a", | |
) | |
logging.info("π Flipbook app started") | |
# 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("public", "flipbooks") # Directory accessible via web | |
# 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: | |
logging.error("Error creating thumbnail: %s", 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: | |
pdf_document = fitz.open(pdf_path) | |
for page_num, page in enumerate(pdf_document): | |
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) | |
thumb_path = os.path.join(thumbs_folder, f"thumb_{page_num + 1}.png") | |
create_thumbnail(image_path, thumb_path) | |
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> | |
""" if page_num == 0 else None | |
pages_info.append({ | |
"src": f"./temp/output/{session_id}/page_{page_num + 1}.png", | |
"thumb": f"./temp/output/thumbs/{session_id}/thumb_{page_num + 1}.png", | |
"title": f"νμ΄μ§ {page_num + 1}", | |
"htmlContent": html_content, | |
}) | |
logging.info("Processed PDF page %d: %s", page_num + 1, image_path) | |
return pages_info | |
except Exception as e: | |
logging.error("Error processing PDF: %s", 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: | |
dest_path = os.path.join(output_folder, f"image_{i + 1}.png") | |
shutil.copy(img_path, dest_path) | |
thumb_path = os.path.join(thumbs_folder, f"thumb_{i + 1}.png") | |
create_thumbnail(img_path, thumb_path) | |
if i == 0: | |
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: | |
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> | |
""" | |
else: | |
html_content = None | |
pages_info.append({ | |
"src": f"./temp/output/{session_id}/image_{i + 1}.png", | |
"thumb": f"./temp/output/thumbs/{session_id}/thumb_{i + 1}.png", | |
"title": f"μ΄λ―Έμ§ {i + 1}", | |
"htmlContent": html_content, | |
}) | |
logging.info("Processed image %d: %s", i + 1, dest_path) | |
except Exception as e: | |
logging.error("Error processing image %s: %s", img_path, e) | |
return pages_info | |
def create_flipbook_from_pdf(pdf_file, view_mode="2d", skin="light"): | |
"""Create a flipbook from an uploaded PDF.""" | |
session_id = str(uuid.uuid4()) | |
debug_info = [] | |
if not pdf_file: | |
return ( | |
"<div style='color:red;padding:20px;'>PDF νμΌμ μ λ‘λν΄μ£ΌμΈμ.</div>", | |
"No file uploaded", | |
) | |
try: | |
pdf_path = pdf_file.name | |
debug_info.append(f"PDF path: {pdf_path}") | |
# 1) PDF β μ΄λ―Έμ§ | |
pages_info = process_pdf(pdf_path, session_id) | |
debug_info.append(f"Number of pages: {len(pages_info)}") | |
if not pages_info: | |
raise RuntimeError("PDF μ²λ¦¬ κ²°κ³Όκ° λΉμ΄ μμ΅λλ€.") | |
# 2) HTML μμ± | |
iframe_html = generate_flipbook_html(pages_info, session_id, view_mode, skin) | |
return iframe_html, "\n".join(debug_info) | |
except Exception as e: | |
tb = traceback.format_exc() | |
logging.error(tb) | |
debug_info.append("β ERROR βββ") | |
debug_info.append(tb) | |
return ( | |
f"<div style='color:red;padding:20px;'>μ€λ₯κ° λ°μνμ΅λλ€: {e}</div>", | |
"\n".join(debug_info), | |
) | |
def create_flipbook_from_images(images, view_mode="2d", skin="light"): | |
"""Create a flipbook from uploaded images.""" | |
session_id = str(uuid.uuid4()) | |
debug_info = [] | |
if not images: | |
return ( | |
"<div style='color:red;padding:20px;'>μ΅μ ν κ° μ΄μμ μ΄λ―Έμ§λ₯Ό μ λ‘λν΄μ£ΌμΈμ.</div>", | |
"No images uploaded", | |
) | |
try: | |
image_paths = [img.name for img in images] | |
debug_info.append(f"Image paths: {image_paths}") | |
pages_info = process_images(image_paths, session_id) | |
debug_info.append(f"Number of images processed: {len(pages_info)}") | |
if not pages_info: | |
raise RuntimeError("μ΄λ―Έμ§ μ²λ¦¬ κ²°κ³Όκ° λΉμ΄ μμ΅λλ€.") | |
iframe_html = generate_flipbook_html(pages_info, session_id, view_mode, skin) | |
return iframe_html, "\n".join(debug_info) | |
except Exception as e: | |
tb = traceback.format_exc() | |
logging.error(tb) | |
debug_info.append("β ERROR βββ") | |
debug_info.append(tb) | |
return ( | |
f"<div style='color:red;padding:20px;'>μ€λ₯κ° λ°μνμ΅λλ€: {e}</div>", | |
"\n".join(debug_info), | |
) | |
def generate_flipbook_html(pages_info, session_id, view_mode, skin): | |
"""Generate a standalone HTML file for the flipbook and return link HTML.""" | |
for page in pages_info: | |
if page.get("htmlContent") is None: | |
page.pop("htmlContent", None) | |
if page.get("items") is None: | |
page.pop("items", None) | |
pages_json = json.dumps(pages_info) | |
html_filename = f"flipbook_{session_id}.html" | |
html_path = os.path.join(HTML_DIR, html_filename) | |
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 () {{ | |
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) {{ | |
new FlipBook(container, options); | |
setTimeout(hideLoading, 1000); | |
}} 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> | |
""" | |
with open(html_path, "w", encoding="utf-8") as f: | |
f.write(html_content) | |
public_url = f"/public/flipbooks/{html_filename}" | |
link_html = f""" | |
<div style=\"text-align:center; padding:20px; background-color:#f9f9f9; border-radius:5px; margin-bottom:20px;\"> | |
<h2 style=\"margin-top:0; color:#333;\">ν립λΆμ΄ μ€λΉλμμ΅λλ€!</h2> | |
<p style=\"margin-bottom:20px;\">μλ λ²νΌμ ν΄λ¦νμ¬ ν립λΆμ μ μ°½μμ μ΄μ΄λ³΄μΈμ.</p> | |
<a href=\"{public_url}\" target=\"_blank\" style=\"display:inline-block; background-color:#4CAF50; color:white; padding | |
<div style="text-align:center; padding:20px; background-color:#f9f9f9; border-radius:5px; margin-bottom:20px;"> | |
<h2 style="margin-top:0; color:#333;">ν립λΆμ΄ μ€λΉλμμ΅λλ€!</h2> | |
<p style="margin-bottom:20px;">μλ λ²νΌμ ν΄λ¦νμ¬ ν립λΆμ μ μ°½μμ μ΄μ΄λ³΄μΈμ.</p> | |
<a href="{public_url}" target="_blank" style="display:inline-block; background-color:#4CAF50; color:white; padding:12px 24px; text-decoration:none; border-radius:4px; font-weight:bold; font-size:16px;">νλ¦½λΆ μ΄κΈ°</a> | |
</div> | |
<div style="margin-top:20px; 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> ν립λΆμ 2D λͺ¨λμμ κ°μ₯ μμ μ μΌλ‘ μλν©λλ€. | |
</div> | |
</div> | |
<div style="margin-top:15px; background-color:#f5f5f5; border-radius:5px; padding:10px;"> | |
<details> | |
<summary style="cursor:pointer; color:#2196F3; font-weight:bold;">κΈ°μ μ μΈλΆμ¬ν (κ°λ°μμ©)</summary> | |
<div style="margin-top:10px;"> | |
<p>μΈμ ID: {session_id}</p> | |
<p>HTML νμΌ κ²½λ‘: {html_path}</p> | |
<p>νμ΄μ§ μ: {len(pages_info)}</p> | |
<p>λ·° λͺ¨λ: {view_mode}</p> | |
<p>μ€ν¨: {skin}</p> | |
</div> | |
</details> | |
</div> | |
""" | |
return link_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() # Remove share=True as it's not supported in Spaces |