FFmpeg 명령을 활용한 영상에서 장면 추출(스크립트 포함)

 

이 글에서는 WSL(Ubuntu-24.04) 환경에서 동영상에서 장면 변화를 감지하여 프레임을 이미지로 추출하는 과정을 정리합니다. UI 없이 명령줄 기반으로 실행하며, 한글 경로를 지원합니다.

환경이 설치 되지 않은 분들은 본글을 보기전에 아래 글을 통해서 사전 환경을 셋팅해주세요

 

 

윈도우에서 WSL 설치 및 환경 설정: 동영상 스크린샷 추출 개발 준비

이 글에서는 윈도우에서 WSL(Ubuntu-24.04)을 설치하고, FFmpeg와 파이썬 환경을 설정하여 동영상에서 프레임을 추출할 수 있는 환경을 준비하는 방법을 설명합니다. 윈도우 WSL 설치 방법과 환경 설정

migo-dev.tistory.com


1. 목표 및 요구사항

목표

  • 동영상에서 장면 변화를 감지하여 프레임을 이미지로 추출.
  • UI 없이 명령줄에서 실행.
  • WSL(Ubuntu-24.04) 환경에서 동작.

원래 FFmpeg 명령어

다음은 원래 사용했던 FFmpeg 명령어입니다:

ffmpeg -c:v h264_cuvid -i "2025032-youtube-short-lecture-2.mp4" -vf "select='eq(n\,0)+gt(scene\,0.2)*between(t\,prev_selected_t+0.5\,inf)',showinfo" -vsync vfr -threads 8 -thread_type slice -c:v mjpeg images/youtube-short-lecture-2_%04d.jpg

설명:

  • 입력 영상: 2025032-youtube-short-lecture-2.mp4
  • 장면 변화 민감도: 0.2 (gt(scene,0.2))
  • 최소 스킵 시간: 0.5초 (prev_selected_t+0.5)
  • 출력 경로: images/youtube-short-lecture-2_%04d.jpg
  • 기타 설정:
    • -c:v h264_cuvid: NVIDIA GPU 디코딩.
    • -threads 8 -thread_type slice: 8개 스레드, 슬라이스 스레딩.
    • -c:v mjpeg: MJPEG 인코딩.
    • -vsync vfr: 가변 프레임 레이트.

요구사항

  • 명령줄에서 입력값(영상 파일 경로, 민감도, 최소 스킵 시간, 출력 경로)을 받아 실행.
  • WSL 환경에서 동작.
  • 한글 경로 지원.
  • GPU 지원 여부에 따라 동작 조정.

2. WSL 환경 설정 확인

(1) FFmpeg 설치 확인

WSL에서 FFmpeg가 설치되어 있는지 확인합니다.

ffmpeg -version

설치되어 있지 않다면 설치:

sudo apt update
sudo apt install ffmpeg -y

(2) NVIDIA GPU 지원 확인 (선택)

h264_cuvid 디코더를 사용하려면 NVIDIA GPU와 드라이버가 필요합니다.

nvidia-smi

출력이 없으면 GPU 지원이 안 되는 환경이므로 -c:v h264_cuvid 옵션을 제거해야 합니다.

GPU 지원 확인:

ffmpeg -encoders | grep nvenc

h264_cuvid 또는 nvenc 관련 인코더가 있어야 합니다.

(3) WSL 경로 확인

  • 작업 디렉토리: /home/doyo/py_pj/get_v2i
  • 입력 영상 경로 예: /mnt/c/녹화폴더/test.mp4
  • 출력 경로 예: /home/doyo/py_pj/get_v2i/extracted_frame_%04d.jpg

3. 파이썬 스크립트 (UI 제외)

UI 없이 명령줄에서 실행 가능한 파이썬 스크립트를 작성했습니다. 사용자가 입력값을 직접 제공하며, 한글 경로를 지원하고 GPU 지원 여부에 따라 동작을 조정합니다.

파이썬 코드 (extract_frames.py)

import subprocess
import os
import sys
import argparse

def convert_to_wsl_path(path):
    """윈도우 경로를 WSL 경로로 변환"""
    if path.startswith("C:") or path.startswith("c:"):
        path = path.replace("\\", "/").replace("C:", "/mnt/c", 1).replace("c:", "/mnt/c", 1)
    return path

def decode_path(path):
    """경로를 UTF-8로 디코딩하여 한글 깨짐 방지"""
    try:
        return path.encode('utf-8').decode('utf-8')
    except (UnicodeEncodeError, UnicodeDecodeError):
        return path

def run_ffmpeg(input_file, sensitivity, min_skip, output_dir, output_prefix, use_gpu):
    # 경로 변환 및 디코딩
    input_file = decode_path(convert_to_wsl_path(input_file))
    output_dir = decode_path(convert_to_wsl_path(output_dir))
    output_path = os.path.join(output_dir, f"{output_prefix}_%04d.jpg")

    # 입력값 검증
    if not os.path.exists(input_file):
        print(f"Error: Input video file '{input_file}' does not exist.")
        sys.exit(1)
    if not os.path.exists(output_dir):
        print(f"Output directory '{output_dir}' does not exist. Creating it...")
        os.makedirs(output_dir)

    try:
        sensitivity = float(sensitivity)
        if not 0 <= sensitivity <= 1:
            raise ValueError("Sensitivity must be between 0 and 1.")
        min_skip = float(min_skip)
        if min_skip < 0:
            raise ValueError("Minimum skip time must be non-negative.")
    except ValueError as e:
        print(f"Error: {e}")
        sys.exit(1)

    # FFmpeg 명령어 구성
    ffmpeg_cmd = ["ffmpeg"]
    if use_gpu:
        ffmpeg_cmd.extend(["-c:v", "h264_cuvid"])
    ffmpeg_cmd.extend([
        "-i", input_file,
        "-vf", f"select='eq(n,0)+gt(scene,{sensitivity})*between(t,prev_selected_t+{min_skip},inf)',showinfo",
        "-vsync", "vfr",
        "-threads", "8",
        "-thread_type", "slice",
        "-c:v", "mjpeg",
        output_path
    ])

    # FFmpeg 실행
    print("Running FFmpeg...")
    print("Command:", " ".join(ffmpeg_cmd))
    try:
        result = subprocess.run(ffmpeg_cmd, capture_output=True, text=True, check=True)
        print("Success! Frames extracted to:", output_dir)
        print("FFmpeg output:", result.stderr)
    except subprocess.CalledProcessError as e:
        print("Error occurred during FFmpeg execution.")
        print("FFmpeg error:", e.stderr)
        sys.exit(1)
    except Exception as e:
        print(f"An error occurred: {e}")
        sys.exit(1)

def main():
    # 명령줄 인자 파싱
    parser = argparse.ArgumentParser(description="Extract frames from a video using FFmpeg.")
    parser.add_argument("--input", required=True, help="Path to the input video file (e.g., /mnt/c/녹화폴더/test.mp4)")
    parser.add_argument("--sensitivity", type=float, default=0.2, help="Scene change sensitivity (0~1, default: 0.2)")
    parser.add_argument("--min-skip", type=float, default=0.5, help="Minimum skip time in seconds (default: 0.5)")
    parser.add_argument("--output-dir", default="/home/doyo/py_pj/get_v2i", help="Output directory for extracted frames")
    parser.add_argument("--output-prefix", default="extracted_frame", help="Prefix for output image files")
    parser.add_argument("--use-gpu", action="store_true", help="Use GPU for decoding (requires NVIDIA GPU)")

    args = parser.parse_args()

    # FFmpeg 실행
    run_ffmpeg(
        input_file=args.input,
        sensitivity=args.sensitivity,
        min_skip=args.min_skip,
        output_dir=args.output_dir,
        output_prefix=args.output_prefix,
        use_gpu=args.use_gpu
    )

if __name__ == "__main__":
    main()

4. 실행 방법

(1) 코드 저장

위 코드를 extract_frames.py로 저장합니다.

nano /home/doyo/py_pj/get_v2i/extract_frames.py

코드를 붙여넣고 저장 (Ctrl+O, Enter, Ctrl+X).

(2) 실행 예시

기본 실행 (GPU 사용)

python3 extract_frames.py --input /mnt/c/녹화폴더/test.mp4 --sensitivity 0.2 --min-skip 0.5 --output-dir /home/doyo/py_pj/get_v2i --output-prefix extracted_frame --use-gpu

입력:

  • --input: /mnt/c/녹화폴더/test.mp4 (한글 경로 지원).
  • --sensitivity: 0.2 (장면 변화 민감도).
  • --min-skip: 0.5 (최소 스킵 시간, 초 단위).
  • --output-dir: /home/doyo/py_pj/get_v2i (출력 디렉토리).
  • --output-prefix: extracted_frame (출력 파일 접두사).
  • --use-gpu: GPU 사용 여부 (NVIDIA GPU 필요).

출력:

  • /home/doyo/py_pj/get_v2i/extracted_frame_0001.jpg, extracted_frame_0002.jpg, ...

GPU 없이 실행

python3 extract_frames.py --input /mnt/c/녹화폴더/test.mp4

기본값 사용:

  • --sensitivity: 0.2
  • --min-skip: 0.5
  • --output-dir: /home/doyo/py_pj/get_v2i
  • --output-prefix: extracted_frame
  • --use-gpu: False (GPU 사용 안 함).

(3) 출력 확인

출력 디렉토리 확인:

ls /home/doyo/py_pj/get_v2i

윈도우에서 확인:

explorer \\wsl.localhost\Ubuntu-24.04\home\doyo\py_pj\get_v2i

출력 예: extracted_frame_0001.jpg, extracted_frame_0002.jpg, ...

5. 과정 요약

(1) 환경 준비

WSL(Ubuntu-24.04)에서 FFmpeg 설치:

sudo apt update
sudo apt install ffmpeg -y

GPU 지원 확인 (선택):

nvidia-smi
ffmpeg -encoders | grep nvenc

(2) 파이썬 스크립트 작성

extract_frames.py 작성:

  • 명령줄 인자로 입력값 받기.
  • 한글 경로 지원 (decode_path 함수).
  • WSL 경로 변환 (convert_to_wsl_path 함수).
  • GPU 사용 여부에 따라 FFmpeg 명령어 조정.

(3) 실행

명령줄에서 실행:

python3 extract_frames.py --input /mnt/c/녹화폴더/test.mp4 --use-gpu

출력 파일 확인:

ls /home/doyo/py_pj/get_v2i

(4) 결과 확인

추출된 이미지 파일 확인:

  • WSL: /home/doyo/py_pj/get_v2i/extracted_frame_*.jpg
  • 윈도우: \\wsl.localhost\Ubuntu-24.04\home\doyo\py_pj\get_v2i

6. 추가 고려사항

(1) 한글 경로 문제

한글 경로(/mnt/c/녹화폴더)를 처리하기 위해 decode_path 함수를 추가했습니다.

WSL 로케일 설정 확인:

locale

LANG=ko_KR.UTF-8이어야 합니다. 설정 변경:

sudo locale-gen ko_KR.UTF-8
sudo update-locale LANG=ko_KR.UTF-8

(2) GPU 지원

GPU 사용 시 -c:v h264_cuvid 옵션 추가.

GPU 지원이 없으면 소프트웨어 디코딩으로 자동 전환.

(3) 에러 처리

입력 파일 및 출력 디렉토리 존재 여부 확인.

FFmpeg 실행 에러 시 상세 메시지 출력.

(4) 성능 최적화

WSL 파일 시스템(/home/doyo)에서 작업 시 성능 저하 가능:

출력 디렉토리를 /mnt/c로 변경하면 성능이 향상됩니다:

python3 extract_frames.py --input /mnt/c/녹화폴더/test.mp4 --output-dir /mnt/c/Users/DOYO/Desktop