YouTube 비디오에서 음성 텍스트를 추출해서 요약하는 방법
YouTube 비디오에서 음성을 추출하고 이를 텍스트로 변환한 후, 요약하는 방법을 소개합니다. 이번 과정에서는 yt_dlp
, whisper
, 그리고 langchain
라이브러리를 사용하여 자동으로 텍스트를 요약하는 기능을 구현합니다.
ref. https://www.youtube.com/watch?v=oyotmQ-vQLs&t=729s
1. 필요한 라이브러리 설치
아래 명령어를 실행하여 필요한 라이브러리를 설치합니다.
pip install yt-dlp openai-whisper langchain
2. YouTube 비디오에서 음성 추출하기
먼저, yt_dlp
를 이용해 YouTube 비디오에서 오디오를 추출합니다.
import yt_dlp
youtube_link = "https://youtu.be/mCyY8pQDpJM"
ydl_opts = {
'format': 'bestaudio/best',
'outtmpl': 'downloaded_audio.%(ext)s',
'postprocessors': [{
"key": "FFmpegExtractAudio",
"preferredcodec": "mp3",
"preferredquality": "320",
}],
}
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
ydl.download([youtube_link])
3. 음성을 텍스트로 변환하기
OpenAI의 whisper
를 이용하여 오디오 파일을 텍스트로 변환합니다.
import whisper
model = whisper.load_model("small") # 작은 모델을 사용하여 변환 속도를 향상
result = model.transcribe("downloaded_audio.mp3")
text = result["text"]
print("추출된 텍스트:", text[:500]) # 첫 500자 출력
4. 텍스트를 작은 단위로 분할하기
긴 텍스트를 효과적으로 처리하기 위해 RecursiveCharacterTextSplitter
를 사용하여 나눕니다.
from langchain.schema.document import Document
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
docs = [Document(page_content=x) for x in text_splitter.split_text(text)]
split_docs = text_splitter.split_documents(docs)
5. 텍스트 요약하기
LangChain의 MapReduceDocumentsChain
을 사용하여 텍스트를 요약합니다.
from langchain.chains import MapReduceDocumentsChain
from langchain.chains.llm import LLMChain
from langchain.chains.combine_documents.stuff import StuffDocumentsChain
from langchain_core.prompts import ChatPromptTemplate
from langchain_ollama import ChatOllama
llm = ChatOllama(model="exaone3.5", temperature=0)
# Map 단계 (부분 요약)
map_template = """다음 문서를 기반으로 주요 내용을 요약하세요:
{docs}
"""
map_prompt = ChatPromptTemplate([("human", map_template)])
map_chain = LLMChain(llm=llm, prompt=map_prompt)
# Reduce 단계 (최종 요약)
reduce_template = """다음 요약된 내용을 종합하여 최종 요약을 작성하세요:
{doc_summaries}
"""
reduce_prompt = ChatPromptTemplate([("human", reduce_template)])
reduce_chain = LLMChain(llm=llm, prompt=reduce_prompt)
combine_documents_chain = StuffDocumentsChain(llm_chain=reduce_chain, document_variable_name="doc_summaries")
map_reduce_chain = MapReduceDocumentsChain(
llm_chain=map_chain,
reduce_documents_chain=combine_documents_chain,
document_variable_name="docs",
return_intermediate_steps=False,
)
summary = map_reduce_chain.run(split_docs)
print("요약된 내용:", summary)
6. 결과 확인
위 과정을 실행하면 YouTube 비디오에서 추출된 음성이 텍스트로 변환된 후, 주요 내용만 요약되어 출력됩니다.
이 방법을 활용하면 긴 동영상 콘텐츠를 빠르게 요약하여 중요한 내용을 쉽게 파악할 수 있습니다.
'''
ref:
1. YouTube 비디오에서 음성 추출: https://theonly1.tistory.com/2479
2. 음성에서 텍스트 추출: https://m.blog.naver.com/baemsu/223239058462?recommendTrackingCode=2
3. 텍스트를 요약: https://python.langchain.com/docs/versions/migrating_chains/map_reduce_chain/
YouTube 비디오 = "https://youtu.be/mCyY8pQDpJM?si=sd324t9fTg9EDZBM" 1'40"
112 출동' 경찰관 흉기 피습..범인은 실탄에 맞아 사망 (2025.02.26/12MBC뉴스)
'''
from langchain_ollama import ChatOllama
import yt_dlp
import whisper
from langchain.schema.document import Document
from langchain.text_splitter import RecursiveCharacterTextSplitter
# from langchain.prompts import PromptTemplate
from langchain.chains.llm import LLMChain
llm = ChatOllama(model="exaone3.5", temperature=0)
# YouTube 비디오에서 음성 추출
''''''
youtube_link = "https://youtu.be/mCyY8pQDpJM?si=sd324t9fTg9EDZBM"
ydl_opts = {
'format': 'bestaudio/best',
'outtmpl': 'downloaded_audio.%(ext)s',
'postprocessors': [{
"key": "FFmpegExtractAudio",
"preferredcodec": "mp3",
"preferredquality": "320",
}],
}
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
ydl.download([youtube_link])
# 음성에서 텍스트 추출
# 추출에 시간이 너무 많이 걸리는데 진행 상황을 트래킹할 수 있나?
model = whisper.load_model("tiny") # "tiny", "base", "small", "medium", "large"
result = model.transcribe("downloaded_audio.mp3")
# # 추출된 텍스트의 길이
# print("추출된 텍스트의 길이: ", len(result["text"]))
# # 추출된 텍스트의 첫 1000자
# print("추출된 텍스트의 첫 1000자: ", result["text"][:1000])
# # 세그먼트 정보 확인
# print("세그먼트 정보: ", result["segments"][:1])
# 텍스트를 요약
text_splitter = RecursiveCharacterTextSplitter( # RecursiveCharacterTextSplitter 초기화
chunk_size=1000, # 원하는 청크 크기
chunk_overlap=200 # 청크 간의 중첩 크기
)
docs = [Document(page_content=x) for x in text_splitter.split_text(result["text"])]
split_docs = text_splitter.split_documents(docs)
print("split_docs 정보: ", split_docs)
# MapReduceChain 구성
from langchain.chains import MapReduceDocumentsChain, ReduceDocumentsChain
from langchain.chains.combine_documents.stuff import StuffDocumentsChain
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
# Map
# Map 프롬프트 , "Write a concise summary of the following: {docs}."
map_template = """다음은 여러 개의 문서입니다.
{docs}
이 문서 목록을 기반으로 주요 테마를 식별해 주세요."""
map_prompt = ChatPromptTemplate([("human", map_template)])
# Map 체인
map_chain = LLMChain(llm=llm, prompt=map_prompt)
# map_chain = map_prompt | llm | StrOutputParser()
# reduce_chain = reduce_prompt | llm | StrOutputParser()
# Reduce
# Reduce 프롬프트
# reduce_template = """
# The following is a set of summaries:
# {docs}
# Take these and distill it into a final, consolidated summary
# of the main themes.
# """
# 최종 답변은 한국어로 약 100단어 정도의 단락이어야 합니다.
reduce_template = """당신은 리포터입니다. 물론입니다 등의 불필요한 말은 사용하지 마세요. 다음은 여러 개의 요약입니다:
{doc_summaries}
이 요약들을 바탕으로 주요 테마를 대략 다섯 개의 단락으로 친절하게 이야기 해주세요."""
reduce_prompt = ChatPromptTemplate([("human", reduce_template)])
# Reduce 체인
reduce_chain = LLMChain(llm=llm, prompt=reduce_prompt)
# Takes a list of documents, combines them into a single string, and passes this to an LLMChain
# 서 목록을 받아 이를 하나의 문자열로 결합한 후, LLMChain에 전달한다.
combine_documents_chain = StuffDocumentsChain(
llm_chain=reduce_chain, document_variable_name="doc_summaries"
)
# Combines and iteratively reduces the mapped documents
reduce_documents_chain = ReduceDocumentsChain(
# This is final chain that is called.
combine_documents_chain=combine_documents_chain,
# If documents exceed context for `StuffDocumentsChain`
collapse_documents_chain=combine_documents_chain,
# The maximum number of tokens to group documents into.
token_max=2000,
)
# Combining documents by mapping a chain over them, then combining results
map_reduce_chain = MapReduceDocumentsChain(
# Map chain
llm_chain=map_chain,
# Reduce chain
reduce_documents_chain=reduce_documents_chain,
# The variable name in the llm_chain to put the documents in
document_variable_name="docs",
# Return the results of the map steps in the output
return_intermediate_steps=False,
)
sum_result = map_reduce_chain.run(split_docs)
print(sum_result)
# response = llm .invoke("<내용>" + assistant_content + "</내용> <내용>을 한국어로 적어줘.")
# print(response)
0 댓글