CS285 강의를 수강하면서 Youtube 영어 자막에 굉장히 불편함을 느껴, 직접 영한 자막을 달아보기로 했다.

그 전에도 OpenAI 의 Whisper 로 음성을 쉽게 추출할 수 있는 걸 알았고 생각만 하고 있다가 사용해볼만 한 적절한 대상을 찾은 것이었다.

YoutubePlaylistDownloader

우선 플레이리스트에 있는 영상을 모두 받아줄 프로그램이 필요했다.

그래서 shaked6540/YoutubePlaylistDownloader를 사용하였다.

윈도우에서는 관리자 권한으로 실행해주어야 하고, .NET 0.10.0 설치가 되어있어야 한다.

faster-whisper 로 음성 추출하기

faster-whisper 는 Whisper 모델을 가속화한 모델로, 훨씬 빠른 속도로 Transcription 이 가능하다. 내 PC의 경우도 GTX 1660S 였음에도 괜찮은 추출 속도를 보여주었다.

Github LINK

모델 사용에 앞서 GPU 사용을 위해 CUDA, cuDNN 을 설치해주었다.

각 GPU 마다 맞는 CUDA 버전이 다르다고 해서 몇몇 글을 참고하였다.

CUDA, cuDNN Installation

References

NVIDIA 공식 문서는 항상 뒤죽박죽되어 있는 느낌이지만 아래 링크대로 차근차근 따라갔다.

CUDA Toolkit 은 11.4.4 버전으로 설치하였다. LINK

>nvcc -V
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2021 NVIDIA Corporation
Built on Mon_Oct_11_22:11:21_Pacific_Daylight_Time_2021
Cuda compilation tools, release 11.4, V11.4.152
Build cuda_11.4.r11.4/compiler.30521435_0

Windows 에서 deviceQuery 로 확인하려면 해당 폴더에 있는 비쥬얼 스튜디어 .sln 파일을 실행해서 빌드해주면 된다.

    디렉터리: C:\ProgramData\NVIDIA Corporation\CUDA Samples\v11.4


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----      2024-01-11   오전 2:15                0_Simple
d-----      2024-01-11   오전 2:15                1_Utilities
d-----      2024-01-11   오전 2:15                2_Graphics
d-----      2024-01-11   오전 2:15                3_Imaging
d-----      2024-01-11   오전 2:15                4_Finance
d-----      2024-01-11   오전 2:15                5_Simulations
d-----      2024-01-11   오전 2:15                6_Advanced
d-----      2024-01-11   오전 2:15                7_CUDALibraries
d-----      2024-01-11   오전 2:15                bin
d-----      2024-01-11   오전 2:15                common
-a----      2021-08-16   오후 1:14         108936 Samples_vs2017.sln
-a----      2021-08-16   오후 1:14         108944 Samples_vs2019.sln

버전에 맞게 나는 ./Samples_vs2019.sln 을 실행하였다. 빌드를 해주면 bin\win64\Debug 위치에 실행파일이 생긴다.

C:\ProgramData\NVIDIA Corporation\CUDA Samples\v11.4\bin\win64\Debug\deviceQuery.exe Starting...

 CUDA Device Query (Runtime API) version (CUDART static linking)

Detected 1 CUDA Capable device(s)

Device 0: "NVIDIA GeForce GTX 1660 SUPER"
  CUDA Driver Version / Runtime Version          12.3 / 11.4
  CUDA Capability Major/Minor version number:    7.5
  Total amount of global memory:                 6144 MBytes (6442123264 bytes)
  (022) Multiprocessors, (064) CUDA Cores/MP:    1408 CUDA Cores
  GPU Max Clock rate:                            1815 MHz (1.81 GHz)
  Memory Clock rate:                             7001 Mhz
  Memory Bus Width:                              192-bit
  L2 Cache Size:                                 1572864 bytes
  Maximum Texture Dimension Size (x,y,z)         1D=(131072), 2D=(131072, 65536), 3D=(16384, 16384, 16384)
  Maximum Layered 1D Texture Size, (num) layers  1D=(32768), 2048 layers
  Maximum Layered 2D Texture Size, (num) layers  2D=(32768, 32768), 2048 layers
  Total amount of constant memory:               65536 bytes
  Total amount of shared memory per block:       49152 bytes
  Total shared memory per multiprocessor:        65536 bytes
  Total number of registers available per block: 65536
  Warp size:                                     32
  Maximum number of threads per multiprocessor:  1024
  Maximum number of threads per block:           1024
  Max dimension size of a thread block (x,y,z): (1024, 1024, 64)
  Max dimension size of a grid size    (x,y,z): (2147483647, 65535, 65535)
  Maximum memory pitch:                          2147483647 bytes
  Texture alignment:                             512 bytes
  Concurrent copy and kernel execution:          Yes with 6 copy engine(s)
  Run time limit on kernels:                     Yes
  Integrated GPU sharing Host Memory:            No
  Support host page-locked memory mapping:       Yes
  Alignment requirement for Surfaces:            Yes
  Device has ECC support:                        Disabled
  CUDA Device Driver Mode (TCC or WDDM):         WDDM (Windows Display Driver Model)
  Device supports Unified Addressing (UVA):      Yes
  Device supports Managed Memory:                Yes
  Device supports Compute Preemption:            Yes
  Supports Cooperative Kernel Launch:            Yes
  Supports MultiDevice Co-op Kernel Launch:      No
  Device PCI Domain ID / Bus ID / location ID:   0 / 1 / 0
  Compute Mode:
     < Default (multiple host threads can use ::cudaSetDevice() with device simultaneously) >

deviceQuery, CUDA Driver = CUDART, CUDA Driver Version = 12.3, CUDA Runtime Version = 11.4, NumDevs = 1
Result = PASS

cuDNN 설치는 아래 링크대로 따라갔다.

NVIDIA Developer Program 에 등록하고 OS 와 CUDA 버전에 맞는 zip 파일을 다운로드 하였다.

여기에 있는 bin, include, lib 폴더를 아까의 CUDA 설치된 위치에 복사 붙여넣기 해준다.

faster-whisper 사용하기

이제 faster-whisper 를 이용해 다운로드한 유튜브 영상들의 음성을 추출하고 이를 SRT 파일로 만들어보자.

설치는 간단하게 pip 로 할 수 있다.

pip install faster-whisper

그리고 공식 문서 링크에 예시가 잘 나와있어서 따라하기만 하면 되었다.

나는 가지고 있는 GPU의 VRAM에 맞게 고르라고 하여서 medium 모델로 돌렸다. 모델에 대한 정보는 여기에 나와있다.

import os, math
from faster_whisper import WhisperModel
import logging
from tqdm import tqdm
import time
 
def to_srt_timecode(seconds):
		hours, remainder = divmod(seconds, 3600)
		minutes, seconds = divmod(remainder, 60)
		milliseconds = math.floor((seconds % 1) * 1000)
		output = f"{int(hours):02}:{int(minutes):02}:{int(seconds):02},{milliseconds:03}"
		return output
 
# Initialize Whisper Model
model_size = "medium" # or "large-v3"
model = WhisperModel(model_size, device="cuda", compute_type="float16")
 
# Directory containing your CS285 videos
video_directory = "./videos/CS285"
video_files = [f for f in os.listdir(video_directory) if f.endswith('.mkv')]
srt_file_directory = "./videos/CS285/SRT"
 
for video_file in video_files:
		video_path = os.path.join(video_directory, video_file)
		segments, info = model.transcribe(video_path, beam_size=5)
 
		srt_file_name = os.path.splitext(video_file)[0] + ".srt"
		srt_file_path = os.path.join(srt_file_directory, srt_file_name)
 
		# Write to SRT file
		timestamps = 0.0
		with open(srt_file_path, "w") as file:
				with tqdm(total = info.duration, unit = " audio seconds") as pbar:
					for i, segment in enumerate(segments):
							start_time = to_srt_timecode(segment.start)
							end_time = to_srt_timecode(segment.end)
							file.write(f"{i + 1}\n")
							file.write(f"{start_time} --> {end_time}\n")
							file.write(segment.text.lstrip() + "\n\n")
							pbar.update(segment.end - timestamps)
							timestamps = segment.end
					if timestamps < info.duration:
						pbar.update(info.duration - timestamps)

실제 코드에서는 폴더 안에 있는 모든 영상을 알아서 추출하고 싶어서 약간의 수정을 더 하였다. 그리고 print() 문이나 tqdm 이 없으면 각 파일마다 진행도를 알기 어려워서 터미널 출력도 추가하였다.

DeepL API 로 SRT 파일 번역하기

만들어진 SRT 파일은 다양한 방법으로 번역할 수 있다. ChatGPT 로 번역해도 되고, API로 해도 되고 Google Translation API 도 있다.

그 중 DeepL 을 직접 잘쓰고 있기도 하고, 성능이 제일 좋은 것 같다는 이야기도 많았어서 DeepL API 로 번역해보기로 하였다.

DeepL API 는 계정을 가입하고 카드 등록만 해도 Free API 가 주어진다. 한 달에 500,000 자 제한이 있어 많이 번역은 어렵지만 사용해 볼 만 하다. LINK

그리고 이걸 SRT 번역에 손쉽게 사용하게 도와주는 레포를 찾아서 이를 활용하였다.

Github LINK

마찬가지로 pip 로 손쉽게 설치하고, API 가 없어도 여러 번 번역기를 호출하면서 번역을 할 수도 있다. 이번 경우에도 제한을 다 사용해서 제공하는 다른 번역기도 사용하였다. (각각의 차이는 정확히는 모르겠다.)

import os
import glob
 
from srtranslator import SrtFile
from srtranslator.translators.deepl_api import DeeplApi
from srtranslator.translators.deepl_scrap import DeeplTranslator
from srtranslator.translators.selenium_utils import create_proxy, create_driver
 
# DEEPL_API_KEY = os.getenv("DEEPL_API_KEY")
# if DEEPL_API_KEY is None:
# 	print("No API key found. Please set the DEEPL_API_KEY using 'export' or 'set'")
# 	exit(1)
 
folder = "./videos/CS285/SRT"
for filepath in glob.glob(os.path.join(folder, "*.srt")):
    # translator = DeeplApi(DEEPL_API_KEY)
    translator = DeeplTranslator()
 
    srt = SrtFile(filepath)
    srt.translate(translator, "en", "ko")
    srt.wrap_lines()
    
    new_filename = "KOR_" + os.path.basename(filepath)
    new_filepath = os.path.join(folder, new_filename)
    srt.save(new_filepath)
 
    translator.quit()
    print(f"Translated and saved: {new_filepath}")

DeepL Python API 공식 문서도 굉장히 잘되어 있어서 직접 구현하는 것도 크게 어렵진 않을 것 같다. LINK

다른 SRT 번역기들

최근에 GPT Store 가 생기면서 각자의 GPTs 를 만들어서 공유하고 있다. 거기에 subtitle 로 검색해서 나온 몇 가지를 사용해보았는데, 대부분 python 스크립트로 연동해서 만들어진 것 같다.

하지만 한국어로 번역을 시키면 모두 처리 도중에 에러가 나타났다.

직접 DeepL, 파파고나 구글 번역기에서 나눠서 번역하는 것도 글자 수 제한때문에 쉽지 않고, DeepL API 제한수가 끝나면 더이상 양질의 번역이 어려운 것도 아쉬움이 있다.

subtitletranslator.com 에서 여러 개의 파일을 한번에 번역하고 다운로드 할 수 있게 해준다. 어떻게 이렇게 빠르게 가능한지는 잘 모르겠으나, 번역 상태를 보았을 때 꽤 괜찮았다.

DeepL

55
00:04:37,759 --> 00:04:42,959
훨씬 더 편리해집니다. 의사 결정 및 제어에서도 마찬가지입니다.
 
56
00:04:42,959 --> 00:04:48,240
정책을 확률 분포로 훈련하는 것이 훨씬 더 편리합니다.
 
57
00:04:48,240 --> 00:04:55,040
훨씬 더 편리합니다. 이제 소개해야 할 용어가 하나 더 있습니다,
 
58
00:04:55,040 --> 00:04:59,680
이제 순차적 의사 결정의 몇 가지 특징에 대해 알아보겠습니다,
 
59
00:04:59,680 --> 00:05:05,680
바로 상태라는 개념입니다. 상태는 문자 S와 기호
 
60
00:05:05,680 --> 00:05:12,079
첨자 T로 표시되며, 상태는 일반적으로 관찰과는 별개의 개념입니다. 이를 이해하려면

subtitletranslator.com

55
00:04:37,759 --> 00:04:42,959
훨씬 더 편리해졌습니다. 의사 결정과 통제도 마찬가지입니다.
 
56
00:04:42,959 --> 00:04:48,240
확률 분포로서의 정책은 결국에는 훨씬 더 편리합니다.
 
57
00:04:48,240 --> 00:04:55,040
우리는 단 하나의 최선의 행동만을 원합니다. 이제 우리가 소개해야 할 용어가 하나 더 있습니다.
 
58
00:04:55,040 --> 00:04:59,680
여기서는 순차적 의사결정의 몇 가지 특이한 점에 대해 알아보겠습니다.
 
59
00:04:59,680 --> 00:05:05,680
국가라는 개념이다. 상태는 문자 S로 표시됩니다.
 
60
00:05:05,680 --> 00:05:12,079
첨자 T이며 상태는 일반적으로 관찰과 별개입니다. 이것을 이해하다

결과물

Adobe Premiere Pro 로 자막을 넣어준 모습. 수학 notation 이나 용어들도 직접 번역되거나 (e.g. : 파이, policy : 정책) 내용이 길면 줄바꿈이 되어서 세 줄로 나오는 등 아쉬움도 있지만 전반적으로 잘 되었다.