feat: VoiceScript STT+OCR 초기 버전

This commit is contained in:
root
2026-04-20 06:15:35 +09:00
commit ddd51da26e
11 changed files with 2163 additions and 0 deletions

97
app/tasks.py Normal file
View File

@@ -0,0 +1,97 @@
import os
from celery import Celery
# ocr_task를 직접 import해서 worker에 등록
from ocr_tasks import ocr_task # noqa: F401
REDIS_URL = os.getenv("REDIS_URL", "redis://redis:6379/0")
MODEL_SIZE = os.getenv("WHISPER_MODEL", "medium")
DEVICE = os.getenv("WHISPER_DEVICE", "cpu")
COMPUTE_TYPE = os.getenv("WHISPER_COMPUTE_TYPE", "int8")
LANGUAGE = os.getenv("WHISPER_LANGUAGE", "ko") or None
BEAM_SIZE = int(os.getenv("WHISPER_BEAM_SIZE", "5"))
INITIAL_PROMPT = os.getenv("WHISPER_INITIAL_PROMPT", "") or None
OUTPUT_DIR = os.getenv("OUTPUT_DIR", "/data/outputs")
celery_app = Celery("whisper_tasks", broker=REDIS_URL, backend=REDIS_URL)
celery_app.conf.update(
task_serializer="json",
result_serializer="json",
accept_content=["json"],
task_track_started=True,
result_expires=3600,
)
_model = None
def get_model():
global _model
if _model is None:
from faster_whisper import WhisperModel
print(f"[Whisper] 로딩: {MODEL_SIZE} / {DEVICE} / {COMPUTE_TYPE}")
_model = WhisperModel(MODEL_SIZE, device=DEVICE, compute_type=COMPUTE_TYPE)
print("[Whisper] 로드 완료")
return _model
@celery_app.task(bind=True, name="tasks.transcribe_task", queue="stt")
def transcribe_task(self, file_id: str, audio_path: str):
self.update_state(state="PROGRESS", meta={"progress": 5, "message": "모델 준비 중..."})
try:
model = get_model()
self.update_state(state="PROGRESS", meta={"progress": 15, "message": "오디오 분석 중..."})
segments_gen, info = model.transcribe(
audio_path,
language=LANGUAGE,
beam_size=BEAM_SIZE,
initial_prompt=INITIAL_PROMPT,
vad_filter=True,
vad_parameters=dict(min_silence_duration_ms=500),
word_timestamps=False,
)
self.update_state(state="PROGRESS", meta={"progress": 30, "message": "텍스트 변환 중..."})
segments, full_text_parts = [], []
duration = info.duration
for seg in segments_gen:
segments.append({"start": round(seg.start, 2),
"end": round(seg.end, 2),
"text": seg.text.strip()})
full_text_parts.append(seg.text.strip())
if duration > 0:
pct = 30 + int((seg.end / duration) * 60)
self.update_state(
state="PROGRESS",
meta={"progress": min(pct, 90),
"message": f"변환 중... {seg.end:.0f}s / {duration:.0f}s"},
)
full_text = "\n".join(full_text_parts)
self.update_state(state="PROGRESS", meta={"progress": 95, "message": "파일 저장 중..."})
os.makedirs(OUTPUT_DIR, exist_ok=True)
output_filename = f"{file_id}.txt"
with open(os.path.join(OUTPUT_DIR, output_filename), "w", encoding="utf-8") as f:
f.write(f"# 변환 결과\n# 언어: {info.language} | 재생 시간: {duration:.1f}\n\n")
f.write("## 전체 텍스트\n\n" + full_text + "\n\n")
f.write("## 타임스탬프별 세그먼트\n\n")
for seg in segments:
f.write(f"[{_fmt(seg['start'])}{_fmt(seg['end'])}] {seg['text']}\n")
try: os.remove(audio_path)
except: pass
return {"text": full_text, "segments": segments,
"language": info.language, "duration": round(duration, 1),
"output_file": output_filename}
except Exception as e:
raise Exception(f"변환 실패: {str(e)}")
def _fmt(s):
m, sec = divmod(int(s), 60)
return f"{m:02d}:{sec:02d}"