import os import logging from sqlalchemy import create_engine from sqlalchemy.orm import Session from .celery_app import celery_app from ..config import settings from ..models.tables import Video, Segment, Job, VideoStatus, JobStatus from ..services.video_utils import extract_audio logger = logging.getLogger(__name__) engine = create_engine(settings.database_url.replace("+aiosqlite", "")) def _get_sync_session(): return Session(engine) @celery_app.task(bind=True, name="app.tasks.process.process_video") def process_video(self, video_id: int, job_id: int = None): from ..services.diarization import diarize, unload_model as unload_diarization from ..services.stt import transcribe_segment, unload_model as unload_stt from ..services.translator import translate, unload_model as unload_translate db = _get_sync_session() temp_files = [] try: video = db.query(Video).filter(Video.id == video_id).first() if not video: raise ValueError(f"Video {video_id} not found") video.status = VideoStatus.processing.value db.commit() if job_id: job = db.query(Job).filter(Job.id == job_id).first() else: job = Job( video_id=video_id, job_type="process", status=JobStatus.running.value ) db.add(job) db.commit() self.update_state(state="PROGRESS", meta={"progress": 0.0}) job.celery_task_id = self.request.id job.status = JobStatus.running.value db.commit() audio_path = os.path.join(settings.temp_dir, f"audio_{video_id}.wav") temp_files.append(audio_path) extract_audio(video.filepath, audio_path) logger.info("Extracted audio for video %d", video_id) self.update_state(state="PROGRESS", meta={"progress": 0.10}) job.progress = 0.10 db.commit() segments = diarize(audio_path) if not segments: raise RuntimeError("No speech segments detected in video") logger.info("Diarized %d segments for video %d", len(segments), video_id) unload_diarization() self.update_state(state="PROGRESS", meta={"progress": 0.20}) job.progress = 0.20 db.commit() total = len(segments) logger.info("Starting STT for %d segments", total) for i, seg in enumerate(segments): en_text = transcribe_segment(audio_path, seg["start_time"], seg["end_time"]) seg["english_text"] = en_text pct = 0.20 + (i + 1) / total * 0.35 self.update_state(state="PROGRESS", meta={"progress": pct}) job.progress = pct db.commit() unload_stt() logger.info("STT complete, unloaded model") logger.info("Starting translation for %d segments", total) for i, seg in enumerate(segments): en_text = seg.get("english_text", "") da_text = translate(en_text) if en_text else "" seg["danish_text"] = da_text pct = 0.55 + (i + 1) / total * 0.35 self.update_state(state="PROGRESS", meta={"progress": pct}) job.progress = pct db.commit() unload_translate() logger.info("Translation complete, unloaded model") for seg in segments: db_seg = Segment( video_id=video_id, speaker_label=seg["speaker_label"], start_time=seg["start_time"], end_time=seg["end_time"], english_text=seg.get("english_text", ""), danish_text=seg.get("danish_text", ""), status="done", ) db.add(db_seg) video.status = VideoStatus.review.value job.status = JobStatus.done.value job.progress = 1.0 db.commit() logger.info("Processing complete for video %d", video_id) return {"video_id": video_id, "status": "review"} except Exception as e: logger.error("Processing failed for video %d: %s", video_id, e, exc_info=True) video = db.query(Video).filter(Video.id == video_id).first() if video: video.status = VideoStatus.error.value video.error_message = str(e) if job_id: job = db.query(Job).filter(Job.id == job_id).first() else: job = ( db.query(Job) .filter(Job.video_id == video_id, Job.job_type == "process") .first() ) if job: job.status = JobStatus.failed.value job.error_message = str(e) db.commit() raise finally: for f in temp_files: if os.path.exists(f): try: os.remove(f) except OSError: pass db.close()