Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
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> | |
""" | |
async def root(): | |
return HTML | |
if __name__ == "__main__": | |
uvicorn.run("app:app", host="0.0.0.0", port=int(os.getenv("PORT", 7860))) | |