ginipick commited on
Commit
66aed24
Β·
verified Β·
1 Parent(s): c00e013

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +98 -88
app.py CHANGED
@@ -1,124 +1,134 @@
1
- # app.py
2
- # FastAPI μ„œλ²„ + Real3D FlipBook 정적 μ‚¬μ΄νŠΈ
3
- # λͺ¨λ“  정적 파일(JS / CSS / MP3 / 이미지 λ“±)κ³Ό 이 νŒŒμΌμ„ 같은 폴더에 λ‘‘λ‹ˆλ‹€.
4
-
5
  from fastapi import FastAPI
6
  from fastapi.responses import HTMLResponse
7
  from fastapi.staticfiles import StaticFiles
8
  import pathlib, os, uvicorn
9
 
10
  BASE = pathlib.Path(__file__).parent
11
-
12
  app = FastAPI()
13
-
14
- # ─────────────────────────────────────────────────────────────
15
- # 1) /static β†’ ν˜„μž¬ 폴더(라이브러리·MP3·이미지) μ„œλΉ™
16
- # ─────────────────────────────────────────────────────────────
17
  app.mount("/static", StaticFiles(directory=BASE), name="static")
18
 
19
- # ─────────────────────────────────────────────────────────────
20
- # 2) index.html 원문 (μ˜€λ””μ˜€ 경둜 /static/turnPage2.mp3 둜 μˆ˜μ •)
21
- # ─────────────────────────────────────────────────────────────
22
- INDEX_HTML = """
23
- <!DOCTYPE html>
24
- <html lang="ko">
25
- <head>
26
- <meta charset="UTF-8">
27
- <title>FlipBook – μ—…λ‘œλ“œ + μ‚¬μš΄λ“œ</title>
28
-
29
  <link rel="stylesheet" href="/static/flipbook.css">
30
-
31
- <!-- ν•„μˆ˜ JS μˆœμ„œ -->
32
  <script src="/static/three.js"></script>
33
  <script src="/static/iscroll.js"></script>
34
  <script src="/static/mark.js"></script>
35
  <script src="/static/mod3d.js"></script>
36
-
37
  <script src="/static/flipbook.js"></script>
38
  <script src="/static/flipbook.book3.js"></script>
39
  <script src="/static/flipbook.scroll.js"></script>
40
  <script src="/static/flipbook.swipe.js"></script>
41
  <script src="/static/flipbook.webgl.js"></script>
42
-
43
  <style>
44
- body{margin:0;font-family:sans-serif;background:#f4f4f4}
45
- h2{text-align:center;margin:24px 0}
46
- #viewer{width:900px;height:600px;margin:0 auto 40px;background:#fff;border:1px solid #ccc}
47
- .upload-wrapper{display:flex;justify-content:center}
48
- #uploadBtn{
49
- all:unset;width:44px;height:44px;line-height:44px;text-align:center;
50
- font-size:26px;border-radius:50%;cursor:pointer;
51
- background:#ffb84d;color:#fff;box-shadow:0 2px 4px rgba(0,0,0,.2);
52
- }
53
- #fileInput{display:none}
54
- </style>
55
- </head>
56
- <body>
57
-
58
- <h2>Real3D FlipBook – 이미지 μ—…λ‘œλ“œ πŸ“·βœ¨</h2>
59
-
60
- <div id="viewer"></div>
61
-
62
- <div class="upload-wrapper">
63
- <button id="uploadBtn" title="이미지 μ—…λ‘œλ“œ">πŸ“·</button>
64
- <input id="fileInput" type="file" accept="image/*" multiple>
65
- </div>
 
 
 
 
 
 
 
 
 
66
 
67
  <script>
68
- let book=null;
69
- const viewer=document.getElementById('viewer');
70
-
71
- document.getElementById('uploadBtn').onclick=()=>document.getElementById('fileInput').click();
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
- document.getElementById('fileInput').onchange=e=>{
74
- if(!e.target.files.length) return;
75
- const files=[...e.target.files];
76
- const pages=[], tot=files.length; let read=0;
77
- files.forEach((f,i)=>{
78
- const r=new FileReader();
79
- r.onload=ev=>{
80
- pages[i]={src:ev.target.result,thumb:ev.target.result};
81
- if(++read===tot) createBook(pages);
82
- };
83
- r.readAsDataURL(f);
84
- });
85
  };
86
 
87
- function createBook(pages){
88
- if(book){ book.destroy(); viewer.innerHTML=''; }
89
-
90
- book=new FlipBook(viewer,{
91
- pages,
92
- viewMode:'webgl',
93
- autoSize:true,
94
- flipDuration:800,
95
- backgroundColor:'#ffffff',
 
 
 
 
 
 
 
 
96
 
97
- /* πŸ”Š μ‚¬μš΄λ“œ – 정적 경둜 μ§€μ • */
 
 
 
 
 
 
 
 
 
 
98
  sound:true,
99
- assets:{
100
- flipMp3 : '/static/turnPage2.mp3',
101
- hardFlipMp3 : '/static/turnPage2.mp3'
102
- },
103
 
104
- controlsProps:{enableFullscreen:true,thumbnails:true}
105
- });
 
 
 
 
106
  }
107
  </script>
108
- </body>
109
- </html>
110
  """
111
 
112
- # ─────────────────────────────────────────────────────────────
113
- # 3) 루트 경둜 β†’ index.html λ°˜ν™˜
114
- # ─────────────────────────────────────────────────────────────
115
  @app.get("/", response_class=HTMLResponse)
116
  async def root():
117
- return INDEX_HTML
118
 
119
- # ─────────────────────────────────────────────────────────────
120
- # 4) Hugging Face Spacesκ°€ python app.py 둜 μ‹€ν–‰ μ‹œ μ„œλ²„ 기동
121
- # ─────────────────────────────────────────────────────────────
122
  if __name__ == "__main__":
123
- port = int(os.environ.get("PORT", 7860))
124
- uvicorn.run("app:app", host="0.0.0.0", port=port, reload=False)
 
 
 
 
 
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>
 
 
 
 
 
 
 
13
  <link rel="stylesheet" href="/static/flipbook.css">
 
 
14
  <script src="/static/three.js"></script>
15
  <script src="/static/iscroll.js"></script>
16
  <script src="/static/mark.js"></script>
17
  <script src="/static/mod3d.js"></script>
18
+ <script src="/static/pdf.js"></script>
19
  <script src="/static/flipbook.js"></script>
20
  <script src="/static/flipbook.book3.js"></script>
21
  <script src="/static/flipbook.scroll.js"></script>
22
  <script src="/static/flipbook.swipe.js"></script>
23
  <script src="/static/flipbook.webgl.js"></script>
 
24
  <style>
25
+ body{margin:0;background:#f0f0f0;font-family:sans-serif}
26
+ header{max-width:960px;margin:0 auto;padding:18px 20px;display:flex;align-items:center}
27
+ #homeBtn{display:none;width:38px;height:38px;border:none;border-radius:50%;cursor:pointer;
28
+ background:#0077c2;color:#fff;font-size:20px;margin-right:12px}
29
+ #homeBtn:hover{background:#005999}
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>
41
+ <button id="homeBtn" title="ν™ˆμœΌλ‘œ">βŒ‚</button>
42
+ <h2>My FlipBook Projects</h2>
43
+ </header>
44
+
45
+ <section id="home">
46
+ <div>
47
+ <label class="upload">πŸ“· 이미지 <input id="imgInput" type="file" accept="image/*" multiple hidden></label>
48
+ <label class="upload">πŸ“„ PDF <input id="pdfInput" type="file" accept="application/pdf" hidden></label>
49
+ </div>
50
+ <div class="grid" id="grid"></div>
51
+ </section>
52
+
53
+ <section id="viewerPage" style="display:none">
54
+ <div id="viewer"></div>
55
+ </section>
56
 
57
  <script>
58
+ 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')
65
+ .play().then(a=>a.pause()).catch(()=>{});document.removeEventListener(evt,u,{capture:true});},
66
+ {once:true,capture:true});
67
+ });
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
+ /* ── 이미지 μ—…λ‘œλ“œ ── */
77
+ $id('imgInput').onchange=e=>{
78
+ const files=[...e.target.files]; if(!files.length) return;
79
+ const pages=[],tot=files.length;let done=0;
80
+ files.forEach((f,i)=>{const r=new FileReader();r.onload=x=>{pages[i]={src:x.target.result,thumb:x.target.result};
81
+ if(++done===tot) save(pages);};r.readAsDataURL(f);});
 
 
 
 
 
 
82
  };
83
 
84
+ /* ── PDF μ—…λ‘œλ“œ ── */
85
+ $id('pdfInput').onchange=e=>{
86
+ const file=e.target.files[0]; if(!file) return;
87
+ const fr=new FileReader();
88
+ fr.onload=v=>{
89
+ pdfjsLib.getDocument({data:v.target.result}).promise.then(async pdf=>{
90
+ const pages=[];
91
+ for(let p=1;p<=pdf.numPages;p++){
92
+ const pg=await pdf.getPage(p), vp=pg.getViewport({scale:1});
93
+ const c=document.createElement('canvas');c.width=vp.width;c.height=vp.height;
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
+ /* ── λ„€λΉ„κ²Œμ΄μ…˜ ── */
119
+ $id('homeBtn').onclick=()=>toggle(true);
120
+ function toggle(showHome){
121
+ $id('home').style.display=showHome?'block':'none';
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)))