ginipick commited on
Commit
898cc4c
Β·
verified Β·
1 Parent(s): 79a7cbc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +129 -162
app.py CHANGED
@@ -4,9 +4,22 @@ import shutil
4
  import uuid
5
  from pathlib import Path
6
  import json
 
 
7
  from PIL import Image
8
  import fitz # PyMuPDF for PDF handling
9
 
 
 
 
 
 
 
 
 
 
 
 
10
  # Constants
11
  TEMP_DIR = "temp"
12
  UPLOAD_DIR = os.path.join(TEMP_DIR, "uploads")
@@ -18,6 +31,7 @@ HTML_DIR = os.path.join("public", "flipbooks") # Directory accessible via web
18
  for dir_path in [TEMP_DIR, UPLOAD_DIR, OUTPUT_DIR, THUMBS_DIR, HTML_DIR]:
19
  os.makedirs(dir_path, exist_ok=True)
20
 
 
21
  def create_thumbnail(image_path, output_path, size=(300, 300)):
22
  """Create a thumbnail from an image."""
23
  try:
@@ -26,113 +40,103 @@ def create_thumbnail(image_path, output_path, size=(300, 300)):
26
  img.save(output_path)
27
  return output_path
28
  except Exception as e:
29
- print(f"Error creating thumbnail: {e}")
30
  return None
31
 
 
32
  def process_pdf(pdf_path, session_id):
33
  """Extract pages from a PDF and save as images with thumbnails."""
34
  pages_info = []
35
  output_folder = os.path.join(OUTPUT_DIR, session_id)
36
  thumbs_folder = os.path.join(THUMBS_DIR, session_id)
37
-
38
  os.makedirs(output_folder, exist_ok=True)
39
  os.makedirs(thumbs_folder, exist_ok=True)
40
-
41
  try:
42
- # Open the PDF
43
  pdf_document = fitz.open(pdf_path)
44
-
45
- # Process each page
46
  for page_num, page in enumerate(pdf_document):
47
- # Render page to an image with a higher resolution
48
  pix = page.get_pixmap(matrix=fitz.Matrix(2, 2))
49
  image_path = os.path.join(output_folder, f"page_{page_num + 1}.png")
50
  pix.save(image_path)
51
-
52
- # Create thumbnail
53
  thumb_path = os.path.join(thumbs_folder, f"thumb_{page_num + 1}.png")
54
  create_thumbnail(image_path, thumb_path)
55
-
56
- # Add simple interactive content to first page
57
- html_content = ""
58
-
59
- if page_num == 0: # First page example
60
- html_content = """
61
  <div style="position: absolute; top: 50px; left: 50px; background-color: rgba(255,255,255,0.7); padding: 10px; border-radius: 5px;">
62
  <div style="color: #333; font-size: 18px; font-weight: bold;">μΈν„°λž™ν‹°λΈŒ ν”Œλ¦½λΆ 예제</div>
63
  <div style="color: #666; margin-top: 5px;">이 νŽ˜μ΄μ§€λŠ” μΈν„°λž™ν‹°λΈŒ 컨텐츠 κΈ°λŠ₯을 λ³΄μ—¬μ€λ‹ˆλ‹€.</div>
64
  </div>
65
- """
66
-
67
- # Add page info with web-accessible paths
68
  pages_info.append({
69
  "src": f"./temp/output/{session_id}/page_{page_num + 1}.png",
70
  "thumb": f"./temp/output/thumbs/{session_id}/thumb_{page_num + 1}.png",
71
  "title": f"νŽ˜μ΄μ§€ {page_num + 1}",
72
- "htmlContent": html_content if html_content else None
73
  })
74
- print(f"Processed PDF page {page_num+1}: {image_path}")
75
-
76
  return pages_info
77
  except Exception as e:
78
- print(f"Error processing PDF: {e}")
79
  return []
80
 
 
81
  def process_images(image_paths, session_id):
82
  """Process uploaded images and create thumbnails."""
83
  pages_info = []
84
  output_folder = os.path.join(OUTPUT_DIR, session_id)
85
  thumbs_folder = os.path.join(THUMBS_DIR, session_id)
86
-
87
  os.makedirs(output_folder, exist_ok=True)
88
  os.makedirs(thumbs_folder, exist_ok=True)
89
-
90
  for i, img_path in enumerate(image_paths):
91
  try:
92
- # Copy original image to output folder
93
  dest_path = os.path.join(output_folder, f"image_{i + 1}.png")
94
  shutil.copy(img_path, dest_path)
95
-
96
- # Create thumbnail
97
  thumb_path = os.path.join(thumbs_folder, f"thumb_{i + 1}.png")
98
  create_thumbnail(img_path, thumb_path)
99
-
100
- # Add interactive content as simple text overlays to avoid compatibility issues
101
- html_content = ""
102
-
103
- if i == 0: # First image example with HTML content
104
  html_content = """
105
- <div style="position: absolute; top: 50px; left: 50px; background-color: rgba(255,255,255,0.7); padding: 10px; border-radius: 5px;">
106
- <div style="color: #333; font-size: 18px; font-weight: bold;">이미지 가러리</div>
107
- <div style="color: #666; margin-top: 5px;">가러리의 첫 번째 μ΄λ―Έμ§€μž…λ‹ˆλ‹€.</div>
108
- </div>
109
  """
110
- elif i == 1: # Second image
111
  html_content = """
112
- <div style="position: absolute; top: 50px; left: 50px; background-color: rgba(255,255,255,0.7); padding: 10px; border-radius: 5px;">
113
- <div style="color: #333; font-size: 18px; font-weight: bold;">두 번째 이미지</div>
114
- <div style="color: #666; margin-top: 5px;">νŽ˜μ΄μ§€λ₯Ό λ„˜κΈ°κ±°λ‚˜ λͺ¨μ„œλ¦¬λ₯Ό λ“œλž˜κ·Έν•˜μ—¬ 이미지λ₯Ό 탐색할 수 μžˆμŠ΅λ‹ˆλ‹€.</div>
115
- </div>
116
  """
117
-
118
- # Add page info with web-accessible paths
 
119
  pages_info.append({
120
  "src": f"./temp/output/{session_id}/image_{i + 1}.png",
121
  "thumb": f"./temp/output/thumbs/{session_id}/thumb_{i + 1}.png",
122
  "title": f"이미지 {i + 1}",
123
- "htmlContent": html_content if html_content else None
124
  })
125
- print(f"Processed image {i+1}: {dest_path}")
126
-
127
  except Exception as e:
128
- print(f"Error processing image {img_path}: {e}")
129
-
130
  return pages_info
131
 
 
132
  def create_flipbook_from_pdf(pdf_file, view_mode="2d", skin="light"):
133
  """Create a flipbook from an uploaded PDF."""
134
  session_id = str(uuid.uuid4())
135
- debug_info = ""
136
 
137
  if not pdf_file:
138
  return (
@@ -141,146 +145,108 @@ def create_flipbook_from_pdf(pdf_file, view_mode="2d", skin="light"):
141
  )
142
 
143
  try:
144
- pdf_path = pdf_file.name # Gradio File 객체의 μ‹€μ œ 경둜
145
- debug_info += f"PDF path: {pdf_path}\n"
146
 
147
- # 1) PDF νŽ˜μ΄μ§€λ₯Ό μ΄λ―Έμ§€λ‘œ λ³€ν™˜
148
  pages_info = process_pdf(pdf_path, session_id)
149
- debug_info += f"Number of pages: {len(pages_info)}\n"
150
 
151
  if not pages_info:
152
- return (
153
- "<div style='color:red;padding:20px;'>PDF 처리 μ‹€νŒ¨.</div>",
154
- "No pages processed",
155
- )
156
 
157
- # 2) 이미지 리슀트둜 ν”Œλ¦½λΆ HTML 생성
158
- iframe_html = generate_flipbook_html(
159
- pages_info, session_id, view_mode, skin
160
- )
161
- return iframe_html, debug_info
162
 
163
  except Exception as e:
164
- error_msg = f"Error creating flipbook from PDF: {e}"
165
- print(error_msg)
 
 
166
  return (
167
  f"<div style='color:red;padding:20px;'>였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€: {e}</div>",
168
- error_msg,
169
  )
170
 
171
 
172
  def create_flipbook_from_images(images, view_mode="2d", skin="light"):
173
  """Create a flipbook from uploaded images."""
 
 
 
 
 
 
 
 
 
174
  try:
175
- session_id = str(uuid.uuid4())
176
- pages_info = []
177
- debug_info = ""
178
-
179
- if images is not None and len(images) > 0:
180
- # Process images using file paths
181
- image_paths = [img.name for img in images]
182
- debug_info += f"Image paths: {image_paths}\n"
183
-
184
- pages_info = process_images(image_paths, session_id)
185
- debug_info += f"Number of images processed: {len(pages_info)}\n"
186
- else:
187
- return """<div style="color: red; padding: 20px;">μ΅œμ†Œ ν•œ 개 μ΄μƒμ˜ 이미지λ₯Ό μ—…λ‘œλ“œν•΄μ£Όμ„Έμš”.</div>""", "No images uploaded"
188
-
189
  if not pages_info:
190
- return """<div style="color: red; padding: 20px;">이미지 처리 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€. λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”.</div>""", "No images processed"
191
-
192
- # Generate HTML file and return iframe HTML
193
  iframe_html = generate_flipbook_html(pages_info, session_id, view_mode, skin)
194
- debug_info += f"HTML file generated with view mode: {view_mode}, skin: {skin}\n"
195
-
196
- return iframe_html, debug_info
197
-
198
  except Exception as e:
199
- error_msg = f"Error creating flipbook from images: {e}"
200
- print(error_msg)
201
- return f"""<div style="color: red; padding: 20px;">였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€: {str(e)}</div>""", error_msg
 
 
 
 
 
 
202
 
203
  def generate_flipbook_html(pages_info, session_id, view_mode, skin):
204
  """Generate a standalone HTML file for the flipbook and return link HTML."""
205
- # Clean up pages_info to remove None values for JSON serialization
206
  for page in pages_info:
207
- if "htmlContent" in page and page["htmlContent"] is None:
208
- del page["htmlContent"]
209
- if "items" in page and page["items"] is None:
210
- del page["items"]
211
-
212
- # Convert pages_info to JSON for JavaScript
213
  pages_json = json.dumps(pages_info)
214
-
215
- # Create a unique filename for this session
216
  html_filename = f"flipbook_{session_id}.html"
217
  html_path = os.path.join(HTML_DIR, html_filename)
218
-
219
- # Create the full HTML file content
220
  html_content = f"""
221
  <!DOCTYPE html>
222
- <html lang="ko">
223
  <head>
224
- <meta charset="UTF-8">
225
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
226
  <title>3D ν”Œλ¦½λΆ</title>
227
- <link rel="stylesheet" type="text/css" href="../flipbook.css">
228
  <style>
229
- body, html {{
230
- margin: 0;
231
- padding: 0;
232
- height: 100%;
233
- overflow: hidden;
234
- }}
235
- #flipbook-container {{
236
- width: 100%;
237
- height: 100%;
238
- position: absolute;
239
- top: 0;
240
- left: 0;
241
- }}
242
- .loading {{
243
- position: absolute;
244
- top: 50%;
245
- left: 50%;
246
- transform: translate(-50%, -50%);
247
- text-align: center;
248
- font-family: Arial, sans-serif;
249
- }}
250
- .loading .spinner {{
251
- width: 50px;
252
- height: 50px;
253
- border: 5px solid #f3f3f3;
254
- border-top: 5px solid #3498db;
255
- border-radius: 50%;
256
- animation: spin 1s linear infinite;
257
- margin: 0 auto 20px;
258
- }}
259
- @keyframes spin {{
260
- 0% {{ transform: rotate(0deg); }}
261
- 100% {{ transform: rotate(360deg); }}
262
- }}
263
  </style>
264
- <script src="../flipbook.js"></script>
265
- <script src="../flipbook.webgl.js"></script>
266
- <script src="../flipbook.swipe.js"></script>
267
- <script src="../flipbook.scroll.js"></script>
268
- <script src="../flipbook.book3.js"></script>
269
  </head>
270
  <body>
271
- <div id="flipbook-container"></div>
272
- <div id="loading" class="loading">
273
- <div class="spinner"></div>
274
  <div>ν”Œλ¦½λΆ λ‘œλ”© 쀑...</div>
275
  </div>
276
-
277
  <script>
278
- document.addEventListener('DOMContentLoaded', function() {{
279
- // Hide loading when everything is ready
280
- function hideLoading() {{
281
- document.getElementById('loading').style.display = 'none';
282
- }}
283
-
284
  try {{
285
  const options = {{
286
  pages: {pages_json},
@@ -302,14 +268,12 @@ def generate_flipbook_html(pages_info, session_id, view_mode, skin):
302
  btnExpand: {{ enabled: true }},
303
  rightToLeft: false,
304
  autoplayOnStart: false,
305
- autoplayInterval: 3000
306
  }};
307
-
308
  const container = document.getElementById('flipbook-container');
309
  if (container) {{
310
- console.log('Initializing flipbook...');
311
  new FlipBook(container, options);
312
- setTimeout(hideLoading, 1000); // Give it time to render
313
  }} else {{
314
  console.error('Flipbook container not found');
315
  alert('였λ₯˜: ν”Œλ¦½λΆ μ»¨ν…Œμ΄λ„ˆλ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.');
@@ -324,14 +288,17 @@ def generate_flipbook_html(pages_info, session_id, view_mode, skin):
324
  </body>
325
  </html>
326
  """
327
-
328
- # Write the HTML file
329
- with open(html_path, 'w', encoding='utf-8') as f:
330
  f.write(html_content)
331
-
332
- # Return HTML with a direct link to open the flipbook in a new tab
333
  public_url = f"/public/flipbooks/{html_filename}"
334
  link_html = f"""
 
 
 
 
 
335
  <div style="text-align:center; padding:20px; background-color:#f9f9f9; border-radius:5px; margin-bottom:20px;">
336
  <h2 style="margin-top:0; color:#333;">ν”Œλ¦½λΆμ΄ μ€€λΉ„λ˜μ—ˆμŠ΅λ‹ˆλ‹€!</h2>
337
  <p style="margin-bottom:20px;">μ•„λž˜ λ²„νŠΌμ„ ν΄λ¦­ν•˜μ—¬ ν”Œλ¦½λΆμ„ μƒˆ μ°½μ—μ„œ μ—΄μ–΄λ³΄μ„Έμš”.</p>
 
4
  import uuid
5
  from pathlib import Path
6
  import json
7
+ import logging
8
+ import traceback
9
  from PIL import Image
10
  import fitz # PyMuPDF for PDF handling
11
 
12
+ # ────────────────────────────────
13
+ # Logging μ„€μ •
14
+ # ────────────────────────────────
15
+ logging.basicConfig(
16
+ level=logging.INFO,
17
+ format="%(asctime)s [%(levelname)s] %(message)s",
18
+ filename="app.log", # μ‹€ν–‰ 디렉터리에 app.log 파일 μ €μž₯
19
+ filemode="a",
20
+ )
21
+ logging.info("πŸš€ Flipbook app started")
22
+
23
  # Constants
24
  TEMP_DIR = "temp"
25
  UPLOAD_DIR = os.path.join(TEMP_DIR, "uploads")
 
31
  for dir_path in [TEMP_DIR, UPLOAD_DIR, OUTPUT_DIR, THUMBS_DIR, HTML_DIR]:
32
  os.makedirs(dir_path, exist_ok=True)
33
 
34
+
35
  def create_thumbnail(image_path, output_path, size=(300, 300)):
36
  """Create a thumbnail from an image."""
37
  try:
 
40
  img.save(output_path)
41
  return output_path
42
  except Exception as e:
43
+ logging.error("Error creating thumbnail: %s", e)
44
  return None
45
 
46
+
47
  def process_pdf(pdf_path, session_id):
48
  """Extract pages from a PDF and save as images with thumbnails."""
49
  pages_info = []
50
  output_folder = os.path.join(OUTPUT_DIR, session_id)
51
  thumbs_folder = os.path.join(THUMBS_DIR, session_id)
52
+
53
  os.makedirs(output_folder, exist_ok=True)
54
  os.makedirs(thumbs_folder, exist_ok=True)
55
+
56
  try:
 
57
  pdf_document = fitz.open(pdf_path)
58
+
 
59
  for page_num, page in enumerate(pdf_document):
 
60
  pix = page.get_pixmap(matrix=fitz.Matrix(2, 2))
61
  image_path = os.path.join(output_folder, f"page_{page_num + 1}.png")
62
  pix.save(image_path)
63
+
 
64
  thumb_path = os.path.join(thumbs_folder, f"thumb_{page_num + 1}.png")
65
  create_thumbnail(image_path, thumb_path)
66
+
67
+ html_content = """
 
 
 
 
68
  <div style="position: absolute; top: 50px; left: 50px; background-color: rgba(255,255,255,0.7); padding: 10px; border-radius: 5px;">
69
  <div style="color: #333; font-size: 18px; font-weight: bold;">μΈν„°λž™ν‹°λΈŒ ν”Œλ¦½λΆ 예제</div>
70
  <div style="color: #666; margin-top: 5px;">이 νŽ˜μ΄μ§€λŠ” μΈν„°λž™ν‹°λΈŒ 컨텐츠 κΈ°λŠ₯을 λ³΄μ—¬μ€λ‹ˆλ‹€.</div>
71
  </div>
72
+ """ if page_num == 0 else None
73
+
 
74
  pages_info.append({
75
  "src": f"./temp/output/{session_id}/page_{page_num + 1}.png",
76
  "thumb": f"./temp/output/thumbs/{session_id}/thumb_{page_num + 1}.png",
77
  "title": f"νŽ˜μ΄μ§€ {page_num + 1}",
78
+ "htmlContent": html_content,
79
  })
80
+ logging.info("Processed PDF page %d: %s", page_num + 1, image_path)
81
+
82
  return pages_info
83
  except Exception as e:
84
+ logging.error("Error processing PDF: %s", e)
85
  return []
86
 
87
+
88
  def process_images(image_paths, session_id):
89
  """Process uploaded images and create thumbnails."""
90
  pages_info = []
91
  output_folder = os.path.join(OUTPUT_DIR, session_id)
92
  thumbs_folder = os.path.join(THUMBS_DIR, session_id)
93
+
94
  os.makedirs(output_folder, exist_ok=True)
95
  os.makedirs(thumbs_folder, exist_ok=True)
96
+
97
  for i, img_path in enumerate(image_paths):
98
  try:
 
99
  dest_path = os.path.join(output_folder, f"image_{i + 1}.png")
100
  shutil.copy(img_path, dest_path)
101
+
 
102
  thumb_path = os.path.join(thumbs_folder, f"thumb_{i + 1}.png")
103
  create_thumbnail(img_path, thumb_path)
104
+
105
+ if i == 0:
 
 
 
106
  html_content = """
107
+ <div style=\"position: absolute; top: 50px; left: 50px; background-color: rgba(255,255,255,0.7); padding: 10px; border-radius: 5px;\">
108
+ <div style=\"color: #333; font-size: 18px; font-weight: bold;\">이미지 가러리</div>
109
+ <div style=\"color: #666; margin-top: 5px;\">가러리의 첫 번째 μ΄λ―Έμ§€μž…λ‹ˆλ‹€.</div>
110
+ </div>
111
  """
112
+ elif i == 1:
113
  html_content = """
114
+ <div style=\"position: absolute; top: 50px; left: 50px; background-color: rgba(255,255,255,0.7); padding: 10px; border-radius: 5px;\">
115
+ <div style=\"color: #333; font-size: 18px; font-weight: bold;\">두 번째 이미지</div>
116
+ <div style=\"color: #666; margin-top: 5px;\">νŽ˜μ΄μ§€λ₯Ό λ„˜κΈ°κ±°λ‚˜ λͺ¨μ„œλ¦¬λ₯Ό λ“œλž˜κ·Έν•˜μ—¬ 이미지λ₯Ό 탐색할 수 μžˆμŠ΅λ‹ˆλ‹€.</div>
117
+ </div>
118
  """
119
+ else:
120
+ html_content = None
121
+
122
  pages_info.append({
123
  "src": f"./temp/output/{session_id}/image_{i + 1}.png",
124
  "thumb": f"./temp/output/thumbs/{session_id}/thumb_{i + 1}.png",
125
  "title": f"이미지 {i + 1}",
126
+ "htmlContent": html_content,
127
  })
128
+ logging.info("Processed image %d: %s", i + 1, dest_path)
129
+
130
  except Exception as e:
131
+ logging.error("Error processing image %s: %s", img_path, e)
132
+
133
  return pages_info
134
 
135
+
136
  def create_flipbook_from_pdf(pdf_file, view_mode="2d", skin="light"):
137
  """Create a flipbook from an uploaded PDF."""
138
  session_id = str(uuid.uuid4())
139
+ debug_info = []
140
 
141
  if not pdf_file:
142
  return (
 
145
  )
146
 
147
  try:
148
+ pdf_path = pdf_file.name
149
+ debug_info.append(f"PDF path: {pdf_path}")
150
 
151
+ # 1) PDF β†’ 이미지
152
  pages_info = process_pdf(pdf_path, session_id)
153
+ debug_info.append(f"Number of pages: {len(pages_info)}")
154
 
155
  if not pages_info:
156
+ raise RuntimeError("PDF 처리 κ²°κ³Όκ°€ λΉ„μ–΄ μžˆμŠ΅λ‹ˆλ‹€.")
 
 
 
157
 
158
+ # 2) HTML 생성
159
+ iframe_html = generate_flipbook_html(pages_info, session_id, view_mode, skin)
160
+ return iframe_html, "\n".join(debug_info)
 
 
161
 
162
  except Exception as e:
163
+ tb = traceback.format_exc()
164
+ logging.error(tb)
165
+ debug_info.append("❌ ERROR ↓↓↓")
166
+ debug_info.append(tb)
167
  return (
168
  f"<div style='color:red;padding:20px;'>였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€: {e}</div>",
169
+ "\n".join(debug_info),
170
  )
171
 
172
 
173
  def create_flipbook_from_images(images, view_mode="2d", skin="light"):
174
  """Create a flipbook from uploaded images."""
175
+ session_id = str(uuid.uuid4())
176
+ debug_info = []
177
+
178
+ if not images:
179
+ return (
180
+ "<div style='color:red;padding:20px;'>μ΅œμ†Œ ν•œ 개 μ΄μƒμ˜ 이미지λ₯Ό μ—…λ‘œλ“œν•΄μ£Όμ„Έμš”.</div>",
181
+ "No images uploaded",
182
+ )
183
+
184
  try:
185
+ image_paths = [img.name for img in images]
186
+ debug_info.append(f"Image paths: {image_paths}")
187
+
188
+ pages_info = process_images(image_paths, session_id)
189
+ debug_info.append(f"Number of images processed: {len(pages_info)}")
190
+
 
 
 
 
 
 
 
 
191
  if not pages_info:
192
+ raise RuntimeError("이미지 처리 κ²°κ³Όκ°€ λΉ„μ–΄ μžˆμŠ΅λ‹ˆλ‹€.")
193
+
 
194
  iframe_html = generate_flipbook_html(pages_info, session_id, view_mode, skin)
195
+ return iframe_html, "\n".join(debug_info)
196
+
 
 
197
  except Exception as e:
198
+ tb = traceback.format_exc()
199
+ logging.error(tb)
200
+ debug_info.append("❌ ERROR ↓↓↓")
201
+ debug_info.append(tb)
202
+ return (
203
+ f"<div style='color:red;padding:20px;'>였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€: {e}</div>",
204
+ "\n".join(debug_info),
205
+ )
206
+
207
 
208
  def generate_flipbook_html(pages_info, session_id, view_mode, skin):
209
  """Generate a standalone HTML file for the flipbook and return link HTML."""
 
210
  for page in pages_info:
211
+ if page.get("htmlContent") is None:
212
+ page.pop("htmlContent", None)
213
+ if page.get("items") is None:
214
+ page.pop("items", None)
215
+
 
216
  pages_json = json.dumps(pages_info)
 
 
217
  html_filename = f"flipbook_{session_id}.html"
218
  html_path = os.path.join(HTML_DIR, html_filename)
219
+
 
220
  html_content = f"""
221
  <!DOCTYPE html>
222
+ <html lang=\"ko\">
223
  <head>
224
+ <meta charset=\"UTF-8\">
225
+ <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
226
  <title>3D ν”Œλ¦½λΆ</title>
227
+ <link rel=\"stylesheet\" type=\"text/css\" href=\"../flipbook.css\">
228
  <style>
229
+ body, html {{ margin: 0; padding: 0; height: 100%; overflow: hidden; }}
230
+ #flipbook-container {{ width: 100%; height: 100%; position: absolute; top: 0; left: 0; }}
231
+ .loading {{ position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; font-family: Arial, sans-serif; }}
232
+ .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; }}
233
+ @keyframes spin {{ 0% {{ transform: rotate(0deg); }} 100% {{ transform: rotate(360deg); }} }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  </style>
235
+ <script src=\"../flipbook.js\"></script>
236
+ <script src=\"../flipbook.webgl.js\"></script>
237
+ <script src=\"../flipbook.swipe.js\"></script>
238
+ <script src=\"../flipbook.scroll.js\"></script>
239
+ <script src=\"../flipbook.book3.js\"></script>
240
  </head>
241
  <body>
242
+ <div id=\"flipbook-container\"></div>
243
+ <div id=\"loading\" class=\"loading\">
244
+ <div class=\"spinner\"></div>
245
  <div>ν”Œλ¦½λΆ λ‘œλ”© 쀑...</div>
246
  </div>
 
247
  <script>
248
+ document.addEventListener('DOMContentLoaded', function () {{
249
+ function hideLoading() {{ document.getElementById('loading').style.display = 'none'; }}
 
 
 
 
250
  try {{
251
  const options = {{
252
  pages: {pages_json},
 
268
  btnExpand: {{ enabled: true }},
269
  rightToLeft: false,
270
  autoplayOnStart: false,
271
+ autoplayInterval: 3000,
272
  }};
 
273
  const container = document.getElementById('flipbook-container');
274
  if (container) {{
 
275
  new FlipBook(container, options);
276
+ setTimeout(hideLoading, 1000);
277
  }} else {{
278
  console.error('Flipbook container not found');
279
  alert('였λ₯˜: ν”Œλ¦½λΆ μ»¨ν…Œμ΄λ„ˆλ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.');
 
288
  </body>
289
  </html>
290
  """
291
+
292
+ with open(html_path, "w", encoding="utf-8") as f:
 
293
  f.write(html_content)
294
+
 
295
  public_url = f"/public/flipbooks/{html_filename}"
296
  link_html = f"""
297
+ <div style=\"text-align:center; padding:20px; background-color:#f9f9f9; border-radius:5px; margin-bottom:20px;\">
298
+ <h2 style=\"margin-top:0; color:#333;\">ν”Œλ¦½λΆμ΄ μ€€λΉ„λ˜μ—ˆμŠ΅λ‹ˆλ‹€!</h2>
299
+ <p style=\"margin-bottom:20px;\">μ•„λž˜ λ²„νŠΌμ„ ν΄λ¦­ν•˜μ—¬ ν”Œλ¦½λΆμ„ μƒˆ μ°½μ—μ„œ μ—΄μ–΄λ³΄μ„Έμš”.</p>
300
+ <a href=\"{public_url}\" target=\"_blank\" style=\"display:inline-block; background-color:#4CAF50; color:white; padding
301
+
302
  <div style="text-align:center; padding:20px; background-color:#f9f9f9; border-radius:5px; margin-bottom:20px;">
303
  <h2 style="margin-top:0; color:#333;">ν”Œλ¦½λΆμ΄ μ€€λΉ„λ˜μ—ˆμŠ΅λ‹ˆλ‹€!</h2>
304
  <p style="margin-bottom:20px;">μ•„λž˜ λ²„νŠΌμ„ ν΄λ¦­ν•˜μ—¬ ν”Œλ¦½λΆμ„ μƒˆ μ°½μ—μ„œ μ—΄μ–΄λ³΄μ„Έμš”.</p>