동영상 속의 자막으로부터 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