{
 "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, 여기서는 60 FPS로 지정)  \n",
    "- CROP_TOP, CROP_BOTTOM, CROP_LEFT, CROP_RIGHT: 영상 가장자리에서 제거할 픽셀 수  \n",
    "- ROI_USE_GUI: ROI 영역을 GUI로 지정할 것인지(True) 또는 코드 내 수동 지정(False)  \n",
    "- MULTI_ROI_ENABLE: True이면 여러 ROI를 지정할 수 있음  \n",
    "- MANUAL_ROI: MULTI_ROI_ENABLE이 False일 경우 사용 (단일 ROI: (x, y, w, h))  \n",
    "- MANUAL_ROIS: MULTI_ROI_ENABLE이 True일 경우 사용 (ROI 리스트, 각 요소: (x, y, w, h))  \n",
    "- BLUR_KERNEL_SIZE: ROI 영역에 적용할 가우시안 블러 커널 사이즈  \n",
    "    * 사용법: (홀수, 홀수) 튜플로 지정 (예: (15,15): 적당한 블러, (3,3): 약함, (31,31): 강함)\n",
    "- GRID_INTERVAL: 디버깅 이미지에 표시할 눈금 간격 (픽셀 단위)  \n",
    "- DEBUG_FRAME_IDX: 디버깅용으로 추출할 프레임 번호  \n",
    "- MODEL_WEIGHT_NAME: 추론에 사용한 가중치 파일명 (예: \"weight_v1.pth\")  \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",
    "# 영상 리사이징 해상도\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 블러 처리 관련 설정\n",
    "ROI_USE_GUI = False         # True: GUI로 ROI 지정, False: 수동 지정\n",
    "MULTI_ROI_ENABLE = True       # True: 여러 ROI 지정 가능, False: 단일 ROI 사용\n",
    "MANUAL_ROI = (250, 70, 1450, 45)         # 단일 ROI (MULTI_ROI_ENABLE이 False인 경우 사용)\n",
    "MANUAL_ROIS = [(250, 70, 1450, 300), (750, 70, 1450, 200)]        # 여러 ROI 지정 (리스트 형태); 필요 시 여러 ROI 추가 가능\n",
    "BLUR_KERNEL_SIZE = (31, 31)   # 가우시안 블러 커널 사이즈\n",
    "\n",
    "# 디버깅 이미지 눈금 간격\n",
    "GRID_INTERVAL = 200\n",
    "\n",
    "# 디버깅용 프레임 번호\n",
    "DEBUG_FRAME_IDX = 0\n",
    "\n",
    "# 추론에 사용한 가중치 파일명 (출력 파일명에 포함)\n",
    "MODEL_WEIGHT_NAME = \"weight_v1.pth\"\n",
    "\n",
    "# 타임스탬프 생성 및 출력 디렉토리 생성 (덮어쓰지 않도록)\n",
    "timestamp = datetime.now().strftime(\"%Y%m%d_%H%M%S\")\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",
    "print(f\"MODEL WEIGHT: {MODEL_WEIGHT_NAME}\")\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로 영상 정보를 확인하고, \n",
    "- DEBUG_FRAME_IDX 프레임을 추출하여 눈금(200픽셀 간격)과 그리드가 있는 상태로 시각화 및 파일로 저장합니다.\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",
    "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",
    "debug_frame_rgb = cv2.cvtColor(debug_frame, cv2.COLOR_BGR2RGB)\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",
    "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",
    "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",
    "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_rois: ROI 영역(여러 개)을 선택하는 함수.\n",
    "  - ROI_USE_GUI가 True이면 cv2.selectROIs를 사용하여 마우스 클릭으로 지정 (다중 선택)\n",
    "  - False이면, MULTI_ROI_ENABLE에 따라 MANUAL_ROIS(다중) 또는 [MANUAL_ROI](단일)를 사용.\n",
    "- apply_roi_blur: ROI 리스트의 각 영역에 가우시안 블러를 적용하는 함수.\n",
    "- 디버깅 시, 원본 및 블러 처리 결과에 각 ROI 영역을 사각형(빨간색/초록색) 오버레이하여 표시합니다.\n",
    "- BLUR_KERNEL_SIZE 사용법: 값이 클수록 블러 효과가 강하며, 반드시 홀수여야 합니다.\n",
    "\"\"\"\n",
    "# %% [code]\n",
    "def select_rois(frame):\n",
    "    \"\"\"\n",
    "    다중 ROI 영역 선택 함수.\n",
    "    - ROI_USE_GUI가 True이면 cv2.selectROIs를 사용하여 ROI 영역(다중)을 지정합니다.\n",
    "    - False이면, MULTI_ROI_ENABLE에 따라 MANUAL_ROIS(리스트) 또는 [MANUAL_ROI]를 반환합니다.\n",
    "    반환:\n",
    "      - ROI 리스트, 각 ROI: (x, y, w, h)\n",
    "    \"\"\"\n",
    "    if ROI_USE_GUI:\n",
    "        print(\"👉 ROI 선택 (다중): 마우스로 영역 지정 후 Enter 키 누름\")\n",
    "        rois = cv2.selectROIs(\"ROI Selector\", frame, showCrosshair=True, fromCenter=False)\n",
    "        cv2.destroyWindow(\"ROI Selector\")\n",
    "        roi_list = [tuple(map(int, roi)) for roi in rois]\n",
    "        return roi_list\n",
    "    else:\n",
    "        if MULTI_ROI_ENABLE:\n",
    "            print(\"👉 수동 다중 ROI 지정 사용:\", MANUAL_ROIS)\n",
    "            return MANUAL_ROIS\n",
    "        else:\n",
    "            print(\"👉 수동 단일 ROI 지정 사용:\", MANUAL_ROI)\n",
    "            return [MANUAL_ROI]\n",
    "\n",
    "def apply_roi_blur(frame, rois, kernel_size=BLUR_KERNEL_SIZE):\n",
    "    \"\"\"\n",
    "    ROI 영역 리스트에 대해 가우시안 블러를 적용하는 함수.\n",
    "    매개변수:\n",
    "      - frame: 입력 영상 프레임\n",
    "      - rois: ROI 리스트, 각 ROI: (x, y, w, h)\n",
    "      - kernel_size: 가우시안 블러 커널 사이즈 (예: (15,15))\n",
    "        * 값이 클수록 블러 효과가 강해지며 반드시 홀수여야 함.\n",
    "    반환:\n",
    "      - 블러가 적용된 프레임\n",
    "    \"\"\"\n",
    "    frame_blurred = frame.copy()\n",
    "    for roi in rois:\n",
    "        x, y, w, h = roi\n",
    "        roi_region = frame_blurred[y:y+h, x:x+w]\n",
    "        roi_blurred = cv2.GaussianBlur(roi_region, kernel_size, 0)\n",
    "        frame_blurred[y:y+h, x:x+w] = roi_blurred\n",
    "    return frame_blurred\n",
    "\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",
    "rois_selected = select_rois(frame_cropped_for_roi)\n",
    "frame_roi_blurred = apply_roi_blur(frame_cropped_for_roi, rois_selected)\n",
    "\n",
    "# --- 추가: 각 ROI 영역 오버레이한 디버깅 이미지 생성 ---\n",
    "debug_roi_overlay = frame_cropped_for_roi.copy()\n",
    "debug_roi_blurred_overlay = frame_roi_blurred.copy()\n",
    "for roi in rois_selected:\n",
    "    x, y, w, h = roi\n",
    "    cv2.rectangle(debug_roi_overlay, (x, y), (x+w, y+h), (0, 0, 255), 2)      # 빨간색: 원본에 표시\n",
    "    cv2.rectangle(debug_roi_blurred_overlay, (x, y), (x+w, y+h), (0, 255, 0), 2)  # 초록색: 블러 결과에 표시\n",
    "\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를 사용하여 저장되며, 출력 파일명에는 원본 파일명, MODEL_WEIGHT_NAME, 타임스탬프가 포함됩니다.\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",
    "        # 다중 ROI 블러 적용: select_rois를 미리 Cell 5에서 선택한 rois_selected 사용\n",
    "        frame = apply_roi_blur(frame, rois_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",
    "# 원본 파일명과 MODEL_WEIGHT_NAME을 포함하여 출력 파일명 생성\n",
    "base_video_name = os.path.splitext(os.path.basename(VIDEO_PATH))[0]\n",
    "output_video_filename = f\"{base_video_name}_{MODEL_WEIGHT_NAME}_{timestamp}.avi\"\n",
    "output_video_path = os.path.join(output_dir, output_video_filename)\n",
    "result_video = process_video(VIDEO_PATH, output_video_path)\n",
    "# %% [markdown]\n",
    "\"\"\"\n",
    "[Cell 6] END\n",
    "\"\"\"\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %% [markdown]\n",
    "\"\"\"\n",
    "[Cell 7] Output 동영상 구간 편집 기능\n",
    "\n",
    "- SEGMENT_EXTRACTION_ENABLE: True로 설정 시, 구간 편집 기능 활성화 (False이면 건너뜁니다)\n",
    "- SEGMENT_START_SEC, SEGMENT_END_SEC: 구간 편집할 시작 및 종료 시간 (초 단위, TARGET_FPS 기준)\n",
    "- 이 셀은 바로 전 단계에서 생성된 output 동영상의 정보를 출력하고,  \n",
    "  지정된 구간만 잘라 새로운 동영상으로 저장합니다.\n",
    "\"\"\"\n",
    "# %% [code]\n",
    "# 구간 편집 활성화 여부 (True: 구간 편집 실행, False: 실행하지 않음)\n",
    "SEGMENT_EXTRACTION_ENABLE = True\n",
    "\n",
    "# 구간 편집할 시작 및 종료 시간 (초 단위)\n",
    "SEGMENT_START_SEC = 3    # 예: 5초부터\n",
    "SEGMENT_END_SEC = 5     # 예: 15초까지\n",
    "\n",
    "if SEGMENT_EXTRACTION_ENABLE:\n",
    "    print(\"🔄 구간 편집 기능 활성화됨.\")\n",
    "    # 출력 동영상 정보 출력 (해상도, FPS, 총 프레임 수, 길이)\n",
    "    cap_seg = cv2.VideoCapture(output_video_path)\n",
    "    if not cap_seg.isOpened():\n",
    "        raise IOError(f\"출력 동영상 열기 실패: {output_video_path}\")\n",
    "    out_fps = cap_seg.get(cv2.CAP_PROP_FPS)\n",
    "    out_frame_count = int(cap_seg.get(cv2.CAP_PROP_FRAME_COUNT))\n",
    "    out_duration = out_frame_count / out_fps if out_fps > 0 else 0\n",
    "    print(\"📌 출력 동영상 정보:\")\n",
    "    print(f\" - FPS: {out_fps}\")\n",
    "    print(f\" - 총 프레임 수: {out_frame_count}\")\n",
    "    print(f\" - 영상 길이: {out_duration:.2f} 초\")\n",
    "    \n",
    "    # 시작 및 종료 프레임 계산\n",
    "    start_frame = int(SEGMENT_START_SEC * TARGET_FPS)\n",
    "    end_frame = int(SEGMENT_END_SEC * TARGET_FPS)\n",
    "    if start_frame < 0 or end_frame > out_frame_count or start_frame >= end_frame:\n",
    "        raise ValueError(\"구간 편집 시간 범위가 동영상 길이를 벗어나거나 올바르지 않습니다.\")\n",
    "    \n",
    "    # 구간 편집된 동영상 저장 경로 생성\n",
    "    segment_video_filename = f\"{base_video_name}_{MODEL_WEIGHT_NAME}_{timestamp}_segment_{SEGMENT_START_SEC}-{SEGMENT_END_SEC}s.avi\"\n",
    "    segment_video_path = os.path.join(output_dir, segment_video_filename)\n",
    "    \n",
    "    fourcc = cv2.VideoWriter_fourcc(*\"XVID\")\n",
    "    # 구간 편집된 동영상의 크기는 원본과 동일\n",
    "    ret, sample_frame = cap_seg.read()\n",
    "    if not ret:\n",
    "        raise IOError(\"구간 편집을 위한 첫 프레임 읽기 실패\")\n",
    "    sample_frame = resize_frame(sample_frame)\n",
    "    sample_frame = crop_frame(sample_frame)\n",
    "    seg_height, seg_width = sample_frame.shape[:2]\n",
    "    out_seg = cv2.VideoWriter(segment_video_path, fourcc, TARGET_FPS, (seg_width, seg_height))\n",
    "    \n",
    "    cap_seg.set(cv2.CAP_PROP_POS_FRAMES, start_frame)\n",
    "    current_frame = start_frame\n",
    "    while current_frame < end_frame:\n",
    "        ret, frame = cap_seg.read()\n",
    "        if not ret:\n",
    "            break\n",
    "        out_seg.write(frame)\n",
    "        current_frame += 1\n",
    "    cap_seg.release()\n",
    "    out_seg.release()\n",
    "    print(\"✅ 구간 편집 동영상 저장 완료:\", segment_video_path)\n",
    "else:\n",
    "    print(\"⚠️ 구간 편집 기능 비활성화됨. 이 셀은 건너뜁니다.\")\n",
    "# %% [markdown]\n",
    "\"\"\"\n",
    "[Cell 7] 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
}
