{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %% [markdown]\n",
    "\"\"\"\n",
    "[Cell 1] 전역 변수 및 하이퍼파라미터 설정\n",
    "\n",
    "- VIDEO_PATH: 원본 입력 영상 경로  \n",
    "- OUTPUT_DIR_BASE: 출력 결과를 저장할 기본 디렉토리  \n",
    "- RESIZE_WIDTH, RESIZE_HEIGHT: 영상 리사이징 해상도 (예: 1920x1080, 1280x720, 640x480 등)  \n",
    "- TARGET_FPS: 변경할 프레임 레이트 (예: 30 FPS)  \n",
    "- CROP_TOP, CROP_BOTTOM, CROP_LEFT, CROP_RIGHT: 영상 가장자리에서 제거할 픽셀 수  \n",
    "- ROI_USE_GUI: ROI 영역을 GUI로 지정할 것인지(True) 또는 코드 내 수동 지정(False)  \n",
    "- MANUAL_ROI: GUI 사용이 off일 때, (x, y, width, height) 형식으로 직접 지정한 ROI 영역  \n",
    "- BLUR_KERNEL_SIZE: ROI 영역에 적용할 가우시안 블러 커널 사이즈  \n",
    "    * 사용법: (홀수, 홀수) 튜플로 지정해야 하며, 예를 들어 (15,15)는 적당한 블러 효과, \n",
    "      (3,3)은 약한 블러, (31,31)은 강한 블러 효과를 줍니다.\n",
    "- GRID_INTERVAL: 디버깅 이미지에 표시할 눈금 간격 (픽셀 단위, 기본 100)\n",
    "- DEBUG_FRAME_IDX: 디버깅용으로 추출할 프레임 번호  \n",
    "- timestamp: 출력 파일 및 폴더 구분에 사용할 현재 시간 정보  \n",
    "\"\"\"\n",
    "# %% [code]\n",
    "import os\n",
    "import cv2\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from datetime import datetime\n",
    "\n",
    "# (!!!) 자주 바꿀 수 있는 글로벌 파라미터\n",
    "VIDEO_PATH = \"/ltb/media/ltb/90887173887158A4/Users/ltb/회사용/4090_옮길거/val_sample/it1_현대_테스트할영상_원본/dl_거리측정_중장비_02.avi\"  # 입력 영상 경로\n",
    "OUTPUT_DIR_BASE = \"/ltb/media/ltb/90887173887158A4/Users/ltb/회사용/4090_옮길거/val_sample/it1_현대_테스트할영상_원본/dl_거리측정_중장비_02\"  # 출력 폴더 기본 경로\n",
    "\n",
    "# 영상 리사이징 해상도 (예시: 1920x1080, 1280x720, 640x480 등)\n",
    "RESIZE_WIDTH = 1920   \n",
    "RESIZE_HEIGHT = 1080  \n",
    "TARGET_FPS = 60  # 원하는 FPS (영상 재샘플링 시 적용)\n",
    "\n",
    "# 영상 가장자리 크롭 픽셀 값 (상, 하, 좌, 우)\n",
    "CROP_TOP = 0       # 상단 제거 픽셀 수\n",
    "CROP_BOTTOM = 0    # 하단 제거 픽셀 수\n",
    "CROP_LEFT = 0      # 좌측 제거 픽셀 수\n",
    "CROP_RIGHT = 0     # 우측 제거 픽셀 수\n",
    "\n",
    "# ROI(Region of Interest) 블러 처리 관련 설정\n",
    "ROI_USE_GUI = False  # True: GUI로 ROI 지정, False: 코드 내 직접 좌표 지정 (headless 환경 대비)\n",
    "MANUAL_ROI = (250, 70, 1450, 45)  # (x, y, width, height) - GUI 사용 안할 경우 적용\n",
    "BLUR_KERNEL_SIZE = (31, 31)  # 가우시안 블러 커널 사이즈; 튜플 값은 홀수로 지정 (예: (15,15): 적당한 블러 효과)\n",
    "# 사용 예시: (3,3) -> 약한 블러, (15,15) -> 중간, (31,31) -> 강한 블러\n",
    "\n",
    "# # ROI(Region of Interest) 블러 처리 관련 설정\n",
    "# ROI_USE_GUI = False  # True: GUI로 ROI 지정, False: 코드 내 직접 좌표 지정 (headless 환경 대비)\n",
    "# MANUAL_ROI = (250, 70, 1450, 45)  # (x, y, width, height) - GUI 사용 안할 경우 적용\n",
    "# BLUR_KERNEL_SIZE = (31, 31)  # 가우시안 블러 커널 사이즈; 튜플 값은 홀수로 지정 (예: (15,15): 적당한 블러 효과)\n",
    "# # 사용 예시: (3,3) -> 약한 블러, (15,15) -> 중간, (31,31) -> 강한 블러\n",
    "\n",
    "# 디버깅 이미지에 적용할 눈금 간격 (픽셀 단위)\n",
    "GRID_INTERVAL = 200\n",
    "\n",
    "# 중간 디버깅용 프레임 지정 (예: 첫 프레임 또는 특정 프레임 번호)\n",
    "DEBUG_FRAME_IDX = 0\n",
    "\n",
    "# 출력 파일명에 사용될 시간정보 (폴더 및 파일 구분에 도움)\n",
    "timestamp = datetime.now().strftime(\"%Y%m%d_%H%M%S\")\n",
    "# 출력 디렉토리 생성 (덮어쓰지 않고 타임스탬프 포함)\n",
    "output_dir = os.path.join(OUTPUT_DIR_BASE, f\"exp_{timestamp}\")\n",
    "os.makedirs(output_dir, exist_ok=True)\n",
    "\n",
    "print(\"✅ 전역 변수 설정 완료\")\n",
    "print(f\"INPUT VIDEO: {VIDEO_PATH}\")\n",
    "print(f\"OUTPUT DIRECTORY: {output_dir}\")\n",
    "# %% [markdown]\n",
    "\"\"\"\n",
    "[Cell 1] END\n",
    "\"\"\"\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %% [markdown]\n",
    "\"\"\"\n",
    "[Cell 2] 영상 정보 읽기 및 첫 프레임 출력\n",
    "\n",
    "- cv2.VideoCapture를 이용해 영상의 해상도, FPS, 총 프레임 수, 길이(초)를 확인합니다.\n",
    "- DEBUG_FRAME_IDX에 지정된 프레임을 추출해 matplotlib로 시각화하며,  \n",
    "  눈금(100픽셀 간격)과 그리드가 표시되어 한눈에 좌표를 확인할 수 있습니다.\n",
    "- 추출한 프레임은 파일로도 저장됩니다.\n",
    "\"\"\"\n",
    "# %% [code]\n",
    "cap = cv2.VideoCapture(VIDEO_PATH)\n",
    "if not cap.isOpened():\n",
    "    raise IOError(f\"영상 열기 실패: {VIDEO_PATH}\")\n",
    "\n",
    "frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))\n",
    "frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))\n",
    "fps = cap.get(cv2.CAP_PROP_FPS)\n",
    "frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))\n",
    "duration = frame_count / fps if fps > 0 else 0\n",
    "\n",
    "print(\"📌 영상 정보:\")\n",
    "print(f\" - 해상도: {frame_width} x {frame_height}\")\n",
    "print(f\" - FPS: {fps}\")\n",
    "print(f\" - 총 프레임 수: {frame_count}\")\n",
    "print(f\" - 영상 길이: {duration:.2f} 초\")\n",
    "\n",
    "# 디버깅: DEBUG_FRAME_IDX 프레임 추출\n",
    "cap.set(cv2.CAP_PROP_POS_FRAMES, DEBUG_FRAME_IDX)\n",
    "ret, debug_frame = cap.read()\n",
    "if not ret:\n",
    "    raise IOError(f\"DEBUG_FRAME_IDX({DEBUG_FRAME_IDX}) 프레임 읽기 실패\")\n",
    "# BGR -> RGB 변환 (matplotlib 시각화를 위해)\n",
    "debug_frame_rgb = cv2.cvtColor(debug_frame, cv2.COLOR_BGR2RGB)\n",
    "\n",
    "# 디버깅 이미지에 눈금 및 그리드 추가\n",
    "plt.figure(figsize=(8, 6))\n",
    "plt.imshow(debug_frame_rgb)\n",
    "plt.title(\"원본 디버그 프레임\")\n",
    "plt.xticks(np.arange(0, debug_frame_rgb.shape[1], GRID_INTERVAL))\n",
    "plt.yticks(np.arange(0, debug_frame_rgb.shape[0], GRID_INTERVAL))\n",
    "plt.grid(True, which='both', linestyle='--', linewidth=0.5)\n",
    "plt.axis(\"on\")\n",
    "plt.show()\n",
    "\n",
    "debug_frame_path = os.path.join(output_dir, \"frame_debug_original.jpg\")\n",
    "cv2.imwrite(debug_frame_path, debug_frame)\n",
    "print(f\"✅ 디버그 원본 프레임 저장: {debug_frame_path}\")\n",
    "cap.release()\n",
    "# %% [markdown]\n",
    "\"\"\"\n",
    "[Cell 2] END\n",
    "\"\"\"\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %% [markdown]\n",
    "\"\"\"\n",
    "[Cell 3] 영상 리사이징 및 FPS 조절 함수 정의\n",
    "\n",
    "- resize_frame: 입력 프레임을 전역 변수 RESIZE_WIDTH, RESIZE_HEIGHT로 리사이징합니다.\n",
    "- 디버깅 예시로 첫 프레임 리사이징 후 눈금과 그리드를 포함한 이미지를 시각화하고 저장합니다.\n",
    "\"\"\"\n",
    "# %% [code]\n",
    "def resize_frame(frame, width=RESIZE_WIDTH, height=RESIZE_HEIGHT):\n",
    "    \"\"\"\n",
    "    입력 프레임을 지정된 해상도로 리사이징하는 함수.\n",
    "    매개변수:\n",
    "      - frame: 원본 영상 프레임 (numpy 배열)\n",
    "      - width, height: 목표 해상도\n",
    "    반환:\n",
    "      - 리사이징된 프레임\n",
    "    \"\"\"\n",
    "    resized = cv2.resize(frame, (width, height))\n",
    "    return resized\n",
    "\n",
    "# 예시: 디버깅을 위해 첫 프레임 리사이징 후 시각화\n",
    "cap = cv2.VideoCapture(VIDEO_PATH)\n",
    "cap.set(cv2.CAP_PROP_POS_FRAMES, DEBUG_FRAME_IDX)\n",
    "ret, frame_example = cap.read()\n",
    "if not ret:\n",
    "    raise IOError(\"프레임 읽기 실패\")\n",
    "resized_example = resize_frame(frame_example)\n",
    "resized_example_rgb = cv2.cvtColor(resized_example, cv2.COLOR_BGR2RGB)\n",
    "\n",
    "plt.figure(figsize=(8, 6))\n",
    "plt.imshow(resized_example_rgb)\n",
    "plt.title(\"리사이징된 디버그 프레임\")\n",
    "plt.xticks(np.arange(0, resized_example_rgb.shape[1], GRID_INTERVAL))\n",
    "plt.yticks(np.arange(0, resized_example_rgb.shape[0], GRID_INTERVAL))\n",
    "plt.grid(True, which='both', linestyle='--', linewidth=0.5)\n",
    "plt.axis(\"on\")\n",
    "plt.show()\n",
    "\n",
    "resized_example_path = os.path.join(output_dir, \"frame_debug_resized.jpg\")\n",
    "cv2.imwrite(resized_example_path, resized_example)\n",
    "print(f\"✅ 리사이징 디버그 프레임 저장: {resized_example_path}\")\n",
    "cap.release()\n",
    "# %% [markdown]\n",
    "\"\"\"\n",
    "[Cell 3] END\n",
    "\"\"\"\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %% [markdown]\n",
    "\"\"\"\n",
    "[Cell 4] 영상 크롭(상하좌우 편집) 함수 및 디버깅\n",
    "\n",
    "- crop_frame: 지정된 픽셀 수만큼 영상의 상, 하, 좌, 우를 크롭합니다.\n",
    "- 디버깅: 리사이징된 프레임과 크롭된 프레임을 나란히 시각화하며, 눈금과 그리드를 추가해 좌표를 쉽게 확인할 수 있습니다.\n",
    "\"\"\"\n",
    "# %% [code]\n",
    "def crop_frame(frame, top=CROP_TOP, bottom=CROP_BOTTOM, left=CROP_LEFT, right=CROP_RIGHT):\n",
    "    \"\"\"\n",
    "    영상 프레임에서 상하좌우 가장자리 픽셀을 지정된 만큼 크롭하는 함수.\n",
    "    매개변수:\n",
    "      - frame: 입력 영상 프레임\n",
    "      - top, bottom, left, right: 각 방향에서 제거할 픽셀 수\n",
    "    반환:\n",
    "      - 크롭된 프레임\n",
    "    \"\"\"\n",
    "    h, w = frame.shape[:2]\n",
    "    cropped = frame[top:h-bottom, left:w-right]\n",
    "    return cropped\n",
    "\n",
    "# 디버깅: DEBUG_FRAME_IDX 프레임에 대해 크롭 전후 비교\n",
    "cap = cv2.VideoCapture(VIDEO_PATH)\n",
    "cap.set(cv2.CAP_PROP_POS_FRAMES, DEBUG_FRAME_IDX)\n",
    "ret, frame_for_crop = cap.read()\n",
    "if not ret:\n",
    "    raise IOError(\"DEBUG_FRAME_IDX 프레임 읽기 실패\")\n",
    "frame_resized = resize_frame(frame_for_crop)\n",
    "frame_cropped = crop_frame(frame_resized)\n",
    "\n",
    "# BGR -> RGB 변환\n",
    "frame_resized_rgb = cv2.cvtColor(frame_resized, cv2.COLOR_BGR2RGB)\n",
    "frame_cropped_rgb = cv2.cvtColor(frame_cropped, cv2.COLOR_BGR2RGB)\n",
    "\n",
    "fig, axes = plt.subplots(1, 2, figsize=(12, 6))\n",
    "axes[0].imshow(frame_resized_rgb)\n",
    "axes[0].set_title(\"리사이징된 원본 프레임\")\n",
    "axes[0].set_xticks(np.arange(0, frame_resized_rgb.shape[1], GRID_INTERVAL))\n",
    "axes[0].set_yticks(np.arange(0, frame_resized_rgb.shape[0], GRID_INTERVAL))\n",
    "axes[0].grid(True, linestyle='--', linewidth=0.5)\n",
    "axes[1].imshow(frame_cropped_rgb)\n",
    "axes[1].set_title(\"상하좌우 크롭 적용 프레임\")\n",
    "axes[1].set_xticks(np.arange(0, frame_cropped_rgb.shape[1], GRID_INTERVAL))\n",
    "axes[1].set_yticks(np.arange(0, frame_cropped_rgb.shape[0], GRID_INTERVAL))\n",
    "axes[1].grid(True, linestyle='--', linewidth=0.5)\n",
    "plt.show()\n",
    "\n",
    "resized_debug_path = os.path.join(output_dir, \"frame_debug_resized_for_crop.jpg\")\n",
    "cropped_debug_path = os.path.join(output_dir, \"frame_debug_cropped.jpg\")\n",
    "cv2.imwrite(resized_debug_path, frame_resized)\n",
    "cv2.imwrite(cropped_debug_path, frame_cropped)\n",
    "print(f\"✅ 리사이징 프레임 저장: {resized_debug_path}\")\n",
    "print(f\"✅ 크롭된 프레임 저장: {cropped_debug_path}\")\n",
    "cap.release()\n",
    "# %% [markdown]\n",
    "\"\"\"\n",
    "[Cell 4] END\n",
    "\"\"\"\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %% [markdown]\n",
    "\"\"\"\n",
    "[Cell 5] ROI 영역 블러 처리 및 디버깅 (좌표 및 블러 영역 오버레이)\n",
    "\n",
    "- select_roi: ROI를 선택하는 함수 (GUI 또는 수동 지정)\n",
    "- apply_roi_blur: 지정한 ROI 영역에 가우시안 블러를 적용하는 함수\n",
    "- 디버깅 시, 원본 이미지 위에 ROI 영역을 (예: 빨간색 사각형) 표시하고, 블러 적용 결과에도 동일한 ROI 테두리를 오버레이하여  \n",
    "  좌표와 블러 적용 범위를 명확히 확인할 수 있도록 합니다.\n",
    "- BLUR_KERNEL_SIZE 사용법: 튜플 값 (예: (15,15))은 가우시안 커널의 크기를 의미하며,  \n",
    "  값이 클수록 블러 효과가 강해집니다. 값은 반드시 홀수여야 합니다.\n",
    "\"\"\"\n",
    "# %% [code]\n",
    "def select_roi(frame):\n",
    "    \"\"\"\n",
    "    ROI 영역 선택 함수.\n",
    "    - ROI_USE_GUI가 True이면 cv2.selectROI로 마우스 클릭으로 지정\n",
    "    - False이면 전역 변수 MANUAL_ROI 사용\n",
    "    \"\"\"\n",
    "    if ROI_USE_GUI:\n",
    "        print(\"👉 ROI 선택: 마우스로 영역 지정 후 Enter 키 누름\")\n",
    "        roi = cv2.selectROI(\"ROI Selector\", frame, fromCenter=False, showCrosshair=True)\n",
    "        cv2.destroyWindow(\"ROI Selector\")\n",
    "        return roi  # (x, y, w, h)\n",
    "    else:\n",
    "        print(\"👉 수동 ROI 지정 사용:\", MANUAL_ROI)\n",
    "        return MANUAL_ROI\n",
    "\n",
    "def apply_roi_blur(frame, roi, kernel_size=BLUR_KERNEL_SIZE):\n",
    "    \"\"\"\n",
    "    ROI 영역에 가우시안 블러를 적용하는 함수.\n",
    "    매개변수:\n",
    "      - frame: 입력 영상 프레임\n",
    "      - roi: (x, y, w, h) 형식의 ROI 영역\n",
    "      - kernel_size: 가우시안 블러 커널 사이즈 (예: (15,15))\n",
    "        * 참고: 값이 클수록 블러 효과가 강해지며, 반드시 홀수여야 함\n",
    "    반환:\n",
    "      - 블러가 적용된 프레임\n",
    "    \"\"\"\n",
    "    x, y, w, h = roi\n",
    "    roi_region = frame[y:y+h, x:x+w]\n",
    "    roi_blurred = cv2.GaussianBlur(roi_region, kernel_size, 0)\n",
    "    frame_blurred = frame.copy()\n",
    "    frame_blurred[y:y+h, x:x+w] = roi_blurred\n",
    "    return frame_blurred\n",
    "\n",
    "# 디버깅: DEBUG_FRAME_IDX 프레임에서 ROI 선택 및 블러 적용\n",
    "cap = cv2.VideoCapture(VIDEO_PATH)\n",
    "cap.set(cv2.CAP_PROP_POS_FRAMES, DEBUG_FRAME_IDX)\n",
    "ret, frame_for_roi = cap.read()\n",
    "if not ret:\n",
    "    raise IOError(\"DEBUG_FRAME_IDX 프레임 읽기 실패 (ROI)\")\n",
    "frame_resized_for_roi = resize_frame(frame_for_roi)\n",
    "frame_cropped_for_roi = crop_frame(frame_resized_for_roi)\n",
    "\n",
    "# ROI 영역 선택 (수동 또는 GUI)\n",
    "roi_selected = select_roi(frame_cropped_for_roi)\n",
    "\n",
    "# ROI 블러 적용\n",
    "frame_roi_blurred = apply_roi_blur(frame_cropped_for_roi, roi_selected)\n",
    "\n",
    "# --- 추가: ROI 영역을 오버레이한 디버깅 이미지 생성 ---\n",
    "# 원본 이미지에 ROI 사각형 그리기 (빨간색, 두께 2)\n",
    "debug_roi_overlay = frame_cropped_for_roi.copy()\n",
    "x, y, w, h = roi_selected\n",
    "cv2.rectangle(debug_roi_overlay, (x, y), (x+w, y+h), (0, 0, 255), 2)\n",
    "\n",
    "# 블러 적용 결과에도 ROI 사각형 오버레이 (초록색, 두께 2)\n",
    "debug_roi_blurred_overlay = frame_roi_blurred.copy()\n",
    "cv2.rectangle(debug_roi_blurred_overlay, (x, y), (x+w, y+h), (0, 255, 0), 2)\n",
    "\n",
    "# BGR -> RGB 변환\n",
    "frame_cropped_rgb = cv2.cvtColor(frame_cropped_for_roi, cv2.COLOR_BGR2RGB)\n",
    "frame_roi_blurred_rgb = cv2.cvtColor(frame_roi_blurred, cv2.COLOR_BGR2RGB)\n",
    "debug_roi_overlay_rgb = cv2.cvtColor(debug_roi_overlay, cv2.COLOR_BGR2RGB)\n",
    "debug_roi_blurred_overlay_rgb = cv2.cvtColor(debug_roi_blurred_overlay, cv2.COLOR_BGR2RGB)\n",
    "\n",
    "fig, axes = plt.subplots(1, 3, figsize=(18, 6))\n",
    "axes[0].imshow(frame_cropped_rgb)\n",
    "axes[0].set_title(\"크롭된 원본 (ROI 적용 전)\")\n",
    "axes[0].set_xticks(np.arange(0, frame_cropped_rgb.shape[1], GRID_INTERVAL))\n",
    "axes[0].set_yticks(np.arange(0, frame_cropped_rgb.shape[0], GRID_INTERVAL))\n",
    "axes[0].grid(True, linestyle='--', linewidth=0.5)\n",
    "axes[1].imshow(debug_roi_overlay_rgb)\n",
    "axes[1].set_title(\"원본 위 ROI 영역 오버레이 (빨강)\")\n",
    "axes[1].set_xticks(np.arange(0, debug_roi_overlay_rgb.shape[1], GRID_INTERVAL))\n",
    "axes[1].set_yticks(np.arange(0, debug_roi_overlay_rgb.shape[0], GRID_INTERVAL))\n",
    "axes[1].grid(True, linestyle='--', linewidth=0.5)\n",
    "axes[2].imshow(debug_roi_blurred_overlay_rgb)\n",
    "axes[2].set_title(\"ROI 블러 처리 결과 (초록 테두리)\")\n",
    "axes[2].set_xticks(np.arange(0, debug_roi_blurred_overlay_rgb.shape[1], GRID_INTERVAL))\n",
    "axes[2].set_yticks(np.arange(0, debug_roi_blurred_overlay_rgb.shape[0], GRID_INTERVAL))\n",
    "axes[2].grid(True, linestyle='--', linewidth=0.5)\n",
    "plt.show()\n",
    "\n",
    "roi_before_path = os.path.join(output_dir, \"frame_debug_roi_before.jpg\")\n",
    "roi_after_path = os.path.join(output_dir, \"frame_debug_roi_after.jpg\")\n",
    "cv2.imwrite(roi_before_path, debug_roi_overlay)\n",
    "cv2.imwrite(roi_after_path, debug_roi_blurred_overlay)\n",
    "print(f\"✅ ROI 적용 전(오버레이) 이미지 저장: {roi_before_path}\")\n",
    "print(f\"✅ ROI 블러 처리(오버레이) 이미지 저장: {roi_after_path}\")\n",
    "cap.release()\n",
    "# %% [markdown]\n",
    "\"\"\"\n",
    "[Cell 5] END\n",
    "\"\"\"\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %% [markdown]\n",
    "\"\"\"\n",
    "[Cell 6] 전체 영상 편집 파이프라인 및 결과 저장\n",
    "\n",
    "- 입력 영상의 모든 프레임에 대해 순차적으로:\n",
    "  1. 리사이징  \n",
    "  2. 상하좌우 크롭  \n",
    "  3. ROI 블러 처리  \n",
    "- 처리된 영상을 cv2.VideoWriter로 저장하며, 파일명에 타임스탬프를 포함해 덮어쓰지 않도록 합니다.\n",
    "- 진행 상황을 출력해 디버깅에 도움을 줍니다.\n",
    "\"\"\"\n",
    "# %% [code]\n",
    "def process_video(input_path, output_path):\n",
    "    \"\"\"\n",
    "    전체 영상 편집 파이프라인:\n",
    "      - 리사이징, 크롭, ROI 블러 적용\n",
    "      - 처리된 영상을 output_path에 저장\n",
    "    \"\"\"\n",
    "    cap = cv2.VideoCapture(input_path)\n",
    "    if not cap.isOpened():\n",
    "        raise IOError(f\"영상 열기 실패: {input_path}\")\n",
    "    \n",
    "    original_fps = cap.get(cv2.CAP_PROP_FPS)\n",
    "    print(f\"👉 입력 FPS: {original_fps}\")\n",
    "    \n",
    "    ret, frame_sample = cap.read()\n",
    "    if not ret:\n",
    "        raise IOError(\"첫 프레임 읽기 실패\")\n",
    "    frame_sample = resize_frame(frame_sample)\n",
    "    frame_sample = crop_frame(frame_sample)\n",
    "    output_height, output_width = frame_sample.shape[:2]\n",
    "    \n",
    "    fourcc = cv2.VideoWriter_fourcc(*\"XVID\")\n",
    "    out = cv2.VideoWriter(output_path, fourcc, TARGET_FPS, (output_width, output_height))\n",
    "    \n",
    "    frame_idx = 0\n",
    "    total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))\n",
    "    print(\"영상 편집 시작...\")\n",
    "    while True:\n",
    "        ret, frame = cap.read()\n",
    "        if not ret:\n",
    "            break\n",
    "        frame = resize_frame(frame)\n",
    "        frame = crop_frame(frame)\n",
    "        frame = apply_roi_blur(frame, roi_selected)\n",
    "        out.write(frame)\n",
    "        frame_idx += 1\n",
    "        if frame_idx % 50 == 0:\n",
    "            print(f\"  처리 중... 프레임 {frame_idx} / {total_frames}\")\n",
    "    \n",
    "    cap.release()\n",
    "    out.release()\n",
    "    print(\"✅ 영상 편집 완료 및 저장됨:\", output_path)\n",
    "    return output_path\n",
    "\n",
    "output_video_path = os.path.join(output_dir, f\"edited_video_{timestamp}.avi\")\n",
    "result_video = process_video(VIDEO_PATH, output_video_path)\n",
    "# %% [markdown]\n",
    "\"\"\"\n",
    "[Cell 6] END\n",
    "\"\"\"\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.20"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
