ginipick commited on
Commit
b40c73b
Β·
verified Β·
1 Parent(s): a7a02d1

Update app-backup.py

Browse files
Files changed (1) hide show
  1. app-backup.py +258 -18
app-backup.py CHANGED
@@ -1,12 +1,37 @@
1
  from fastapi import FastAPI
2
  from fastapi.responses import HTMLResponse
3
  from fastapi.staticfiles import StaticFiles
4
- import pathlib, os, uvicorn
5
 
6
  BASE = pathlib.Path(__file__).parent
7
  app = FastAPI()
8
  app.mount("/static", StaticFiles(directory=BASE), name="static")
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  HTML = """
11
  <!doctype html><html lang="ko"><head>
12
  <meta charset="utf-8"><title>FlipBook Space</title>
@@ -30,11 +55,61 @@ header{max-width:960px;margin:0 auto;padding:18px 20px;display:flex;align-items:
30
  h2{margin:0;font-size:1.5rem;font-weight:600}
31
  #home,#viewerPage{max-width:960px;margin:0 auto;padding:0 20px 40px}
32
  .grid{display:grid;grid-template-columns:repeat(auto-fill,180px);gap:16px;margin-top:24px}
33
- .card{background:#fff;border:1px solid #ccc;border-radius:6px;cursor:pointer;box-shadow:0 2px 4px rgba(0,0,0,.12)}
34
- .card img{width:100%;height:140px;object-fit:cover}
35
- .card p{text-align:center;margin:6px 0}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  button.upload{all:unset;cursor:pointer;border:1px solid #bbb;padding:8px 14px;border-radius:6px;background:#fff;margin:0 8px}
37
- #viewer{width:100%;max-width:1200px;height:80vh;margin:24px auto;background:#fff;border:1px solid #ccc}
 
 
 
 
 
 
 
 
 
 
 
38
  </style></head><body>
39
 
40
  <header>
@@ -59,6 +134,9 @@ let projects=[], fb=null;
59
  const grid=$id('grid'), viewer=$id('viewer');
60
  pdfjsLib.GlobalWorkerOptions.workerSrc='/static/pdf.worker.js';
61
 
 
 
 
62
  /* πŸ”Š μ˜€λ””μ˜€ unlock – λ‚΄μž₯ Audio 와 같은 MP3 경둜 μ‚¬μš© */
63
  ['click','touchstart'].forEach(evt=>{
64
  document.addEventListener(evt,function u(){new Audio('static/turnPage2.mp3')
@@ -68,9 +146,18 @@ pdfjsLib.GlobalWorkerOptions.workerSrc='/static/pdf.worker.js';
68
 
69
  /* ── μœ ν‹Έ ── */
70
  function $id(id){return document.getElementById(id)}
71
- function addCard(i,thumb){
72
- const d=document.createElement('div');d.className='card';d.onclick=()=>open(i);
73
- d.innerHTML=`<img src="${thumb}"><p>ν”„λ‘œμ νŠΈ ${i+1}</p>`;grid.appendChild(d);
 
 
 
 
 
 
 
 
 
74
  }
75
 
76
  /* ── 이미지 μ—…λ‘œλ“œ ── */
@@ -94,25 +181,119 @@ $id('pdfInput').onchange=e=>{
94
  await pg.render({canvasContext:c.getContext('2d'),viewport:vp}).promise;
95
  pages.push({src:c.toDataURL(),thumb:c.toDataURL()});
96
  }
97
- save(pages);
98
  });
99
  };fr.readAsArrayBuffer(file);
100
  };
101
 
102
  /* ── ν”„λ‘œμ νŠΈ μ €μž₯ ── */
103
- function save(pages){const id=projects.push(pages)-1;addCard(id,pages[0].thumb);}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
 
105
  /* ── μΉ΄λ“œ β†’ FlipBook ── */
106
  function open(i){
107
  toggle(false);
108
- const pages=projects[i];
 
 
109
  if(fb){fb.destroy();viewer.innerHTML='';}
110
- fb=new FlipBook(viewer,{
111
- pages,viewMode:'webgl',autoSize:true,flipDuration:800,backgroundColor:'#fff',
112
- /* πŸ”Š λ‚΄μž₯ μ‚¬μš΄λ“œ */
113
- sound:true,
114
- assets:{flipMp3:'static/turnPage2.mp3',hardFlipMp3:'static/turnPage2.mp3'},
115
- controlsProps:{enableFullscreen:true,thumbnails:true}});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  }
117
 
118
  /* ── λ„€λΉ„κ²Œμ΄μ…˜ ── */
@@ -122,13 +303,72 @@ function toggle(showHome){
122
  $id('viewerPage').style.display=showHome?'none':'block';
123
  $id('homeBtn').style.display=showHome?'none':'inline-block';
124
  }
 
 
 
125
  </script>
126
  </body></html>
127
  """
128
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  @app.get("/", response_class=HTMLResponse)
130
  async def root():
131
  return HTML
132
 
133
  if __name__ == "__main__":
134
- uvicorn.run("app:app", host="0.0.0.0", port=int(os.getenv("PORT", 7860)))
 
1
  from fastapi import FastAPI
2
  from fastapi.responses import HTMLResponse
3
  from fastapi.staticfiles import StaticFiles
4
+ import pathlib, os, uvicorn, base64
5
 
6
  BASE = pathlib.Path(__file__).parent
7
  app = FastAPI()
8
  app.mount("/static", StaticFiles(directory=BASE), name="static")
9
 
10
+ # PDF 디렉토리 μ„€μ •
11
+ PDF_DIR = BASE / "pdf"
12
+ if not PDF_DIR.exists():
13
+ PDF_DIR.mkdir(parents=True)
14
+
15
+ # PDF 파일 λͺ©λ‘ κ°€μ Έμ˜€κΈ°
16
+ def get_pdf_files():
17
+ pdf_files = []
18
+ if PDF_DIR.exists():
19
+ pdf_files = [f for f in PDF_DIR.glob("*.pdf")]
20
+ return pdf_files
21
+
22
+ # PDF 썸넀일 생성 및 ν”„λ‘œμ νŠΈ 데이터 μ€€λΉ„
23
+ def generate_pdf_projects():
24
+ projects_data = []
25
+ pdf_files = get_pdf_files()
26
+
27
+ for pdf_file in pdf_files:
28
+ projects_data.append({
29
+ "path": str(pdf_file),
30
+ "name": pdf_file.stem
31
+ })
32
+
33
+ return projects_data
34
+
35
  HTML = """
36
  <!doctype html><html lang="ko"><head>
37
  <meta charset="utf-8"><title>FlipBook Space</title>
 
55
  h2{margin:0;font-size:1.5rem;font-weight:600}
56
  #home,#viewerPage{max-width:960px;margin:0 auto;padding:0 20px 40px}
57
  .grid{display:grid;grid-template-columns:repeat(auto-fill,180px);gap:16px;margin-top:24px}
58
+ .card{
59
+ background:#fff url('/static/book2.jpg') no-repeat center center;
60
+ background-size: 130%; /* λ°°κ²½ 이미지 30% ν™•λŒ€ */
61
+ border:1px solid #ccc;
62
+ border-radius:6px;
63
+ cursor:pointer;
64
+ box-shadow:0 2px 4px rgba(0,0,0,.12);
65
+ width: 180px;
66
+ height: 240px;
67
+ position: relative;
68
+ display: flex;
69
+ flex-direction: column;
70
+ align-items: center;
71
+ justify-content: center;
72
+ }
73
+ .card img{
74
+ width:65%; /* 썸넀일 크기 μ‘°μ • */
75
+ height:auto;
76
+ object-fit:contain;
77
+ position:absolute; /* μ ˆλŒ€ μœ„μΉ˜λ‘œ λ³€κ²½ */
78
+ top:50%; /* μƒλ‹¨μ—μ„œ 50% */
79
+ left:50%; /* μ’ŒμΈ‘μ—μ„œ 50% */
80
+ transform: translate(-50%, -50%); /* 정쀑앙 배치 */
81
+ border: 1px solid #ddd;
82
+ box-shadow: 0 2px 5px rgba(0,0,0,0.2);
83
+ }
84
+ .card p{
85
+ text-align:center;
86
+ margin:6px 0;
87
+ position: absolute;
88
+ bottom: 10px;
89
+ left: 50%;
90
+ transform: translateX(-50%);
91
+ background: rgba(255, 255, 255, 0.7);
92
+ padding: 4px 8px;
93
+ border-radius: 4px;
94
+ width: 85%;
95
+ white-space: nowrap;
96
+ overflow: hidden;
97
+ text-overflow: ellipsis;
98
+ max-width: 150px;
99
+ }
100
  button.upload{all:unset;cursor:pointer;border:1px solid #bbb;padding:8px 14px;border-radius:6px;background:#fff;margin:0 8px}
101
+ #viewer{
102
+ width:100%;
103
+ height:100vh; /* 전체 ν™”λ©΄ λ†’μ΄λ‘œ ν™•μž₯ */
104
+ max-width:100%; /* μ΅œλŒ€ λ„ˆλΉ„ μ œν•œ ν•΄μ œ */
105
+ margin:0; /* λ§ˆμ§„ 제거 */
106
+ background:#fff;
107
+ border:none; /* ν…Œλ‘λ¦¬ 제거 */
108
+ position:fixed; /* κ³ μ • μœ„μΉ˜ */
109
+ top:0;
110
+ left:0;
111
+ z-index:1000; /* μ΅œμƒμœ„ ν‘œμ‹œ */
112
+ }
113
  </style></head><body>
114
 
115
  <header>
 
134
  const grid=$id('grid'), viewer=$id('viewer');
135
  pdfjsLib.GlobalWorkerOptions.workerSrc='/static/pdf.worker.js';
136
 
137
+ // μ„œλ²„μ—μ„œ 미리 λ‘œλ“œλœ PDF ν”„λ‘œμ νŠΈ
138
+ let serverProjects = [];
139
+
140
  /* πŸ”Š μ˜€λ””μ˜€ unlock – λ‚΄μž₯ Audio 와 같은 MP3 경둜 μ‚¬μš© */
141
  ['click','touchstart'].forEach(evt=>{
142
  document.addEventListener(evt,function u(){new Audio('static/turnPage2.mp3')
 
146
 
147
  /* ── μœ ν‹Έ ── */
148
  function $id(id){return document.getElementById(id)}
149
+ function addCard(i,thumb,title){
150
+ const d=document.createElement('div');
151
+ d.className='card';
152
+ d.onclick=()=>open(i);
153
+
154
+ // 제λͺ© 10κΈ€μž μ œν•œ 및 λ§μ€„μž„ν‘œ 처리
155
+ const displayTitle = title ?
156
+ (title.length > 10 ? title.substring(0, 10) + '...' : title) :
157
+ 'ν”„λ‘œμ νŠΈ ' + (i+1);
158
+
159
+ d.innerHTML=`<img src="${thumb}"><p title="${title || 'ν”„λ‘œμ νŠΈ ' + (i+1)}">${displayTitle}</p>`;
160
+ grid.appendChild(d);
161
  }
162
 
163
  /* ── 이미지 μ—…λ‘œλ“œ ── */
 
181
  await pg.render({canvasContext:c.getContext('2d'),viewport:vp}).promise;
182
  pages.push({src:c.toDataURL(),thumb:c.toDataURL()});
183
  }
184
+ save(pages, file.name.replace('.pdf', ''));
185
  });
186
  };fr.readAsArrayBuffer(file);
187
  };
188
 
189
  /* ── ν”„λ‘œμ νŠΈ μ €μž₯ ── */
190
+ function save(pages, title){
191
+ const id=projects.push(pages)-1;
192
+ addCard(id,pages[0].thumb, title);
193
+ }
194
+
195
+ /* ── ���버 PDF λ‘œλ“œ ── */
196
+ async function loadServerPDFs() {
197
+ try {
198
+ const response = await fetch('/api/pdf-projects');
199
+ serverProjects = await response.json();
200
+
201
+ // μ„œλ²„ PDF λ‘œλ“œ 및 썸넀일 생성
202
+ for(let i = 0; i < serverProjects.length; i++) {
203
+ const project = serverProjects[i];
204
+ const response = await fetch(`/api/pdf-thumbnail?path=${encodeURIComponent(project.path)}`);
205
+ const data = await response.json();
206
+
207
+ if(data.thumbnail) {
208
+ const pages = [{
209
+ src: data.thumbnail,
210
+ thumb: data.thumbnail,
211
+ path: project.path
212
+ }];
213
+
214
+ save(pages, project.name);
215
+ }
216
+ }
217
+ } catch(error) {
218
+ console.error('μ„œλ²„ PDF λ‘œλ“œ μ‹€νŒ¨:', error);
219
+ }
220
+ }
221
 
222
  /* ── μΉ΄λ“œ β†’ FlipBook ── */
223
  function open(i){
224
  toggle(false);
225
+ const pages = projects[i];
226
+
227
+ // 둜컬 ν”„λ‘œμ νŠΈ λ˜λŠ” μ„œλ²„ PDF λ‘œλ“œ
228
  if(fb){fb.destroy();viewer.innerHTML='';}
229
+
230
+ if(pages[0].path) {
231
+ // μ„œλ²„ PDF 파일 λ‘œλ“œ
232
+ fetch(`/api/pdf-content?path=${encodeURIComponent(pages[0].path)}`)
233
+ .then(response => {
234
+ if (!response.ok) {
235
+ throw new Error('PDF λ‘œλ“œ μ‹€νŒ¨: ' + response.statusText);
236
+ }
237
+ return response.arrayBuffer();
238
+ })
239
+ .then(pdfData => {
240
+ // PDF 데이터 λ‘œλ“œ 확인 λ‘œκΉ…
241
+ console.log('PDF 데이터 λ‘œλ“œ μ™„λ£Œ:', pdfData.byteLength + ' λ°”μ΄νŠΈ');
242
+
243
+ return pdfjsLib.getDocument({data: pdfData}).promise;
244
+ })
245
+ .then(async pdf => {
246
+ console.log('PDF λ¬Έμ„œ λ‘œλ“œ μ™„λ£Œ. νŽ˜μ΄μ§€ 수:', pdf.numPages);
247
+
248
+ const pdfPages = [];
249
+ for(let p = 1; p <= pdf.numPages; p++) {
250
+ console.log('νŽ˜μ΄μ§€ λ Œλ”λ§ 쀑:', p + '/' + pdf.numPages);
251
+
252
+ const pg = await pdf.getPage(p);
253
+ const vp = pg.getViewport({scale: 1});
254
+ const c = document.createElement('canvas');
255
+ c.width = vp.width;
256
+ c.height = vp.height;
257
+
258
+ await pg.render({canvasContext: c.getContext('2d'), viewport: vp}).promise;
259
+ pdfPages.push({src: c.toDataURL(), thumb: c.toDataURL()});
260
+ }
261
+
262
+ console.log('λͺ¨λ“  νŽ˜μ΄μ§€ λ Œλ”λ§ μ™„λ£Œ:', pdfPages.length);
263
+ createFlipBook(pdfPages);
264
+ })
265
+ .catch(error => {
266
+ console.error('PDF 처리 쀑 였λ₯˜ λ°œμƒ:', error);
267
+ alert('PDFλ₯Ό λ‘œλ“œν•˜λŠ” 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€: ' + error.message);
268
+ });
269
+ } else {
270
+ // μ—…λ‘œλ“œλœ ν”„λ‘œμ νŠΈ 보기
271
+ console.log('둜컬 μ—…λ‘œλ“œλœ ν”„λ‘œμ νŠΈ λ Œλ”λ§:', pages.length + 'νŽ˜μ΄μ§€');
272
+ createFlipBook(pages);
273
+ }
274
+ }
275
+
276
+ function createFlipBook(pages) {
277
+ console.log('FlipBook 생성 μ‹œμž‘. νŽ˜μ΄μ§€ 수:', pages.length);
278
+
279
+ try {
280
+ fb = new FlipBook(viewer, {
281
+ pages: pages,
282
+ viewMode: 'webgl',
283
+ autoSize: true,
284
+ flipDuration: 800,
285
+ backgroundColor: '#fff',
286
+ /* πŸ”Š λ‚΄μž₯ μ‚¬μš΄λ“œ */
287
+ sound: true,
288
+ assets: {flipMp3: 'static/turnPage2.mp3', hardFlipMp3: 'static/turnPage2.mp3'},
289
+ controlsProps: {enableFullscreen: true, thumbnails: true}
290
+ });
291
+
292
+ console.log('FlipBook 생성 μ™„λ£Œ');
293
+ } catch (error) {
294
+ console.error('FlipBook 생성 쀑 였λ₯˜ λ°œμƒ:', error);
295
+ alert('FlipBook을 μƒμ„±ν•˜λŠ” 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€: ' + error.message);
296
+ }
297
  }
298
 
299
  /* ── λ„€λΉ„κ²Œμ΄μ…˜ ── */
 
303
  $id('viewerPage').style.display=showHome?'none':'block';
304
  $id('homeBtn').style.display=showHome?'none':'inline-block';
305
  }
306
+
307
+ // νŽ˜μ΄μ§€ λ‘œλ“œ μ‹œ μ„œλ²„ PDF λ‘œλ“œ
308
+ window.addEventListener('DOMContentLoaded', loadServerPDFs);
309
  </script>
310
  </body></html>
311
  """
312
 
313
+ # API μ—”λ“œν¬μΈνŠΈ: PDF ν”„λ‘œμ νŠΈ λͺ©λ‘
314
+ @app.get("/api/pdf-projects")
315
+ async def get_pdf_projects():
316
+ return generate_pdf_projects()
317
+
318
+ # API μ—”λ“œν¬μΈνŠΈ: PDF 썸넀일 생성
319
+ @app.get("/api/pdf-thumbnail")
320
+ async def get_pdf_thumbnail(path: str):
321
+ try:
322
+ import fitz # PyMuPDF
323
+
324
+ # PDF 파일 μ—΄κΈ°
325
+ doc = fitz.open(path)
326
+
327
+ # 첫 νŽ˜μ΄μ§€ κ°€μ Έμ˜€κΈ°
328
+ if doc.page_count > 0:
329
+ page = doc[0]
330
+ # μΈλ„€μΌμš© 이미지 λ Œλ”λ§ (해상도 μ‘°μ •)
331
+ pix = page.get_pixmap(matrix=fitz.Matrix(0.5, 0.5))
332
+ img_data = pix.tobytes("png")
333
+
334
+ # Base64 인코딩
335
+ b64_img = base64.b64encode(img_data).decode('utf-8')
336
+ return {"thumbnail": f"data:image/png;base64,{b64_img}"}
337
+
338
+ return {"thumbnail": None}
339
+ except Exception as e:
340
+ return {"error": str(e), "thumbnail": None}
341
+
342
+ @app.get("/api/pdf-content")
343
+ async def get_pdf_content(path: str):
344
+ try:
345
+ # 파일 쑴재 μ—¬λΆ€ 확인
346
+ pdf_path = pathlib.Path(path)
347
+ if not pdf_path.exists():
348
+ return {"error": f"νŒŒμΌμ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€: {path}"}, 404
349
+
350
+ # 파일 읽기
351
+ with open(path, "rb") as pdf_file:
352
+ content = pdf_file.read()
353
+
354
+ # 응닡 헀더 μ„€μ • - PDF νŒŒμΌμž„μ„ λͺ…μ‹œ
355
+ headers = {
356
+ "Content-Type": "application/pdf",
357
+ "Content-Disposition": f"inline; filename={pdf_path.name}"
358
+ }
359
+
360
+ # 파일 μ½˜ν…μΈ  λ°˜ν™˜
361
+ from fastapi.responses import Response
362
+ return Response(content=content, headers=headers)
363
+ except Exception as e:
364
+ import traceback
365
+ error_details = traceback.format_exc()
366
+ print(f"PDF μ½˜ν…μΈ  λ‘œλ“œ 였λ₯˜: {str(e)}\n{error_details}")
367
+ return {"error": str(e)}, 500
368
+
369
  @app.get("/", response_class=HTMLResponse)
370
  async def root():
371
  return HTML
372
 
373
  if __name__ == "__main__":
374
+ uvicorn.run("app:app", host="0.0.0.0", port=int(os.getenv("PORT", 7860)))