동영상 속의 자막으로부터 SBV 파일 추출하기

  • 저는 블로그 뿐만 아니라 유튜브 채널도 운영하고 있는데요.
  • 비록 미미한 구독자수와 형편없는 조회수를 자랑하지만, 그래도 1년 넘게 꾸준히 영상을 업로드하고 있습니다.
  • 제가 만드는 컨텐츠들은 주로 영상속에 포함되어 렌더링되는 자막에 크게 의존하고 있는데요. 내용을 이해하는데 이 자막들이 상당히 중요합니다.
  • 그런데 구독자들 중에 외국인들이 생기면서 몇 가지 고민거리가 생겼어요. 그 고민거리는 아래와 같습니다.
    • 적어도 영어 자막은 제공하고 싶다.
    • 사용하는 영상제작 프로그램에서 sbv와 같은 시퀀싱 자막 파일을 추출할 수 없다.
    • 매번 유튜브 자막제작툴을 이용하고 있는데, 타임 레인지 맞추는게 너무 불편하다.

자막편집의고통

  • 이런 고민을 해결해보려고 여러가지 프로그램을 찾아봤지만, 뾰족한 대안을 찾기어려웠어요.
  • 그래서! 직접 만들어 봤습니다. 전체코드는 아래 링크로 공유할게요.
import easyocr
from difflib import SequenceMatcher
import cv2
import os
import numpy
import scipy.cluster.hierarchy as hcluster
import matplotlib.pyplot as plt
from datetime import timedelta

def get_text_from_frame(img_file):
    reader = easyocr.Reader(['ko','en']) # this needs to run only once to load the model into memory
    result = reader.readtext(img_file)
    if len(result) > 0:
        text = ''
        for item in result:
            if len(item) > 1:
                text = "%s %s" % (text, item[1])
        return text
    return ''

def text_diff(t1, t2):
    return SequenceMatcher(None, t1, t2).ratio()

def frame_to_time(fps, frame):
    as_msecond = (frame / fps) * 1000
    td = timedelta(milliseconds=as_msecond)
    if str(td).find('.') == -1:
        return "%s.000" % str(td)
    return str(td)[:-3]

filepath = '[추출할동영상].mp4'
video = cv2.VideoCapture(filepath)
if not video.isOpened():
    print("Could not Open :", filepath)
    exit(0)
length = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
width = int(video.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(video.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = video.get(cv2.CAP_PROP_FPS)
fps2 = int(fps/2)
before_text = ''
count = 0
before_diff = 0
start_frame = 0
end_frame = 0
data = []
text_count = {}
while(video.isOpened() and count < length):
    ret, image = video.read()
    if(int(video.get(1)) % fps2 == 0): #앞서 불러온 fps 값을 사용하여 0.5초마다 추출
        cv2.imwrite('frame.png', image)
        text = get_text_from_frame('frame.png')
        diff = text_diff(text, before_text)
        if diff < 0.75:
            diff = 0.0
        else:
            diff = 1.0
        if text == '':
            diff = 0.0
        else:
            if text in text_count:
                text_count[text] = text_count[text] + 1
            else:
                text_count[text] = 1
        print("%s, %s" % (diff, text))
        if before_diff == 0.0 and diff == 1.0:
            start_frame = count
        elif before_diff == 1.0 and diff == 0.0:
            end_frame = count
            sorted_text_count = sorted(text_count.items(), key = lambda item: item[1], reverse = True)
            print(sorted_text_count)
            if sorted_text_count[0][0] != '':
                data.append([start_frame, end_frame, sorted_text_count[0][0]])
            text_count = {}
        before_diff = diff
        before_text = text

    print("%d / %d" % (count, length))
    if count < length:
        count = count + 1

if start_frame > end_frame:
    end_frame = count
    sorted_text_count = sorted(text_count.items(), key = lambda item: item[1], reverse = True)
    print(sorted_text_count)
    if sorted_text_count[0][0] != '':
        data.append([start_frame, end_frame, sorted_text_count[0][0]])

video.release()

f = open('[자막파일명].sbv', 'w')
for cap in data:
    start_time = frame_to_time(fps, cap[0])
    end_time = frame_to_time(fps, cap[1])
    f.write("%s,%s\n" % (start_time, end_time))
    f.write("%s\n\n" % cap[2])
f.close()

전체코드 다운로드

  • 주의사항
    • CUDA를 지원하는 GPU가 없을 경우 CPU만 이용해서 OCR을 수행하기 때문에 매우 느립니다.
    • GPU가 없다면 구글 코랩에서 하드웨어 가속기를 이용해보세요.
  • 원래는 번역까지 자동으로 되게하는 코드를 작성하려고 했었습니다만…
  • OCR의 품질이 번역 가능할 수 있는 정도까지 결과를 만들어내지는 못합니다.
  • 일단 타임레인지를 잡아내는 정도에서 만족해 보려고 하구요.
  • 아래 영상은 자동으로 추출한 타임레인지의 SBV 파일에서 영어 번역만 수행해서 올린 것 입니다.
Share: Twitter Facebook
김민석's Picture

About 김민석

항상 공부가 부족한 개발자, 항상 시간이 부족한 딸바보, 항상 체력이 부족한 부족한남편, 그리고 고양이 집사

JungNangGu, Seoul, Korea Rep https://reddol18.pe.kr

Comments