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, base64 | |
BASE = pathlib.Path(__file__).parent | |
app = FastAPI() | |
app.mount("/static", StaticFiles(directory=BASE), name="static") | |
# PDF ๋๋ ํ ๋ฆฌ ์ค์ | |
PDF_DIR = BASE / "pdf" | |
if not PDF_DIR.exists(): | |
PDF_DIR.mkdir(parents=True) | |
# PDF ํ์ผ ๋ชฉ๋ก ๊ฐ์ ธ์ค๊ธฐ | |
def get_pdf_files(): | |
pdf_files = [] | |
if PDF_DIR.exists(): | |
pdf_files = [f for f in PDF_DIR.glob("*.pdf")] | |
return pdf_files | |
# PDF ์ธ๋ค์ผ ์์ฑ ๋ฐ ํ๋ก์ ํธ ๋ฐ์ดํฐ ์ค๋น | |
def generate_pdf_projects(): | |
projects_data = [] | |
pdf_files = get_pdf_files() | |
for pdf_file in pdf_files: | |
projects_data.append({ | |
"path": str(pdf_file), | |
"name": pdf_file.stem | |
}) | |
return projects_data | |
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 url('/static/book2.jpg') no-repeat center center; | |
background-size: 169%; /* ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง๋ฅผ ํ์ฌ๋ณด๋ค 30% ๋ ํค์ (130% * 1.3 = 169%) */ | |
border:1px solid #ccc; | |
border-radius:6px; | |
cursor:pointer; | |
box-shadow:0 2px 4px rgba(0,0,0,.12); | |
width: 180px; | |
height: 240px; | |
position: relative; | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
justify-content: center; | |
} | |
.card img{ | |
width:55%; /* ์ธ๋ค์ผ ํฌ๊ธฐ ์ค์ (๋ฐฐ๊ฒฝ์ด ์ปค์ ธ์ ๋น์จ ์ ์ง) */ | |
height:auto; | |
object-fit:contain; | |
position:absolute; /* ์ ๋ ์์น๋ก ๋ณ๊ฒฝ */ | |
top:50%; /* ์๋จ์์ 50% */ | |
left:50%; /* ์ข์ธก์์ 50% */ | |
transform: translate(-50%, -70%); /* ์ ์ค์์์ 20% ์๋ก ์ด๋ */ | |
border: 1px solid #ddd; | |
box-shadow: 0 2px 5px rgba(0,0,0,0.2); | |
} | |
.card p{ | |
text-align:center; | |
margin:6px 0; | |
position: absolute; | |
bottom: 10px; | |
left: 50%; | |
transform: translateX(-50%); | |
background: rgba(255, 255, 255, 0.7); | |
padding: 4px 8px; | |
border-radius: 4px; | |
width: 85%; | |
white-space: nowrap; | |
overflow: hidden; | |
text-overflow: ellipsis; | |
max-width: 150px; | |
font-size: 11px; /* ๊ธฐ๋ณธ 16px์์ ์ฝ 30% ๊ฐ์ */ | |
} | |
button.upload{all:unset;cursor:pointer;border:1px solid #bbb;padding:8px 14px;border-radius:6px;background:#fff;margin:0 8px} | |
#viewer{ | |
width:90%; | |
height:90vh; | |
max-width:90%; | |
margin:0; | |
background:#fff; | |
border:1px solid #ddd; | |
border-radius:8px; | |
position:fixed; | |
top:50%; | |
left:50%; | |
transform:translate(-50%, -50%); | |
z-index:1000; | |
box-shadow:0 4px 20px rgba(0,0,0,0.15); | |
/* ํ๋ฉด ๋น์จ์ ๋ง๊ฒ ์กฐ์ */ | |
max-height: calc(90vh - 40px); /* ์ฌ๋ฐฑ ๊ณ ๋ ค */ | |
aspect-ratio: auto; /* ๋น์จ ์๋ ์กฐ์ */ | |
object-fit: contain; /* ๋ด์ฉ๋ฌผ ๋น์จ ์ ์ง */ | |
overflow: hidden; /* ๋ด์ฉ ๋์นจ ๋ฐฉ์ง */ | |
} | |
/* FlipBook ์ปจํธ๋กค๋ฐ ๊ด๋ จ ์คํ์ผ ์ค๋ฒ๋ผ์ด๋ */ | |
.flipbook-container .fb3d-menu-bar { | |
z-index: 2000 !important; /* ์ปจํธ๋กค๋ฐ๊ฐ ๋ค๋ฅธ ์์๋ณด๋ค ์์ ์ค๋๋ก ํจ */ | |
opacity: 1 !important; /* ํญ์ ํ์๋๋๋ก ํจ */ | |
bottom: 0 !important; /* ํ๋จ์ ๊ณ ์ */ | |
background-color: rgba(0,0,0,0.7) !important; /* ๋ฐฐ๊ฒฝ์ ์ค์ */ | |
border-radius: 0 0 8px 8px !important; /* ํ๋จ ๋ชจ์๋ฆฌ๋ง ๋ฅ๊ธ๊ฒ */ | |
padding: 8px 0 !important; /* ์ํ ํจ๋ฉ ์ถ๊ฐ */ | |
} | |
.flipbook-container .fb3d-menu-bar > ul > li > img, | |
.flipbook-container .fb3d-menu-bar > ul > li > div { | |
opacity: 1 !important; /* ๋ฉ๋ด ์์ด์ฝ ํญ์ ํ์ */ | |
transform: scale(1.2) !important; /* ์์ด์ฝ ํฌ๊ธฐ ์ฝ๊ฐ ํค์ */ | |
} | |
.flipbook-container .fb3d-menu-bar > ul > li { | |
margin: 0 8px !important; /* ๋ฉ๋ด ์์ดํ ๊ฐ๊ฒฉ ์กฐ์ */ | |
} | |
/* ๋ฉ๋ด ํดํ ์คํ์ผ ๊ฐ์ */ | |
.flipbook-container .fb3d-menu-bar > ul > li > span { | |
background-color: rgba(0,0,0,0.8) !important; | |
color: white !important; | |
border-radius: 4px !important; | |
padding: 4px 8px !important; | |
font-size: 12px !important; | |
bottom: 45px !important; /* ํดํ ์์น ์กฐ์ */ | |
} | |
</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'; | |
// ์๋ฒ์์ ๋ฏธ๋ฆฌ ๋ก๋๋ PDF ํ๋ก์ ํธ | |
let serverProjects = []; | |
/* ๐ ์ค๋์ค 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,title){ | |
const d=document.createElement('div'); | |
d.className='card'; | |
d.onclick=()=>open(i); | |
// ์ ๋ชฉ 10๊ธ์ ์ ํ ๋ฐ ๋ง์ค์ํ ์ฒ๋ฆฌ | |
const displayTitle = title ? | |
(title.length > 10 ? title.substring(0, 10) + '...' : title) : | |
'ํ๋ก์ ํธ ' + (i+1); | |
d.innerHTML=`<img src="${thumb}"><p title="${title || 'ํ๋ก์ ํธ ' + (i+1)}">${displayTitle}</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, file.name.replace('.pdf', '')); | |
}); | |
};fr.readAsArrayBuffer(file); | |
}; | |
/* โโ ํ๋ก์ ํธ ์ ์ฅ โโ */ | |
function save(pages, title){ | |
const id=projects.push(pages)-1; | |
addCard(id,pages[0].thumb, title); | |
} | |
/* โโ ์๋ฒ PDF ๋ก๋ โโ */ | |
async function loadServerPDFs() { | |
try { | |
const response = await fetch('/api/pdf-projects'); | |
serverProjects = await response.json(); | |
// ์๋ฒ PDF ๋ก๋ ๋ฐ ์ธ๋ค์ผ ์์ฑ | |
for(let i = 0; i < serverProjects.length; i++) { | |
const project = serverProjects[i]; | |
const response = await fetch(`/api/pdf-thumbnail?path=${encodeURIComponent(project.path)}`); | |
const data = await response.json(); | |
if(data.thumbnail) { | |
const pages = [{ | |
src: data.thumbnail, | |
thumb: data.thumbnail, | |
path: project.path | |
}]; | |
save(pages, project.name); | |
} | |
} | |
} catch(error) { | |
console.error('์๋ฒ PDF ๋ก๋ ์คํจ:', error); | |
} | |
} | |
/* โโ ์นด๋ โ FlipBook โโ */ | |
function open(i){ | |
toggle(false); | |
const pages = projects[i]; | |
// ๋ก์ปฌ ํ๋ก์ ํธ ๋๋ ์๋ฒ PDF ๋ก๋ | |
if(fb){fb.destroy();viewer.innerHTML='';} | |
if(pages[0].path) { | |
// ๋ก๋ฉ ํ์ | |
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>'; | |
// ์คํ์ผ ์ถ๊ฐ | |
if (!document.getElementById('loadingStyle')) { | |
const style = document.createElement('style'); | |
style.id = 'loadingStyle'; | |
style.textContent = '@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}'; | |
document.head.appendChild(style); | |
} | |
// ์๋ฒ PDF ํ์ผ ๋ก๋ | |
fetch(`/api/pdf-content?path=${encodeURIComponent(pages[0].path)}`) | |
.then(response => { | |
if (!response.ok) { | |
throw new Error('PDF ๋ก๋ ์คํจ: ' + response.statusText); | |
} | |
return response.arrayBuffer(); | |
}) | |
.then(pdfData => { | |
// PDF ๋ฐ์ดํฐ ๋ก๋ ํ์ธ ๋ก๊น | |
console.log('PDF ๋ฐ์ดํฐ ๋ก๋ ์๋ฃ:', pdfData.byteLength + ' ๋ฐ์ดํธ'); | |
return pdfjsLib.getDocument({data: pdfData}).promise; | |
}) | |
.then(async pdf => { | |
console.log('PDF ๋ฌธ์ ๋ก๋ ์๋ฃ. ํ์ด์ง ์:', pdf.numPages); | |
const pdfPages = []; | |
const progressElement = viewer.querySelector('p'); | |
for(let p = 1; p <= pdf.numPages; p++) { | |
if (progressElement) { | |
progressElement.textContent = `PDF ํ์ด์ง ๋ก๋ฉ ์ค... (${p}/${pdf.numPages})`; | |
} | |
try { | |
const pg = await pdf.getPage(p); | |
const 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; | |
pdfPages.push({src: c.toDataURL(), thumb: c.toDataURL()}); | |
} catch (pageError) { | |
console.error(`ํ์ด์ง ${p} ๋ ๋๋ง ์ค๋ฅ:`, pageError); | |
} | |
} | |
console.log('๋ชจ๋ ํ์ด์ง ๋ ๋๋ง ์๋ฃ:', pdfPages.length); | |
if (pdfPages.length > 0) { | |
createFlipBook(pdfPages); | |
} else { | |
throw new Error('PDF์์ ํ์ด์ง๋ฅผ ์ถ์ถํ ์ ์์ต๋๋ค.'); | |
} | |
}) | |
.catch(error => { | |
console.error('PDF ์ฒ๋ฆฌ ์ค ์ค๋ฅ ๋ฐ์:', error); | |
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>`; | |
document.getElementById('backBtn').addEventListener('click', function() { | |
toggle(true); | |
}); | |
}); | |
} else { | |
// ์ ๋ก๋๋ ํ๋ก์ ํธ ๋ณด๊ธฐ | |
console.log('๋ก์ปฌ ์ ๋ก๋๋ ํ๋ก์ ํธ ๋ ๋๋ง:', pages.length + 'ํ์ด์ง'); | |
createFlipBook(pages); | |
} | |
} | |
function createFlipBook(pages) { | |
console.log('FlipBook ์์ฑ ์์. ํ์ด์ง ์:', pages.length); | |
try { | |
// ํ๋ฉด ๋น์จ ๊ณ์ฐ | |
const calculateAspectRatio = () => { | |
const windowWidth = window.innerWidth; | |
const windowHeight = window.innerHeight; | |
const aspectRatio = windowWidth / windowHeight; | |
// ๋๋น ๋๋ ๋์ด ๊ธฐ์ค์ผ๋ก ์ต๋ 90% ์ ํ | |
let width, height; | |
if (aspectRatio > 1) { // ๊ฐ๋ก ํ๋ฉด | |
height = Math.min(windowHeight * 0.9, windowHeight - 40); | |
width = height * aspectRatio * 0.8; // ๊ฐ๋ก ํ๋ฉด์์๋ ์ฝ๊ฐ ์ค์ | |
if (width > windowWidth * 0.9) { | |
width = windowWidth * 0.9; | |
height = width / (aspectRatio * 0.8); | |
} | |
} else { // ์ธ๋ก ํ๋ฉด | |
width = Math.min(windowWidth * 0.9, windowWidth - 40); | |
height = width / aspectRatio * 0.9; // ์ธ๋ก ํ๋ฉด์์๋ ์ฝ๊ฐ ๋๋ฆผ | |
if (height > windowHeight * 0.9) { | |
height = windowHeight * 0.9; | |
width = height * aspectRatio * 0.9; | |
} | |
} | |
// ์ต์ ์ฌ์ด์ฆ ๋ฐํ | |
return { | |
width: Math.round(width), | |
height: Math.round(height) | |
}; | |
}; | |
// ์ด๊ธฐ ํ๋ฉด ๋น์จ ๊ณ์ฐ | |
const size = calculateAspectRatio(); | |
viewer.style.width = size.width + 'px'; | |
viewer.style.height = size.height + 'px'; | |
fb = new FlipBook(viewer, { | |
pages: pages, | |
viewMode: 'webgl', | |
autoSize: true, | |
flipDuration: 800, | |
backgroundColor: '#fff', | |
/* ๐ ๋ด์ฅ ์ฌ์ด๋ */ | |
sound: true, | |
assets: {flipMp3: 'static/turnPage2.mp3', hardFlipMp3: 'static/turnPage2.mp3'}, | |
controlsProps: { | |
enableFullscreen: true, | |
enableToc: true, | |
enableDownload: false, | |
enablePrint: false, | |
enableZoom: true, | |
enableShare: false, | |
enableSearch: true, | |
enableAutoPlay: true, | |
enableAnnotation: false, | |
enableSound: true, | |
enableLightbox: false, | |
layout: 10, // ๋ ์ด์์ ์ต์ | |
skin: 'light', // ์คํจ ์คํ์ผ | |
autoNavigationTime: 3600, // ์๋ ๋๊น ์๊ฐ(์ด) | |
hideControls: false, // ์ปจํธ๋กค ์จ๊น ๋นํ์ฑํ | |
paddingTop: 10, // ์๋จ ํจ๋ฉ | |
paddingLeft: 10, // ์ข์ธก ํจ๋ฉ | |
paddingRight: 10, // ์ฐ์ธก ํจ๋ฉ | |
paddingBottom: 10, // ํ๋จ ํจ๋ฉ | |
pageTextureSize: 1024, // ํ์ด์ง ํ ์ค์ฒ ํฌ๊ธฐ | |
thumbnails: true, // ์ฌ๋ค์ผ ํ์ฑํ | |
autoHideControls: false, // ์๋ ์จ๊น ๋นํ์ฑํ | |
controlsTimeout: 8000 // ์ปจํธ๋กค ํ์ ์๊ฐ ์ฐ์ฅ | |
} | |
}); | |
// ํ๋ฉด ํฌ๊ธฐ ๋ณ๊ฒฝ ์ FlipBook ํฌ๊ธฐ ์กฐ์ | |
window.addEventListener('resize', () => { | |
if (fb) { | |
const newSize = calculateAspectRatio(); | |
viewer.style.width = newSize.width + 'px'; | |
viewer.style.height = newSize.height + 'px'; | |
fb.resize(); | |
} | |
}); | |
// FlipBook ์์ฑ ํ ์ปจํธ๋กค๋ฐ ๊ฐ์ ํ์ | |
setTimeout(() => { | |
try { | |
// ์ปจํธ๋กค๋ฐ ๊ด๋ จ ์์ ์ฐพ๊ธฐ ๋ฐ ์คํ์ผ ์ ์ฉ | |
const menuBars = document.querySelectorAll('.flipbook-container .fb3d-menu-bar'); | |
if (menuBars && menuBars.length > 0) { | |
menuBars.forEach(menuBar => { | |
menuBar.style.display = 'block'; | |
menuBar.style.opacity = '1'; | |
menuBar.style.visibility = 'visible'; | |
menuBar.style.zIndex = '9999'; | |
}); | |
} | |
} catch (e) { | |
console.warn('์ปจํธ๋กค๋ฐ ์คํ์ผ ์ ์ฉ ์ค ์ค๋ฅ:', e); | |
} | |
}, 1000); | |
console.log('FlipBook ์์ฑ ์๋ฃ'); | |
} catch (error) { | |
console.error('FlipBook ์์ฑ ์ค ์ค๋ฅ ๋ฐ์:', error); | |
alert('FlipBook์ ์์ฑํ๋ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: ' + error.message); | |
} | |
} | |
/* โโ ๋ค๋น๊ฒ์ด์ โโ */ | |
$id('homeBtn').onclick=()=>{ | |
if(fb) { | |
fb.destroy(); | |
viewer.innerHTML = ''; | |
fb = null; | |
} | |
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'; | |
// ์ถ๊ฐ: ์ ์ฒด ํ๋ฉด ๋ชจ๋์์ homeBtn ์์น ์กฐ์ | |
if(!showHome) { | |
$id('homeBtn').style.position = 'fixed'; | |
$id('homeBtn').style.top = '20px'; | |
$id('homeBtn').style.left = '20px'; | |
$id('homeBtn').style.zIndex = '9999'; // ์ต์์ z-index๋ก ๋ณ๊ฒฝ | |
$id('homeBtn').style.fontSize = '24px'; // ํฌ๊ธฐ ์ฆ๊ฐ | |
$id('homeBtn').style.width = '48px'; | |
$id('homeBtn').style.height = '48px'; | |
$id('homeBtn').style.boxShadow = '0 2px 10px rgba(0,0,0,0.2)'; // ๊ทธ๋ฆผ์ ์ถ๊ฐ | |
// ๋ฐฐ๊ฒฝ ์ค๋ฒ๋ ์ด ์ถ๊ฐ | |
document.body.style.backgroundColor = '#3a3a3a'; | |
} else { | |
$id('homeBtn').style.position = ''; | |
$id('homeBtn').style.top = ''; | |
$id('homeBtn').style.left = ''; | |
$id('homeBtn').style.zIndex = ''; | |
$id('homeBtn').style.fontSize = ''; | |
$id('homeBtn').style.width = ''; | |
$id('homeBtn').style.height = ''; | |
$id('homeBtn').style.boxShadow = ''; | |
// ๋ฐฐ๊ฒฝ ์๋๋๋ก | |
document.body.style.backgroundColor = '#f0f0f0'; | |
} | |
} | |
// ํ์ด์ง ๋ก๋ ์ ์๋ฒ PDF ๋ก๋ | |
window.addEventListener('DOMContentLoaded', loadServerPDFs); | |
</script> | |
</body></html> | |
""" | |
# API ์๋ํฌ์ธํธ: PDF ํ๋ก์ ํธ ๋ชฉ๋ก | |
async def get_pdf_projects(): | |
return generate_pdf_projects() | |
# API ์๋ํฌ์ธํธ: PDF ์ธ๋ค์ผ ์์ฑ | |
async def get_pdf_thumbnail(path: str): | |
try: | |
import fitz # PyMuPDF | |
# PDF ํ์ผ ์ด๊ธฐ | |
doc = fitz.open(path) | |
# ์ฒซ ํ์ด์ง ๊ฐ์ ธ์ค๊ธฐ | |
if doc.page_count > 0: | |
page = doc[0] | |
# ์ธ๋ค์ผ์ฉ ์ด๋ฏธ์ง ๋ ๋๋ง (ํด์๋ ์กฐ์ ) | |
pix = page.get_pixmap(matrix=fitz.Matrix(0.5, 0.5)) | |
img_data = pix.tobytes("png") | |
# Base64 ์ธ์ฝ๋ฉ | |
b64_img = base64.b64encode(img_data).decode('utf-8') | |
return {"thumbnail": f"data:image/png;base64,{b64_img}"} | |
return {"thumbnail": None} | |
except Exception as e: | |
return {"error": str(e), "thumbnail": None} | |
async def get_pdf_content(path: str): | |
try: | |
# ํ์ผ ์กด์ฌ ์ฌ๋ถ ํ์ธ | |
pdf_path = pathlib.Path(path) | |
if not pdf_path.exists(): | |
return {"error": f"ํ์ผ์ ์ฐพ์ ์ ์์ต๋๋ค: {path}"}, 404 | |
# ํ์ผ ์ฝ๊ธฐ | |
with open(path, "rb") as pdf_file: | |
content = pdf_file.read() | |
# ํ์ผ๋ช ์ฒ๋ฆฌ - URL ์ธ์ฝ๋ฉ์ผ๋ก ํ๊ธ ๋ฑ ํน์ ๋ฌธ์ ์ฒ๋ฆฌ | |
import urllib.parse | |
filename = pdf_path.name | |
encoded_filename = urllib.parse.quote(filename) | |
# ์๋ต ํค๋ ์ค์ - RFC 6266 ํ์ค ์ฌ์ฉ | |
headers = { | |
"Content-Type": "application/pdf", | |
"Content-Disposition": f"inline; filename=\"{encoded_filename}\"; filename*=UTF-8''{encoded_filename}" | |
} | |
# ํ์ผ ์ฝํ ์ธ ์ง์ ๋ฐํ (dict๊ฐ ์๋ Response ๊ฐ์ฒด) | |
from fastapi.responses import Response | |
return Response(content=content, media_type="application/pdf") | |
except Exception as e: | |
import traceback | |
error_details = traceback.format_exc() | |
print(f"PDF ์ฝํ ์ธ ๋ก๋ ์ค๋ฅ: {str(e)}\n{error_details}") | |
# ์ค๋ฅ ์๋ต ๋ฐํ (JSON ํ์) | |
from fastapi.responses import JSONResponse | |
return JSONResponse(content={"error": str(e)}, status_code=500) | |
async def root(): | |
return HTML | |
if __name__ == "__main__": | |
uvicorn.run("app:app", host="0.0.0.0", port=int(os.getenv("PORT", 7860))) |