AI-BOOK / app.py
ginipick's picture
Update app.py
66aed24 verified
raw
history blame
5.23 kB
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
import pathlib, os, uvicorn
BASE = pathlib.Path(__file__).parent
app = FastAPI()
app.mount("/static", StaticFiles(directory=BASE), name="static")
HTML = """
<!doctype html><html lang="ko"><head>
<meta charset="utf-8"><title>FlipBook Space</title>
<link rel="stylesheet" href="/static/flipbook.css">
<script src="/static/three.js"></script>
<script src="/static/iscroll.js"></script>
<script src="/static/mark.js"></script>
<script src="/static/mod3d.js"></script>
<script src="/static/pdf.js"></script>
<script src="/static/flipbook.js"></script>
<script src="/static/flipbook.book3.js"></script>
<script src="/static/flipbook.scroll.js"></script>
<script src="/static/flipbook.swipe.js"></script>
<script src="/static/flipbook.webgl.js"></script>
<style>
body{margin:0;background:#f0f0f0;font-family:sans-serif}
header{max-width:960px;margin:0 auto;padding:18px 20px;display:flex;align-items:center}
#homeBtn{display:none;width:38px;height:38px;border:none;border-radius:50%;cursor:pointer;
background:#0077c2;color:#fff;font-size:20px;margin-right:12px}
#homeBtn:hover{background:#005999}
h2{margin:0;font-size:1.5rem;font-weight:600}
#home,#viewerPage{max-width:960px;margin:0 auto;padding:0 20px 40px}
.grid{display:grid;grid-template-columns:repeat(auto-fill,180px);gap:16px;margin-top:24px}
.card{background:#fff;border:1px solid #ccc;border-radius:6px;cursor:pointer;box-shadow:0 2px 4px rgba(0,0,0,.12)}
.card img{width:100%;height:140px;object-fit:cover}
.card p{text-align:center;margin:6px 0}
button.upload{all:unset;cursor:pointer;border:1px solid #bbb;padding:8px 14px;border-radius:6px;background:#fff;margin:0 8px}
#viewer{width:100%;max-width:1200px;height:80vh;margin:24px auto;background:#fff;border:1px solid #ccc}
</style></head><body>
<header>
<button id="homeBtn" title="ν™ˆμœΌλ‘œ">βŒ‚</button>
<h2>My FlipBook Projects</h2>
</header>
<section id="home">
<div>
<label class="upload">πŸ“· 이미지 <input id="imgInput" type="file" accept="image/*" multiple hidden></label>
<label class="upload">πŸ“„ PDF <input id="pdfInput" type="file" accept="application/pdf" hidden></label>
</div>
<div class="grid" id="grid"></div>
</section>
<section id="viewerPage" style="display:none">
<div id="viewer"></div>
</section>
<script>
let projects=[], fb=null;
const grid=$id('grid'), viewer=$id('viewer');
pdfjsLib.GlobalWorkerOptions.workerSrc='/static/pdf.worker.js';
/* πŸ”Š μ˜€λ””μ˜€ unlock – λ‚΄μž₯ Audio 와 같은 MP3 경둜 μ‚¬μš© */
['click','touchstart'].forEach(evt=>{
document.addEventListener(evt,function u(){new Audio('static/turnPage2.mp3')
.play().then(a=>a.pause()).catch(()=>{});document.removeEventListener(evt,u,{capture:true});},
{once:true,capture:true});
});
/* ── μœ ν‹Έ ── */
function $id(id){return document.getElementById(id)}
function addCard(i,thumb){
const d=document.createElement('div');d.className='card';d.onclick=()=>open(i);
d.innerHTML=`<img src="${thumb}"><p>ν”„λ‘œμ νŠΈ ${i+1}</p>`;grid.appendChild(d);
}
/* ── 이미지 μ—…λ‘œλ“œ ── */
$id('imgInput').onchange=e=>{
const files=[...e.target.files]; if(!files.length) return;
const pages=[],tot=files.length;let done=0;
files.forEach((f,i)=>{const r=new FileReader();r.onload=x=>{pages[i]={src:x.target.result,thumb:x.target.result};
if(++done===tot) save(pages);};r.readAsDataURL(f);});
};
/* ── PDF μ—…λ‘œλ“œ ── */
$id('pdfInput').onchange=e=>{
const file=e.target.files[0]; if(!file) return;
const fr=new FileReader();
fr.onload=v=>{
pdfjsLib.getDocument({data:v.target.result}).promise.then(async pdf=>{
const pages=[];
for(let p=1;p<=pdf.numPages;p++){
const pg=await pdf.getPage(p), vp=pg.getViewport({scale:1});
const c=document.createElement('canvas');c.width=vp.width;c.height=vp.height;
await pg.render({canvasContext:c.getContext('2d'),viewport:vp}).promise;
pages.push({src:c.toDataURL(),thumb:c.toDataURL()});
}
save(pages);
});
};fr.readAsArrayBuffer(file);
};
/* ── ν”„λ‘œμ νŠΈ μ €μž₯ ── */
function save(pages){const id=projects.push(pages)-1;addCard(id,pages[0].thumb);}
/* ── μΉ΄λ“œ β†’ FlipBook ── */
function open(i){
toggle(false);
const pages=projects[i];
if(fb){fb.destroy();viewer.innerHTML='';}
fb=new FlipBook(viewer,{
pages,viewMode:'webgl',autoSize:true,flipDuration:800,backgroundColor:'#fff',
/* πŸ”Š λ‚΄μž₯ μ‚¬μš΄λ“œ */
sound:true,
assets:{flipMp3:'static/turnPage2.mp3',hardFlipMp3:'static/turnPage2.mp3'},
controlsProps:{enableFullscreen:true,thumbnails:true}});
}
/* ── λ„€λΉ„κ²Œμ΄μ…˜ ── */
$id('homeBtn').onclick=()=>toggle(true);
function toggle(showHome){
$id('home').style.display=showHome?'block':'none';
$id('viewerPage').style.display=showHome?'none':'block';
$id('homeBtn').style.display=showHome?'none':'inline-block';
}
</script>
</body></html>
"""
@app.get("/", response_class=HTMLResponse)
async def root():
return HTML
if __name__ == "__main__":
uvicorn.run("app:app", host="0.0.0.0", port=int(os.getenv("PORT", 7860)))