PDF변환 추가
This commit is contained in:
140
app/main.py
140
app/main.py
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user