PDF변환 추가

This commit is contained in:
root
2026-05-07 17:45:54 +09:00
parent c3cb7a6e8f
commit 148d8b3483
9 changed files with 960 additions and 189 deletions

View File

@@ -12,6 +12,7 @@ from auth import (authenticate, create_access_token, init_users,
list_users, create_user, update_user, delete_user)
from tasks import celery_app, transcribe_task, subtitle_pipeline_task
from ocr_tasks import ocr_task
from pdf_tasks import pdf_convert_task
app = FastAPI(title="VoiceScript API")
@@ -32,6 +33,7 @@ os.makedirs(OUTPUT_DIR, exist_ok=True)
AUDIO_EXT = {"mp3","mp4","wav","m4a","ogg","flac","aac","wma","webm",
"mkv","avi","mov","ts","mts","m2ts","wmv","flv","h264","h265","hevc","264","265","m4v"}
IMAGE_EXT = {"jpg","jpeg","png","bmp","tiff","tif","webp","gif"}
PDF_FMT = {"html","docx","xlsx","pptx"}
_DEFAULT_SETTINGS = {
"stt_ollama_model":"","ocr_ollama_model":"granite3.2-vision:latest",
@@ -122,6 +124,13 @@ def _update_history_by_task(task_id:str, result:dict, success:bool, error_msg:st
"srt_trans":result.get("srt_trans",""),
"vtt_trans":result.get("vtt_trans",""),
}
elif h["type"]=="pdf":
h["output"]={
"output_file":result.get("output_file",""),
"target_fmt":result.get("target_fmt",""),
"file_size":result.get("file_size",0),
"pdf_name":result.get("pdf_name",""),
}
else:
ft=result.get("full_text","")
h["output"]={
@@ -307,6 +316,44 @@ async def transcribe_batch(request:Request,files:List[UploadFile]=File(...),
# ════════════════════════════════════════════════════════════════
# 자막
# ════════════════════════════════════════════════════════════════
async def _dispatch_subtitle(request,files,src_language,subtitle_fmt,stt_engine,
refine_model,refine_via,translate_to,trans_model,trans_via,user):
if subtitle_fmt not in ("srt","vtt","both"): subtitle_fmt="srt"
s=_load_settings()
if not stt_engine: stt_engine=s.get("default_stt_engine","local")
_rm=refine_model if refine_model.strip() else (
s.get("openrouter_stt_model","") if refine_via=="openrouter" else s.get("stt_ollama_model",""))
_tm=trans_model if trans_model.strip() else (
s.get("openrouter_stt_model","") if trans_via=="openrouter" else s.get("stt_ollama_model",""))
subtitle_timeout=int(s.get("subtitle_timeout",600))
results=[]
for file in files:
_check_size(request)
ext=_ext(file.filename)
if ext not in AUDIO_EXT:
results.append({"error":f"{file.filename}: 지원하지 않는 형식","filename":file.filename}); continue
file_id=str(uuid.uuid4())
save_path=os.path.join(UPLOAD_DIR,f"{file_id}.{ext}")
await _save_upload(file,save_path)
file_size=os.path.getsize(save_path)
task=subtitle_pipeline_task.delay(
file_id,save_path,src_language,subtitle_fmt,
stt_engine,s.get("groq_api_key",""),s.get("openai_api_key",""),
_rm,refine_via,translate_to,_tm,trans_via,
s.get("openrouter_url",""),s.get("openrouter_api_key",""),
subtitle_timeout,
)
append_history({"id":file_id,"task_id":task.id,"type":"subtitle","status":"processing",
"timestamp":datetime.now().strftime("%Y-%m-%d %H:%M:%S"),"username":user["username"],
"input":{"filename":file.filename,"size_bytes":file_size,"format":ext.upper()},
"settings":{"src_language":src_language or "auto","subtitle_fmt":subtitle_fmt,
"stt_engine":stt_engine,"refine_model":_rm,"refine_via":refine_via,
"translate_to":translate_to,"trans_model":_tm,"trans_via":trans_via,
"subtitle_timeout":subtitle_timeout},
"output":None})
results.append({"task_id":task.id,"file_id":file_id,"filename":file.filename})
return results
@app.post("/api/subtitle")
async def create_subtitle(
request:Request, file:UploadFile=File(...),
@@ -316,39 +363,25 @@ async def create_subtitle(
translate_to:str=Form(""),trans_model:str=Form(""),trans_via:str=Form("ollama"),
user:dict=Depends(require_subtitle),
):
_check_size(request)
ext=_ext(file.filename)
if ext not in AUDIO_EXT: raise HTTPException(400,"지원하지 않는 형식입니다")
if subtitle_fmt not in ("srt","vtt","both"): subtitle_fmt="srt"
s=_load_settings()
if not stt_engine: stt_engine=s.get("default_stt_engine","local")
if not refine_model.strip():
refine_model=(s.get("openrouter_stt_model","") if refine_via=="openrouter"
else s.get("stt_ollama_model",""))
if not trans_model.strip():
trans_model=(s.get("openrouter_stt_model","") if trans_via=="openrouter"
else s.get("stt_ollama_model",""))
file_id=str(uuid.uuid4())
save_path=os.path.join(UPLOAD_DIR,f"{file_id}.{ext}")
await _save_upload(file,save_path)
file_size=os.path.getsize(save_path)
subtitle_timeout=int(s.get("subtitle_timeout",600))
task=subtitle_pipeline_task.delay(
file_id,save_path,src_language,subtitle_fmt,
stt_engine,s.get("groq_api_key",""),s.get("openai_api_key",""),
refine_model,refine_via,translate_to,trans_model,trans_via,
s.get("openrouter_url",""),s.get("openrouter_api_key",""),
subtitle_timeout,
)
append_history({"id":file_id,"task_id":task.id,"type":"subtitle","status":"processing",
"timestamp":datetime.now().strftime("%Y-%m-%d %H:%M:%S"),"username":user["username"],
"input":{"filename":file.filename,"size_bytes":file_size,"format":ext.upper()},
"settings":{"src_language":src_language or "auto","subtitle_fmt":subtitle_fmt,
"stt_engine":stt_engine,"refine_model":refine_model,"refine_via":refine_via,
"translate_to":translate_to,"trans_model":trans_model,"trans_via":trans_via,
"subtitle_timeout":subtitle_timeout},
"output":None})
return {"task_id":task.id,"file_id":file_id,"filename":file.filename}
items=await _dispatch_subtitle(request,[file],src_language,subtitle_fmt,stt_engine,
refine_model,refine_via,translate_to,trans_model,trans_via,user)
if "error" in items[0]: raise HTTPException(400,items[0]["error"])
return items[0]
@app.post("/api/subtitle/batch")
async def create_subtitle_batch(
request:Request, files:List[UploadFile]=File(...),
src_language:str=Form(""),subtitle_fmt:str=Form("srt"),
stt_engine:str=Form("local"),
refine_model:str=Form(""),refine_via:str=Form("ollama"),
translate_to:str=Form(""),trans_model:str=Form(""),trans_via:str=Form("ollama"),
user:dict=Depends(require_subtitle),
):
if not files: raise HTTPException(400,"파일이 없습니다")
if len(files)>10: raise HTTPException(400,"최대 10개까지")
items=await _dispatch_subtitle(request,files,src_language,subtitle_fmt,stt_engine,
refine_model,refine_via,translate_to,trans_model,trans_via,user)
return {"items":items,"total":len(items)}
# ════════════════════════════════════════════════════════════════
@@ -401,6 +434,44 @@ async def ocr_batch(request:Request,files:List[UploadFile]=File(...),
return {"items":items,"total":len(items)}
# ════════════════════════════════════════════════════════════════
# PDF 변환
# ════════════════════════════════════════════════════════════════
async def _dispatch_pdf(request, files, target_fmt, user):
if target_fmt not in PDF_FMT: target_fmt="html"
results=[]
for file in files:
_check_size(request)
if not file.filename.lower().endswith(".pdf"):
results.append({"error":f"{file.filename}: PDF 파일만 지원합니다","filename":file.filename}); continue
file_id=str(uuid.uuid4())
save_path=os.path.join(UPLOAD_DIR,f"{file_id}.pdf")
await _save_upload(file,save_path); file_size=os.path.getsize(save_path)
task=pdf_convert_task.delay(file_id,save_path,target_fmt)
append_history({"id":file_id,"task_id":task.id,"type":"pdf","status":"processing",
"timestamp":datetime.now().strftime("%Y-%m-%d %H:%M:%S"),"username":user["username"],
"input":{"filename":file.filename,"size_bytes":file_size,"format":"PDF"},
"settings":{"target_fmt":target_fmt},
"output":None})
results.append({"task_id":task.id,"file_id":file_id,"filename":file.filename})
return results
@app.post("/api/pdf/convert")
async def pdf_convert(request:Request,file:UploadFile=File(...),
target_fmt:str=Form("html"),user:dict=Depends(require_auth)):
items=await _dispatch_pdf(request,[file],target_fmt,user)
if "error" in items[0]: raise HTTPException(400,items[0]["error"])
return items[0]
@app.post("/api/pdf/convert/batch")
async def pdf_convert_batch(request:Request,files:List[UploadFile]=File(...),
target_fmt:str=Form("html"),user:dict=Depends(require_auth)):
if not files: raise HTTPException(400,"파일이 없습니다")
if len(files)>10: raise HTTPException(400,"최대 10개까지")
items=await _dispatch_pdf(request,files,target_fmt,user)
return {"items":items,"total":len(items)}
# ════════════════════════════════════════════════════════════════
# 이력
# ════════════════════════════════════════════════════════════════
@@ -431,6 +502,9 @@ def download(filename:str,user:dict=Depends(require_auth)):
path=os.path.join(OUTPUT_DIR,filename)
if not os.path.exists(path): raise HTTPException(404,"파일을 찾을 수 없습니다")
if filename.endswith(".xlsx"): media="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
elif filename.endswith(".docx"): media="application/vnd.openxmlformats-officedocument.wordprocessingml.document"
elif filename.endswith(".pptx"): media="application/vnd.openxmlformats-officedocument.presentationml.presentation"
elif filename.endswith(".html"): media="text/html; charset=utf-8"
elif filename.endswith(".vtt"): media="text/vtt"
elif filename.endswith(".srt"): media="text/plain; charset=utf-8"
else: media="text/plain; charset=utf-8"