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

Delete app-backup.py

Browse files
Files changed (1) hide show
  1. app-backup.py +0 -374
app-backup.py DELETED
@@ -1,374 +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: 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>
116
- <button id="homeBtn" title="홈으로">⌂</button>
117
- <h2>My FlipBook Projects</h2>
118
- </header>
119
-
120
- <section id="home">
121
- <div>
122
- <label class="upload">📷 이미지 <input id="imgInput" type="file" accept="image/*" multiple hidden></label>
123
- <label class="upload">📄 PDF <input id="pdfInput" type="file" accept="application/pdf" hidden></label>
124
- </div>
125
- <div class="grid" id="grid"></div>
126
- </section>
127
-
128
- <section id="viewerPage" style="display:none">
129
- <div id="viewer"></div>
130
- </section>
131
-
132
- <script>
133
- let projects=[], fb=null;
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')
143
- .play().then(a=>a.pause()).catch(()=>{});document.removeEventListener(evt,u,{capture:true});},
144
- {once:true,capture:true});
145
- });
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
- /* ── 이미지 업로드 ── */
164
- $id('imgInput').onchange=e=>{
165
- const files=[...e.target.files]; if(!files.length) return;
166
- const pages=[],tot=files.length;let done=0;
167
- files.forEach((f,i)=>{const r=new FileReader();r.onload=x=>{pages[i]={src:x.target.result,thumb:x.target.result};
168
- if(++done===tot) save(pages);};r.readAsDataURL(f);});
169
- };
170
-
171
- /* ── PDF 업로드 ── */
172
- $id('pdfInput').onchange=e=>{
173
- const file=e.target.files[0]; if(!file) return;
174
- const fr=new FileReader();
175
- fr.onload=v=>{
176
- pdfjsLib.getDocument({data:v.target.result}).promise.then(async pdf=>{
177
- const pages=[];
178
- for(let p=1;p<=pdf.numPages;p++){
179
- const pg=await pdf.getPage(p), vp=pg.getViewport({scale:1});
180
- const c=document.createElement('canvas');c.width=vp.width;c.height=vp.height;
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
- /* ── 네비게이션 ── */
300
- $id('homeBtn').onclick=()=>toggle(true);
301
- function toggle(showHome){
302
- $id('home').style.display=showHome?'block':'none';
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)))