diff --git a/App/routes/auth.py b/App/routes/auth.py index b640953..4a8154e 100644 --- a/App/routes/auth.py +++ b/App/routes/auth.py @@ -1,20 +1,21 @@ +from pydantic import BaseModel from fastapi import APIRouter, Depends, status, Request from services import auth_svc, session_svc from sqlalchemy import Connection from db.database import context_get_conn from fastapi.exceptions import HTTPException +from fastapi.responses import JSONResponse from schemas.auth_schema import RegisterRequest, LoginRequest router = APIRouter(prefix="/auth", tags=["auth"]) -# --- 로그인 상태 확인 API --- # 프론트엔드 새로고침 시 세션 유지 여부를 확인하고 유저 정보를 반환합니다 -@router.get("/check", status_code=status.HTTP_200_OK) +@router.get("/check", status_code=status.HTTP_200_OK, response_class=JSONResponse, summary= "로그인 상태 확인 API") async def check_session(session_user = Depends(session_svc.get_session_user_opt)): return {"user": session_user} -# --- 회원가입 API --- -@router.post("/register", status_code=status.HTTP_201_CREATED) + +@router.post("/register", status_code=status.HTTP_201_CREATED,response_class=JSONResponse, summary="회원가입 API") async def register_user(request: Request, user_info: RegisterRequest, conn: Connection = Depends(context_get_conn)): @@ -40,8 +41,7 @@ async def register_user(request: Request, return {"message": "회원가입이 성공적으로 완료되었습니다.", "status": "success"} -# --- 로그인 API --- -@router.post("/login", status_code=status.HTTP_200_OK) +@router.post("/login", status_code=status.HTTP_200_OK,response_class=JSONResponse ,summary="로그인 API") async def login_user(request: Request, login_info: LoginRequest, conn: Connection = Depends(context_get_conn)): @@ -73,7 +73,7 @@ async def login_user(request: Request, "status": "success" } -@router.get("/logout", status_code=status.HTTP_200_OK) +@router.get("/logout", status_code=status.HTTP_200_OK, response_class=JSONResponse, summary="로그아웃") async def logout_user(request: Request): request.state.session.clear() return { diff --git a/App/routes/home.py b/App/routes/home.py index 6a2a87f..112b09f 100644 --- a/App/routes/home.py +++ b/App/routes/home.py @@ -1,9 +1,10 @@ -from fastapi import APIRouter, Depends +from fastapi import APIRouter, Depends, status from services import session_svc +from fastapi.responses import JSONResponse router = APIRouter(prefix="/home", tags=["home"]) -@router.get("/") +@router.get("/", status_code=status.HTTP_200_OK,response_class=JSONResponse, summary="세션 유저 정보 가져오기") async def home_ui(session_user = Depends(session_svc.get_session_user_opt)): return { "session_user": session_user diff --git a/App/routes/image.py b/App/routes/image.py index 94c63d2..d8fea0e 100644 --- a/App/routes/image.py +++ b/App/routes/image.py @@ -2,11 +2,12 @@ from services import session_svc, image_svc from sqlalchemy import Connection from db.database import context_get_conn +from fastapi.responses import JSONResponse router = APIRouter(prefix="/image", tags=["image"]) # 사용자 전체 업로드 히스토리 조회 -@router.get("/history", status_code=status.HTTP_200_OK) +@router.get("/history", status_code=status.HTTP_200_OK, response_class=JSONResponse,summary="이미지 히스토리 전체 조회") async def get_user_histories( conn: Connection = Depends(context_get_conn), session_user = Depends(session_svc.get_session_user_prt) # 로그인 필수 @@ -22,7 +23,7 @@ async def get_user_histories( } # 특정 이미지의 상세 내역 조회 (개별 히스토리) -@router.get("/history/{image_id}", status_code=status.HTTP_200_OK) +@router.get("/history/{image_id}", status_code=status.HTTP_200_OK, response_class=JSONResponse,summary="이미지 개별 상세 조회") async def get_user_history( image_id: int, conn: Connection = Depends(context_get_conn), @@ -40,7 +41,7 @@ async def get_user_history( } # 이미지 히스토리 삭제 -@router.delete("/history/{image_id}", status_code=status.HTTP_200_OK, summary="버튼 삭제") +@router.delete("/history/{image_id}", status_code=status.HTTP_200_OK,response_class=JSONResponse, summary="이미지 버튼 히스토리 삭제") async def delete_image_history( image_id: int, conn: Connection = Depends(context_get_conn), @@ -51,8 +52,9 @@ async def delete_image_history( if not history: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="해당 이미지 기록을 찾을 수 없습니다.") + # DB 레코드 삭제 await image_svc.delete_image_db(conn, image_id) - + # 실제 이미지 삭제 await image_svc.delete_image(history.image_loc) return { diff --git a/App/routes/inference.py b/App/routes/inference.py index 9d04a32..377c6da 100644 --- a/App/routes/inference.py +++ b/App/routes/inference.py @@ -137,6 +137,6 @@ async def get_video_detail( ) meta = await video_svc.get_video_meta_result(conn, video_id) - frames = await video_svc.get_video_frame_results(conn, video_id) + frames = await video_svc.get_video_frame_result(conn, video_id) return VideoDetailResponse(meta=meta, frames=frames) diff --git a/App/routes/video.py b/App/routes/video.py index 5ccb134..99d84a6 100644 --- a/App/routes/video.py +++ b/App/routes/video.py @@ -1,12 +1,15 @@ -from fastapi import APIRouter, Depends, status, HTTPException +from fastapi import APIRouter, Depends, status +from fastapi.exceptions import HTTPException +from fastapi.responses import JSONResponse from services import session_svc, video_svc from sqlalchemy import Connection from db.database import context_get_conn +from fastapi.responses import JSONResponse router = APIRouter(prefix="/video", tags=["video"]) # 사용자 전체 비디오 업로드 히스토리 조회 -@router.get("/history", status_code=status.HTTP_200_OK) +@router.get("/history", status_code=status.HTTP_200_OK, response_class=JSONResponse,summary="비디오 히스토리 전체 조회") async def get_video_histories( conn: Connection = Depends(context_get_conn), session_user = Depends(session_svc.get_session_user_prt) # 로그인 필수 @@ -22,7 +25,7 @@ async def get_video_histories( } # 특정 비디오의 상세 내역 조회 -@router.get("/history/{video_id}", status_code=status.HTTP_200_OK) +@router.get("/history/{video_id}", status_code=status.HTTP_200_OK,response_class=JSONResponse, summary="비디오 개별 상세 조회") async def get_video_history( video_id: int, conn: Connection = Depends(context_get_conn), @@ -43,7 +46,8 @@ async def get_video_history( } # 비디오 히스토리 삭제 -@router.delete("/history/{video_id}", status_code=status.HTTP_200_OK, summary="버튼 삭제") +@router.delete("/history/{video_id}", status_code=status.HTTP_200_OK, + response_class=JSONResponse, summary="비디오 버튼 히스토리 삭제") async def delete_video_history( video_id: int, conn: Connection = Depends(context_get_conn), @@ -56,8 +60,11 @@ async def delete_video_history( if not history: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="해당 비디오 기록을 찾을 수 없습니다.") + if history.status == "SUCCESS": + await video_svc.delete_video_meta_result(conn, video_id) + await video_svc.delete_video_frame_result(conn, video_id) + await video_svc.delete_video_db(conn, video_id) - await video_svc.delete_video(history.video_loc) return { diff --git a/App/schemas/auth_schema.py b/App/schemas/auth_schema.py index b844abd..66c65a6 100644 --- a/App/schemas/auth_schema.py +++ b/App/schemas/auth_schema.py @@ -17,4 +17,4 @@ class LoginRequest(BaseModel): # [요청용] 회원가입 API (POST /auth/register) 수신 JSON 데이터 검증 class RegisterRequest(LoginRequest): - name: str = Field(min_length=2, max_length=100) + name: str = Field(min_length=2, max_length=100) \ No newline at end of file diff --git a/App/services/image_svc.py b/App/services/image_svc.py index f83194e..4088006 100644 --- a/App/services/image_svc.py +++ b/App/services/image_svc.py @@ -21,7 +21,9 @@ os.makedirs(EXPLAIN_UPLOAD_DIR, exist_ok=True) os.makedirs(UPLOAD_DIR, exist_ok=True) -# 사용자 업로드 이미지 파일 서버 내 저장 (회원/비회원 공통) +# 사용자 업로드 이미지 서버 내 저장 (회원/비회원 공통) +# 호출 : inference.py +# image_loc = await image_svc.upload_image(user_email, imagefile) -> 이미지 업로드 이후, 이미지 저장 경로 반환 async def upload_image(user_email: str | None, imagefile: UploadFile) -> str: try: # 사용자별 하위 디렉토리 결정 @@ -82,6 +84,8 @@ async def upload_image_cam(user_email: str, image_id: int, image: np.ndarray) -> return cam_loc[1:].replace("\\", "/") # 사용자 업로드 이미지 서버 내 삭제 +# 호출 : image.py : history 삭제 할 때 db 와 실제 파일 삭제 +# 호출 : inference.py : 추론 FAIL일 때 delete_video and delete_video_db 실행 async def delete_image(image_loc: str): try: @@ -97,7 +101,10 @@ async def delete_image(image_loc: str): status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="알수없는 이유로 문제가 발생하였습니다." ) + + # 사용자 전체 히스토리 조회 (image_result 테이블 반영) +# 호출 : image.py / video.py async def get_user_histories(conn: Connection, user_id: int): try: # SQL에 맞춰 테이블명과 컬럼 변경 @@ -108,8 +115,7 @@ async def get_user_histories(conn: Connection, user_id: int): ORDER BY created_at DESC; """) stmt = text(query) - bind_stmt = stmt.bindparams(user_id = user_id) - result = await conn.execute(bind_stmt) + result = await conn.execute(stmt, {"user_id": user_id}) user_histories = [UserHistory( image_id = row.id, @@ -137,6 +143,7 @@ async def get_user_histories(conn: Connection, user_id: int): raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="알수없는 이유로 문제가 발생하였습니다.") # 사용자 개별 히스토리 조회 +# 호출 : image.py / video.py async def get_user_history(conn: Connection, image_id: int): try: query = """ @@ -145,8 +152,7 @@ async def get_user_history(conn: Connection, image_id: int): WHERE id = :image_id; """ stmt = text(query) - bind_stmt = stmt.bindparams(image_id=image_id) - result = await conn.execute(bind_stmt) + result = await conn.execute(stmt, {"image_id": image_id}) row = result.fetchone() if row is None: @@ -183,7 +189,8 @@ async def get_user_history(conn: Connection, image_id: int): print(e) raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="알수없는 이유로 문제가 발생하였습니다.") -# 비회원 데이터 5분 후 자동 삭제 태스크 +# 비회원 데이터 1분 후 자동 삭제 태스크 +# inference_svc.py : def process_image_task에서 추론이 성공 했을 때, 비회원일 경우 1분 후 자동 삭제 @celery_app.task(name="cleanup_anonymous_image") def cleanup_anonymous_image(image_id: int, image_loc: str): loop = asyncio.new_event_loop() @@ -191,12 +198,23 @@ def cleanup_anonymous_image(image_id: int, image_loc: str): try: async def _delete(): async with celery_db_conn() as conn: - await delete_image_db(conn, image_id) - await delete_image(image_loc) - print(f"[Cleanup] 비회원 이미지 삭제 완료 - image_id: {image_id}, image_loc: {image_loc}") + success = True + try: + await delete_image_db(conn, image_id) + except Exception as e: + print(f"[Cleanup] 이미지 DB 삭제 실패 - image_id: {image_id}, error: {e}") + success=False + try: + await delete_image(image_loc) + except Exception as e: + print(f"[Cleanup] 이미지 파일 삭제 실패 - image_loc: {image_loc}, error: {e}") + success=False + + if success: + print(f"[Cleanup] 비회원 이미지 삭제 프로세스 완료 - image_id: {image_id}, image_loc: {image_loc}") + loop.run_until_complete(_delete()) - except Exception as e: - print(f"[Cleanup Error] 비회원 이미지 삭제 실패 - image_id: {image_id}, error: {e}") + finally: loop.close() @@ -218,10 +236,12 @@ async def _delete(): loop.close() # 이미지 DB 레코드 및 물리 파일 완전 삭제 +# 호출 : image.py : history 삭제 시 +# 호출 : inference.py : get_image_result 에서 FALIED일 시 async def delete_image_db(conn: Connection, image_id: int): try: delete_query = text("DELETE FROM image_result WHERE id = :image_id") - result = await conn.execute(delete_query.bindparams(image_id=image_id)) + result = await conn.execute(delete_query, {"image_id": image_id}) if result.rowcount == 0: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"해당 이미지 id {id}는(은) 존재하지 않아 삭제할 수 없습니다.") @@ -236,11 +256,12 @@ async def delete_image_db(conn: Connection, image_id: int): raise except Exception as e: - print(e) await conn.rollback() raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="알수없는 이유로 문제가 발생하였습니다.") # 빈 이미지 DB 생성 이후, image_id 반환(접수 완료) +# 호출 : inference.py : 빈 이미지 DB 생성 후, image_id 받기 +# image_id = (conn, user_id, image_loc, version_type, model_type, domain_type) async def register_image_result(conn: Connection, user_id: int | None, image_loc: str, version_type: str, model_type: str, domain_type: str): try: @@ -275,6 +296,7 @@ async def register_image_result(conn: Connection, user_id: int | None, image_loc detail="요청데이터가 제대로 전달되지 않았습니다") # 이미지 메타데이터 + 추론 결과값 DB에 저장 +# 호출 : inference_svc.py : process_image_task 추론 종료 시점 async def update_image_result(conn: Connection, image_id: int, analysis: dict, result_msg: str, status: str): @@ -320,6 +342,8 @@ async def update_image_result(conn: Connection, image_id: int, analysis: dict, raise e # 이미지 결과 값 가져오기 +# 프론트엔드가 특정 이미지의 분석 진행 상태 및 최종 결과를 확인하고자 할 때 데이터 반환 +# 호출 위치: routers/inference.py - get_image_result() API async def get_image_result(conn: Connection, image_id: int): try: diff --git a/App/services/inference_svc.py b/App/services/inference_svc.py index 1a1d440..a9468da 100644 --- a/App/services/inference_svc.py +++ b/App/services/inference_svc.py @@ -234,7 +234,8 @@ async def run_inference(): finally: if not user_id: - video_svc.cleanup_anonymous_video.apply_async(args=[video_id, video_loc], countdown=60) + is_success = result.get("status") == "success" + video_svc.cleanup_anonymous_video.apply_async(args=[video_id, video_loc, is_success], countdown=60) diff --git a/App/services/video_svc.py b/App/services/video_svc.py index fd69f20..e719b8d 100644 --- a/App/services/video_svc.py +++ b/App/services/video_svc.py @@ -16,6 +16,8 @@ UPLOAD_DIR = os.getenv("UPLOAD_DIR") # 사용자 업로드 동영상 서버 내 저장 (회원/비회원 공통) +# 호출 : inference.py : video_loc = await video_svc.upload_video(user_email, videofile) +# -> 비디오 업로드 이후, 비디오 저장 경로 반환 async def upload_video(user_email: str | None, videofile: UploadFile) -> str: try: # 1. 사용자별 하위 디렉토리 결정 @@ -65,6 +67,7 @@ async def upload_video(user_email: str | None, videofile: UploadFile) -> str: detail="비디오 업로드 과정에서 예상치 못한 오류가 발생했습니다.") # 사용자 업로드 비디오 서버 내 삭제 +# 호출 : inference.py : 추론 FAIL일 때 delete_video and delete_video_db 실행 async def delete_video(video_loc: str): try: file_path = "." + video_loc @@ -82,6 +85,7 @@ async def delete_video(video_loc: str): ) # 사용자 전체 비디오 히스토리 조회 +# 호출 : image.py / video.py async def get_user_histories(conn: Connection, user_id: int): try: query = """ @@ -92,8 +96,7 @@ async def get_user_histories(conn: Connection, user_id: int): ORDER BY created_at DESC; """ stmt = text(query) - bind_stmt = stmt.bindparams(user_id=user_id) - result = await conn.execute(bind_stmt) + result = await conn.execute(stmt, {"user_id": user_id}) video_histories = [VideoData( id = row.id, @@ -120,6 +123,7 @@ async def get_user_histories(conn: Connection, user_id: int): # 사용자 개별 비디오 히스토리 조회 +# 호출 : image.py / video.py async def get_user_history(conn: Connection, user_id: int, video_id: int): try: stmt = text(""" @@ -161,31 +165,55 @@ async def get_user_history(conn: Connection, user_id: int, video_id: int): print(e) raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="알수없는 이유로 문제가 발생하였습니다.") -# 비회원 데이터 5분 후 자동 삭제 태스크 +# 비회원 데이터 1분 후 자동 삭제 태스크 +# inference_svc.py : def process_video_task에서 추론이 성공 했을 때, 비회원일 경우 1분 후 자동 삭제 @celery_app.task(name="cleanup_anonymous_video") -def cleanup_anonymous_video(video_id: int, video_loc: str): +def cleanup_anonymous_video(video_id: int, video_loc: str, is_success: bool): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) try: async def _delete(): + success = True async with celery_db_conn() as conn: - await delete_video_db(conn, video_id) - await delete_video(video_loc) - print(f"[Cleanup] 비회원 비디오 삭제 완료 - video_id: {video_id}, video_loc: {video_loc}") + if is_success: + try: + await delete_video_meta_result(conn, video_id) + except Exception as e : + print(f"[Cleanup] frame 삭제 실패 - video_id: {video_id}, error: {e}") + success=False + + try: + await delete_video_frame_result(conn, video_id) + except Exception as e : + print (f"[Cleanup] meta 삭제 실패 - video_id: {video_id}, error: {e}") + success=False + try: + await delete_video_db(conn, video_id) + except Exception as e: + print(f"[Cleanup] video_result 삭제 실패 - video_id: {video_id}, error:{e}") + success=False + try: + await delete_video(video_loc) + except Exception as e: + print(f"[Cleanup] 파일 삭제 실패 - video_loc: {video_loc}, error: {e}") + success=False + + if is_success: + print(f"[Cleanup] 비회원 비디오 삭제 완료 - video_id: {video_id}, video_loc: {video_loc}") + loop.run_until_complete(_delete()) - except Exception as e: - print(f"[Cleanup Error] 비회원 비디오 삭제 실패 - video_id: {video_id}, error: {e}") finally: loop.close() # 비디오 DB 레코드 및 물리 파일 완전 삭제 +# 호출 : inference.py : 추론 FAIL일 때 delete_video and delete_video_db 실행 async def delete_video_db(conn: Connection, video_id: int): try: delete_query = text("DELETE FROM video_result WHERE id = :video_id") - result = await conn.execute(delete_query.bindparams(video_id=video_id)) + result = await conn.execute(delete_query, {"video_id": video_id}) if result.rowcount == 0: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"해당 비디오 id {id}는(은) 존재하지 않아 삭제할 수 없습니다.") + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"해당 비디오 id {video_id}는(은) 존재하지 않아 삭제할 수 없습니다.") await conn.commit() @@ -205,6 +233,7 @@ async def delete_video_db(conn: Connection, video_id: int): # 빈 비디오 DB 생성 이후, video_id 반환(접수 완료) +# 호출 : inference.py : video_id = await video_svc.register_video_result(conn, user_id, video_loc, version_type, model_type, domain_type) async def register_video_result(conn: Connection, user_id: int | None, video_loc: str, version_type: str, model_type: str, domain_type: str): try: @@ -239,6 +268,7 @@ async def register_video_result(conn: Connection, user_id: int | None, video_loc detail="요청데이터가 제대로 전달되지 않았습니다") # 비디오 메타데이터 + 추론 결과값 DB에 저장 +# 호출 : inference_svc.py : 비디오 메타데이터 + 추론 결과값 DB에 저장 / 더미값으로 오류처리 (무한로딩 방지) async def update_video_result(conn: Connection, video_id: int, analysis: dict, result_msg: str, status: str): @@ -280,6 +310,9 @@ async def update_video_result(conn: Connection, video_id: int, analysis: dict, raise e # 비디오 결과 값 가져오기 +# 프론트엔드가 특정 비디오의 분석 상태 및 최종 결과를 요청할 때 사용 +# 호출 : inference.py - get_video_result() API +# video_data = await video_svc.get_video_result(conn, video_id) video_data에 저장 async def get_video_result(conn: Connection, video_id: int): try: @@ -322,6 +355,8 @@ async def get_video_result(conn: Connection, detail="알수없는 이유로 문제가 발생하였습니다.") # 비디오 메타 데이터 정보 저장하기 +# 비디오 1개에 대한 프레임 요약 통계를 video_meta_result 테이블에 삽입 +# 호출 위치: services/inference_svc.py - process_video_task() → run_inference() async def save_video_meta_result(conn: Connection, video_id: int, analysis: dict): try: query = """ @@ -344,6 +379,8 @@ async def save_video_meta_result(conn: Connection, video_id: int, analysis: dict # 비디오 상세 결과 값 저장하기 # 비디오 프레임 별 딥페이크 점수, 얼굴 신뢰도, 얼굴 비율, 얼굴 조도 반환 +# 비디오 1개에 속한 여러 프레임의 분석 결과를 한 번에 INSERT. +# 호출 위치: services/inference_svc.py - process_video_task() → run_inference() async def save_video_frame_result(conn: Connection, video_id: int, frame_results: list): try: query = """ @@ -371,7 +408,64 @@ async def save_video_frame_result(conn: Connection, video_id: int, frame_results await conn.rollback() raise e + +async def delete_video_meta_result(conn: Connection, video_id: int): + try: + delete_query = text(""" + DELETE FROM video_meta_result + WHERE video_id = :video_id + """) + result = await conn.execute(delete_query, {"video_id": video_id}) + if result.rowcount == 0: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"해당 비디오 id {video_id}는(은) 존재하지 않아 삭제할 수 없습니다.") + + await conn.commit() + + except SQLAlchemyError as e: + print(e) + await conn.rollback() + raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="요청하신 서비스가 잠시 내부적으로 문제가 발생하였습니다.") + + except HTTPException: + raise + + except Exception as e: + print(e) + await conn.rollback() + raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="알수없는 이유로 문제가 발생하였습니다.") + + +async def delete_video_frame_result(conn: Connection, video_id: int): + try: + delete_query = text(""" + DELETE FROM video_frame_result + WHERE video_id = :video_id + """) + result = await conn.execute(delete_query, {"video_id": video_id}) + if result.rowcount == 0: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"해당 비디오 id {video_id}는(은) 존재하지 않아 삭제할 수 없습니다.") + + await conn.commit() + + except SQLAlchemyError as e: + print(e) + await conn.rollback() + raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="요청하신 서비스가 잠시 내부적으로 문제가 발생하였습니다.") + + except HTTPException: + raise + + except Exception as e: + print(e) + await conn.rollback() + raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="알수없는 이유로 문제가 발생하였습니다.") + + + + # 비디오 메타 데이터 정보 가져오기 +# 호출 위치: routers/inference.py - get_video_detail() +# 초당프레임수, 영상 전체 프레임 수, 샘플링한 프레임 수, 얼굴 추출에 성공한 프레임 수, score산출 성공 프레임 수 async def get_video_meta_result(conn: Connection, video_id: int): try: query = text(""" @@ -403,8 +497,10 @@ async def get_video_meta_result(conn: Connection, video_id: int): print(e) raise HTTPException(status_code=500, detail="알수없는 이유로 문제가 발생하였습니다.") -# 비디오 상세 결과 값 가져오기 -async def get_video_frame_results(conn: Connection, video_id: int): +# 비디오 프레임별 상세 결과 조회 +# 호출 위치: routers/inference.py - get_video_detail() +# video_id에 속한 모든 프레임 결과를 frame_index 반환 하여, 프레임별 점수 그래프, 의심 구간 표시 등 시각화에 활용. +async def get_video_frame_result(conn: Connection, video_id: int): try: query = text(""" SELECT frame_index, frame_time, score, face_conf, face_ratio, face_brightness @@ -433,4 +529,7 @@ async def get_video_frame_results(conn: Connection, video_id: int): raise HTTPException(status_code=503, detail="데이터베이스 조회 중 문제가 발생했습니다.") except Exception as e: print(e) - raise HTTPException(status_code=500, detail="알수없는 이유로 문제가 발생하였습니다.") \ No newline at end of file + raise HTTPException(status_code=500, detail="알수없는 이유로 문제가 발생하였습니다.") + + + \ No newline at end of file diff --git a/App/sql/initial_video_frame_result.sql b/App/sql/initial_video_frame_result.sql index c946b5d..d280613 100644 --- a/App/sql/initial_video_frame_result.sql +++ b/App/sql/initial_video_frame_result.sql @@ -1,14 +1,14 @@ drop table if exists video_frame_result; create table deepfake_db.video_frame_result ( - id integer auto_increment primary key, - video_id integer not null, - frame_index integer not null, + id integer auto_increment primary key, -- 행 자체의 고유 ID (대리키) + video_id integer not null, + frame_index integer not null, -- 영상 내 프레임 순번. frame_time float not null, -- 영상 내 타임스탬프(초) - score float not null, - face_conf float not null, - face_ratio float not null, - face_brightness float not null, + score float not null,-- 해당 프레임의 딥페이크 의심 점수 (0.0~1.0) + face_conf float not null,-- 얼굴 검출 신뢰도. 낮으면 score 신뢰도도 낮음. + face_ratio float not null,-- 프레임 대비 얼굴 면적 비율 (0.0~1.0) + face_brightness float not null,-- 얼굴 영역 평균 밝기 (조도 품질 지표) index video_id_idx(video_id) ); \ No newline at end of file diff --git a/App/sql/initial_video_meta_result.sql b/App/sql/initial_video_meta_result.sql index 80e95da..289f4d6 100644 --- a/App/sql/initial_video_meta_result.sql +++ b/App/sql/initial_video_meta_result.sql @@ -1,13 +1,13 @@ drop table if exists video_meta_result; create table deepfake_db.video_meta_result ( - id integer auto_increment primary key, + id integer auto_increment primary key,-- 행 자체의 고유 ID (대리키). video_id integer not null unique, - fps float not null, - total_frames integer not null, - num_sampled integer not null, - num_extracted integer not null, - num_detected integer not null, + fps float not null, -- 초당 프레임수 + total_frames integer not null,-- 영상 전체 프레임 수 + num_sampled integer not null,-- [1단계] 분석을 위해 샘플링한 프레임 수 + num_extracted integer not null,-- [2단계] 샘플 중 얼굴 추출에 성공한 프레임 수 + num_detected integer not null,-- [3단계] 추출된 얼굴 중 딥페이크 score 산출에 성공한 프레임 수 - index video_id_idx(video_id) + index video_id_idx(video_id)-- video_id로 메타 조회 시 사용 ); \ No newline at end of file diff --git a/App/sql/initial_video_result.sql b/App/sql/initial_video_result.sql index cc683a9..9daa890 100644 --- a/App/sql/initial_video_result.sql +++ b/App/sql/initial_video_result.sql @@ -1,20 +1,30 @@ drop table if exists video_result; +-- 딥페이크 분석 결과를 저장하는 테이블 생성 create table deepfake_db.video_result ( - id integer auto_increment primary key, - user_id integer null, -- null 허용 - video_loc varchar(300) not null unique, - status varchar(20) not null default "PENDING", - label varchar(10) not null, - score float not null, + id integer auto_increment primary key, -- 시스템에서 부여하는 데이터 고유 식별 번호 + user_id integer null, -- 영상을 업로드한 사용자의 ID (비회원 허용을 위해 null 가능) + video_loc varchar(300) not null unique, -- 영상 파일이 저장된 경로/URL (중복 등록 방지) + + -- 상태 및 결과 정보 + status varchar(20) not null default "PENDING", -- 분석 진행 상태 (대기중, 분석중, 완료, 실패 등) + label varchar(10) not null, -- 최종 판정 결과 (예: 'Fake', 'Real') + score float not null, + + -- 분석 품질 및 메타데이터 face_conf float not null, face_ratio float not null, face_brightness float not null, + + -- 분석 환경 정보 version_type varchar(10) not null, model_type varchar(10) not null, domain_type varchar(20) not null, - result_msg varchar(200) not null, - created_at timestamp default current_timestamp, - index user_id_idx(user_id) + -- 기타 기록 + result_msg varchar(200) not null, -- 분석 결과에 대한 상세 메시지나 오류 내용 + created_at timestamp default current_timestamp, -- 데이터가 생성된(분석 요청된) 시간 + + -- 성능 최적화 + index user_id_idx(user_id) -- 특정 사용자의 분석 이력을 빠르게 조회하기 위한 인덱스 ); \ No newline at end of file