Skip to content

feat: backend callback E2E upload pipeline#3

Draft
tkv00 wants to merge 1 commit into
mainfrom
codex/e2e-backend-callback
Draft

feat: backend callback E2E upload pipeline#3
tkv00 wants to merge 1 commit into
mainfrom
codex/e2e-backend-callback

Conversation

@tkv00

@tkv00 tkv00 commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

🍀 이슈 번호

  • 없음


✅ 작업 사항

  • Spring backend /api/pre-signed가 발급하는 /upload?signature=... URL을 OpenCV에서 실제 multipart 영상 업로드로 수신하도록 구현
  • 업로드 진행률은 opencv-progress-upload:{jobId}, 하이라이트 처리/완료 진행률은 opencv-progress-highlight:{jobId}로 분리 발행
  • 비용 절감형 로컬 E2E를 위해 실제 mp4를 ffmpeg로 짧은 하이라이트 클립으로 자르고 backend /api/highlight/upload-result callback까지 수행
  • AI Worker callback URL 하드코딩 제거 및 Spring LocalDateTime 호환 createdAt 형식으로 조정
  • Docker/local 실행 설정과 tools/e2e_local_upload.py smoke E2E 스크립트, docs/local-e2e.md 재현 문서 추가

⌨ 기타

검증 완료:

  • .venv/bin/python -m compileall app ai_worker tools
  • tools/e2e_local_upload.py --backend-url http://127.0.0.1:9000 --opencv-url http://127.0.0.1:8000
  • 실제 결과: 테스트 회원 동기화, 등번호 등록, presigned 발급, 실제 mp4 업로드, Redis 진행률 발행, backend callback 저장, /api/highlight/latest?jobId=...에서 3개 highlight row 확인
  • 생성 클립 정적 서빙 확인: HEAD /highlight/{jobId}/{jobId}_segment_01.mp4 -> 200 OK

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a direct video upload and highlight processing pipeline, including a new /upload endpoint, background processing tasks using ffmpeg, and progress reporting routed to separate Redis channels. It also adds comprehensive local E2E testing scripts and documentation. Feedback on the changes includes improving the robustness of the video duration parsing and segment cutting logic in the pipeline, ensuring incomplete files are cleaned up upon failure, and moving directory creation in app/main.py from import time to a startup event handler to prevent potential startup failures.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

segments: list[dict[str, Any]] = []
for index, center in enumerate(centers, start=1):
start = max(0.0, min(center - (clip_seconds / 2.0), max(0.0, duration - clip_seconds)))
segment_duration = min(clip_seconds, max(0.5, duration - start))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

영상 전체 길이가 clip_seconds보다 짧거나 남은 구간이 0.5초 미만인 경우, segment_duration이 실제 남은 길이(duration - start)보다 커지게 되어 ffmpeg 실행 시 오류가 발생하거나 비정상적인 파일이 생성될 수 있습니다. max(0.5, ...) 제약을 제거하고 실제 남은 길이로 제한하는 것이 안전합니다.

Suggested change
segment_duration = min(clip_seconds, max(0.5, duration - start))
segment_duration = min(clip_seconds, duration - start)

text=True,
timeout=min(settings.FFMPEG_TIMEOUT_SEC, 30),
)
return max(0.1, float(result.stdout.strip()))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

ffprobe 실행 결과에서 duration을 파싱할 때, 비디오 파일이 손상되었거나 특정 포맷의 경우 result.stdout이 비어있거나 숫자가 아닌 값(예: N/A)을 반환할 수 있습니다. 이 경우 float() 변환 시 ValueError가 발생하므로, 예외 처리를 추가하여 더 명확한 에러 메시지를 제공하는 것이 좋습니다.

Suggested change
return max(0.1, float(result.stdout.strip()))
try:
return max(0.1, float(result.stdout.strip()))
except ValueError as exc:
raise RuntimeError(f"Failed to parse video duration from ffprobe output: {result.stdout!r}") from exc

Comment thread app/main.py
Comment on lines +180 to +181
Path(settings.SAVE_ROOT).mkdir(parents=True, exist_ok=True)
Path(settings.HIGHLIGHT_OUTPUT_ROOT).mkdir(parents=True, exist_ok=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

임포트 시점(import time)에 파일 시스템 I/O(mkdir)를 수행하면, 권한 문제나 마운트 지연 등으로 인해 애플리케이션 시작 자체가 실패하거나 테스트/정적 분석 도구 실행 시 예기치 않은 오류가 발생할 수 있습니다. 이러한 디렉터리 생성 로직은 @app.on_event("startup") 이벤트 핸들러 내부로 이동하는 것이 안전합니다.

Comment on lines +269 to +278
except Exception as exc:
logger.exception("[%s] direct upload E2E pipeline failed", job_id)
await report_progress_to_spring(
job_id,
"COMPLETE",
None,
member_id=member_id,
success=False,
message=f"{type(exc).__name__}: {exc}",
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

비디오 분할 및 콜백 처리 중 예외가 발생하여 파이프라인이 실패하는 경우, output_dir에 이미 생성되었거나 생성 중이던 불완전한 하이라이트 세그먼트 파일들이 그대로 남게 됩니다. 이는 디스크 공간 낭비 및 잘못된 파일 서빙으로 이어질 수 있으므로, except Exception 블록에서 output_dir을 정리(cleanup)하는 로직을 추가하는 것이 좋습니다.

Suggested change
except Exception as exc:
logger.exception("[%s] direct upload E2E pipeline failed", job_id)
await report_progress_to_spring(
job_id,
"COMPLETE",
None,
member_id=member_id,
success=False,
message=f"{type(exc).__name__}: {exc}",
)
except Exception as exc:
logger.exception("[%s] direct upload E2E pipeline failed", job_id)
try:
if output_dir.exists():
import shutil
shutil.rmtree(output_dir, ignore_errors=True)
except Exception as cleanup_exc:
logger.warning("[%s] Failed to cleanup output directory: %s", job_id, cleanup_exc)
await report_progress_to_spring(
job_id,
"COMPLETE",
None,
member_id=member_id,
success=False,
message=f"{type(exc).__name__}: {exc}",
)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant