ginipick commited on
Commit
a0cf61d
·
verified ·
1 Parent(s): 1767734

Delete app-backup2.py

Browse files
Files changed (1) hide show
  1. app-backup2.py +0 -447
app-backup2.py DELETED
@@ -1,447 +0,0 @@
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>
38
- <link rel="stylesheet" href="/static/flipbook.css">
39
- <script src="/static/three.js"></script>
40
- <script src="/static/iscroll.js"></script>
41
- <script src="/static/mark.js"></script>
42
- <script src="/static/mod3d.js"></script>
43
- <script src="/static/pdf.js"></script>
44
- <script src="/static/flipbook.js"></script>
45
- <script src="/static/flipbook.book3.js"></script>
46
- <script src="/static/flipbook.scroll.js"></script>
47
- <script src="/static/flipbook.swipe.js"></script>
48
- <script src="/static/flipbook.webgl.js"></script>
49
- <style>
50
- body{margin:0;background:#f0f0f0;font-family:sans-serif}
51
- header{max-width:960px;margin:0 auto;padding:18px 20px;display:flex;align-items:center}
52
- #homeBtn{display:none;width:38px;height:38px;border:none;border-radius:50%;cursor:pointer;
53
- background:#0077c2;color:#fff;font-size:20px;margin-right:12px}
54
- #homeBtn:hover{background:#005999}
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: 169%; /* 배경 이미지를 현재보다 30% 더 키움 (130% * 1.3 = 169%) */
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:55%; /* 썸네일 크기 줄임 (배경이 커져서 비율 유지) */
75
- height:auto;
76
- object-fit:contain;
77
- position:absolute; /* 절대 위치로 변경 */
78
- top:50%; /* 상단에서 50% */
79
- left:50%; /* 좌측에서 50% */
80
- transform: translate(-50%, -70%); /* 정중앙에서 20% 위로 이동 */
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
- font-size: 11px; /* 기본 16px에서 약 30% 감소 */
100
- }
101
- button.upload{all:unset;cursor:pointer;border:1px solid #bbb;padding:8px 14px;border-radius:6px;background:#fff;margin:0 8px}
102
- #viewer{
103
- width:80%;
104
- height:80vh; /* 전체 화면 높이의 80%로 축소 */
105
- max-width:80%; /* 최대 너비 80%로 제한 */
106
- margin:0;
107
- background:#fff;
108
- border:1px solid #ddd;
109
- border-radius:8px;
110
- position:fixed; /* 고정 위치 */
111
- top:50%;
112
- left:50%;
113
- transform:translate(-50%, -50%); /* 중앙 정렬 */
114
- z-index:1000; /* 최상위 표시 */
115
- box-shadow:0 4px 20px rgba(0,0,0,0.15);
116
- }
117
- </style></head><body>
118
-
119
- <header>
120
- <button id="homeBtn" title="홈으로">⌂</button>
121
- <h2>My FlipBook Projects</h2>
122
- </header>
123
-
124
- <section id="home">
125
- <div>
126
- <label class="upload">📷 이미지 <input id="imgInput" type="file" accept="image/*" multiple hidden></label>
127
- <label class="upload">📄 PDF <input id="pdfInput" type="file" accept="application/pdf" hidden></label>
128
- </div>
129
- <div class="grid" id="grid"></div>
130
- </section>
131
-
132
- <section id="viewerPage" style="display:none">
133
- <div id="viewer"></div>
134
- </section>
135
-
136
- <script>
137
- let projects=[], fb=null;
138
- const grid=$id('grid'), viewer=$id('viewer');
139
- pdfjsLib.GlobalWorkerOptions.workerSrc='/static/pdf.worker.js';
140
-
141
- // 서버에서 미리 로드된 PDF 프로젝트
142
- let serverProjects = [];
143
-
144
- /* 🔊 오디오 unlock – 내장 Audio 와 같은 MP3 경로 사용 */
145
- ['click','touchstart'].forEach(evt=>{
146
- document.addEventListener(evt,function u(){new Audio('static/turnPage2.mp3')
147
- .play().then(a=>a.pause()).catch(()=>{});document.removeEventListener(evt,u,{capture:true});},
148
- {once:true,capture:true});
149
- });
150
-
151
- /* ── 유틸 ── */
152
- function $id(id){return document.getElementById(id)}
153
- function addCard(i,thumb,title){
154
- const d=document.createElement('div');
155
- d.className='card';
156
- d.onclick=()=>open(i);
157
-
158
- // 제목 10글자 제한 및 말줄임표 처리
159
- const displayTitle = title ?
160
- (title.length > 10 ? title.substring(0, 10) + '...' : title) :
161
- '프로젝트 ' + (i+1);
162
-
163
- d.innerHTML=`<img src="${thumb}"><p title="${title || '프로젝트 ' + (i+1)}">${displayTitle}</p>`;
164
- grid.appendChild(d);
165
- }
166
-
167
- /* ── 이미지 업로드 ── */
168
- $id('imgInput').onchange=e=>{
169
- const files=[...e.target.files]; if(!files.length) return;
170
- const pages=[],tot=files.length;let done=0;
171
- files.forEach((f,i)=>{const r=new FileReader();r.onload=x=>{pages[i]={src:x.target.result,thumb:x.target.result};
172
- if(++done===tot) save(pages);};r.readAsDataURL(f);});
173
- };
174
-
175
- /* ── PDF 업로드 ── */
176
- $id('pdfInput').onchange=e=>{
177
- const file=e.target.files[0]; if(!file) return;
178
- const fr=new FileReader();
179
- fr.onload=v=>{
180
- pdfjsLib.getDocument({data:v.target.result}).promise.then(async pdf=>{
181
- const pages=[];
182
- for(let p=1;p<=pdf.numPages;p++){
183
- const pg=await pdf.getPage(p), vp=pg.getViewport({scale:1});
184
- const c=document.createElement('canvas');c.width=vp.width;c.height=vp.height;
185
- await pg.render({canvasContext:c.getContext('2d'),viewport:vp}).promise;
186
- pages.push({src:c.toDataURL(),thumb:c.toDataURL()});
187
- }
188
- save(pages, file.name.replace('.pdf', ''));
189
- });
190
- };fr.readAsArrayBuffer(file);
191
- };
192
-
193
- /* ── 프로젝트 저장 ── */
194
- function save(pages, title){
195
- const id=projects.push(pages)-1;
196
- addCard(id,pages[0].thumb, title);
197
- }
198
-
199
- /* ── 서버 PDF 로드 ── */
200
- async function loadServerPDFs() {
201
- try {
202
- const response = await fetch('/api/pdf-projects');
203
- serverProjects = await response.json();
204
-
205
- // 서버 PDF 로드 및 썸네일 생성
206
- for(let i = 0; i < serverProjects.length; i++) {
207
- const project = serverProjects[i];
208
- const response = await fetch(`/api/pdf-thumbnail?path=${encodeURIComponent(project.path)}`);
209
- const data = await response.json();
210
-
211
- if(data.thumbnail) {
212
- const pages = [{
213
- src: data.thumbnail,
214
- thumb: data.thumbnail,
215
- path: project.path
216
- }];
217
-
218
- save(pages, project.name);
219
- }
220
- }
221
- } catch(error) {
222
- console.error('서버 PDF 로드 실패:', error);
223
- }
224
- }
225
-
226
- /* ── 카드 → FlipBook ── */
227
- function open(i){
228
- toggle(false);
229
- const pages = projects[i];
230
-
231
- // 로컬 프로젝트 또는 서버 PDF 로드
232
- if(fb){fb.destroy();viewer.innerHTML='';}
233
-
234
- if(pages[0].path) {
235
- // 로딩 표시
236
- viewer.innerHTML = '<div style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center;"><div style="border:4px solid #f3f3f3;border-top:4px solid #3498db;border-radius:50%;width:50px;height:50px;margin:0 auto;animation:spin 2s linear infinite;"></div><p style="margin-top:20px;font-size:16px;">PDF 로딩 중...</p></div>';
237
-
238
- // 스타일 추가
239
- if (!document.getElementById('loadingStyle')) {
240
- const style = document.createElement('style');
241
- style.id = 'loadingStyle';
242
- style.textContent = '@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}';
243
- document.head.appendChild(style);
244
- }
245
-
246
- // 서버 PDF 파일 로드
247
- fetch(`/api/pdf-content?path=${encodeURIComponent(pages[0].path)}`)
248
- .then(response => {
249
- if (!response.ok) {
250
- throw new Error('PDF 로드 실패: ' + response.statusText);
251
- }
252
- return response.arrayBuffer();
253
- })
254
- .then(pdfData => {
255
- // PDF 데이터 로드 확인 로깅
256
- console.log('PDF 데이터 로드 완료:', pdfData.byteLength + ' 바이트');
257
-
258
- return pdfjsLib.getDocument({data: pdfData}).promise;
259
- })
260
- .then(async pdf => {
261
- console.log('PDF 문서 로드 완료. 페이지 수:', pdf.numPages);
262
-
263
- const pdfPages = [];
264
- const progressElement = viewer.querySelector('p');
265
-
266
- for(let p = 1; p <= pdf.numPages; p++) {
267
- if (progressElement) {
268
- progressElement.textContent = `PDF 페이지 로딩 중... (${p}/${pdf.numPages})`;
269
- }
270
-
271
- try {
272
- const pg = await pdf.getPage(p);
273
- const vp = pg.getViewport({scale: 1});
274
- const c = document.createElement('canvas');
275
- c.width = vp.width;
276
- c.height = vp.height;
277
-
278
- await pg.render({canvasContext: c.getContext('2d'), viewport: vp}).promise;
279
- pdfPages.push({src: c.toDataURL(), thumb: c.toDataURL()});
280
- } catch (pageError) {
281
- console.error(`페이지 ${p} 렌더링 오류:`, pageError);
282
- }
283
- }
284
-
285
- console.log('모든 페이지 렌더링 완료:', pdfPages.length);
286
-
287
- if (pdfPages.length > 0) {
288
- createFlipBook(pdfPages);
289
- } else {
290
- throw new Error('PDF에서 페이지를 추출할 수 없습니다.');
291
- }
292
- })
293
- .catch(error => {
294
- console.error('PDF 처리 중 오류 발생:', error);
295
- viewer.innerHTML = `<div style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center;"><p style="color:red;font-size:16px;">PDF를 로드하는 중 오류가 발생했습니다:<br>${error.message}</p><button id="backBtn" style="margin-top:20px;padding:10px 20px;background:#0077c2;color:white;border:none;border-radius:4px;cursor:pointer;">홈으로 돌아가기</button></div>`;
296
-
297
- document.getElementById('backBtn').addEventListener('click', function() {
298
- toggle(true);
299
- });
300
- });
301
- } else {
302
- // 업로드된 프로젝트 보기
303
- console.log('로컬 업로드된 프로젝트 렌더링:', pages.length + '페이지');
304
- createFlipBook(pages);
305
- }
306
- }
307
-
308
- function createFlipBook(pages) {
309
- console.log('FlipBook 생성 시작. 페이지 수:', pages.length);
310
-
311
- try {
312
- fb = new FlipBook(viewer, {
313
- pages: pages,
314
- viewMode: 'webgl',
315
- autoSize: true,
316
- flipDuration: 800,
317
- backgroundColor: '#fff',
318
- /* 🔊 내장 사운드 */
319
- sound: true,
320
- assets: {flipMp3: 'static/turnPage2.mp3', hardFlipMp3: 'static/turnPage2.mp3'},
321
- controlsProps: {enableFullscreen: true, thumbnails: true}
322
- });
323
-
324
- console.log('FlipBook 생성 완료');
325
- } catch (error) {
326
- console.error('FlipBook 생성 중 오류 발생:', error);
327
- alert('FlipBook을 생성하는 중 오류가 발생했습니다: ' + error.message);
328
- }
329
- }
330
-
331
- /* ── 네비게이션 ── */
332
- $id('homeBtn').onclick=()=>{
333
- if(fb) {
334
- fb.destroy();
335
- viewer.innerHTML = '';
336
- fb = null;
337
- }
338
- toggle(true);
339
- };
340
-
341
- function toggle(showHome){
342
- $id('home').style.display=showHome?'block':'none';
343
- $id('viewerPage').style.display=showHome?'none':'block';
344
- $id('homeBtn').style.display=showHome?'none':'inline-block';
345
-
346
- // 추가: 전체 화면 모드에서 homeBtn 위치 조정
347
- if(!showHome) {
348
- $id('homeBtn').style.position = 'fixed';
349
- $id('homeBtn').style.top = '20px';
350
- $id('homeBtn').style.left = '20px';
351
- $id('homeBtn').style.zIndex = '1001'; // 뷰어보다 높은 z-index
352
- $id('homeBtn').style.fontSize = '24px'; // 크기 증가
353
- $id('homeBtn').style.width = '48px';
354
- $id('homeBtn').style.height = '48px';
355
-
356
- // 배경 오버레이 추가
357
- document.body.style.backgroundColor = '#3a3a3a';
358
- } else {
359
- $id('homeBtn').style.position = '';
360
- $id('homeBtn').style.top = '';
361
- $id('homeBtn').style.left = '';
362
- $id('homeBtn').style.zIndex = '';
363
- $id('homeBtn').style.fontSize = '';
364
- $id('homeBtn').style.width = '';
365
- $id('homeBtn').style.height = '';
366
-
367
- // 배경 원래대로
368
- document.body.style.backgroundColor = '#f0f0f0';
369
- }
370
- }
371
-
372
- // 페이지 로드 시 서버 PDF 로드
373
- window.addEventListener('DOMContentLoaded', loadServerPDFs);
374
- </script>
375
- </body></html>
376
- """
377
-
378
- # API 엔드포인트: PDF 프로젝트 목록
379
- @app.get("/api/pdf-projects")
380
- async def get_pdf_projects():
381
- return generate_pdf_projects()
382
-
383
- # API 엔드포인트: PDF 썸네일 생성
384
- @app.get("/api/pdf-thumbnail")
385
- async def get_pdf_thumbnail(path: str):
386
- try:
387
- import fitz # PyMuPDF
388
-
389
- # PDF 파일 열기
390
- doc = fitz.open(path)
391
-
392
- # 첫 페이지 가져오기
393
- if doc.page_count > 0:
394
- page = doc[0]
395
- # 썸네일용 이미지 렌더링 (해상도 조정)
396
- pix = page.get_pixmap(matrix=fitz.Matrix(0.5, 0.5))
397
- img_data = pix.tobytes("png")
398
-
399
- # Base64 인코딩
400
- b64_img = base64.b64encode(img_data).decode('utf-8')
401
- return {"thumbnail": f"data:image/png;base64,{b64_img}"}
402
-
403
- return {"thumbnail": None}
404
- except Exception as e:
405
- return {"error": str(e), "thumbnail": None}
406
-
407
- @app.get("/api/pdf-content")
408
- async def get_pdf_content(path: str):
409
- try:
410
- # 파일 존재 여부 확인
411
- pdf_path = pathlib.Path(path)
412
- if not pdf_path.exists():
413
- return {"error": f"파일을 찾을 수 없습니다: {path}"}, 404
414
-
415
- # 파일 읽기
416
- with open(path, "rb") as pdf_file:
417
- content = pdf_file.read()
418
-
419
- # 파일명 처리 - URL 인코딩으로 한글 등 특수 문자 처리
420
- import urllib.parse
421
- filename = pdf_path.name
422
- encoded_filename = urllib.parse.quote(filename)
423
-
424
- # 응답 헤더 설정 - RFC 6266 표준 사용
425
- headers = {
426
- "Content-Type": "application/pdf",
427
- "Content-Disposition": f"inline; filename=\"{encoded_filename}\"; filename*=UTF-8''{encoded_filename}"
428
- }
429
-
430
- # 파일 콘텐츠 직접 반환 (dict가 아닌 Response 객체)
431
- from fastapi.responses import Response
432
- return Response(content=content, media_type="application/pdf")
433
- except Exception as e:
434
- import traceback
435
- error_details = traceback.format_exc()
436
- print(f"PDF 콘텐츠 로드 오류: {str(e)}\n{error_details}")
437
-
438
- # 오류 응답 반환 (JSON 형식)
439
- from fastapi.responses import JSONResponse
440
- return JSONResponse(content={"error": str(e)}, status_code=500)
441
-
442
- @app.get("/", response_class=HTMLResponse)
443
- async def root():
444
- return HTML
445
-
446
- if __name__ == "__main__":
447
- uvicorn.run("app:app", host="0.0.0.0", port=int(os.getenv("PORT", 7860)))