AI × 自動化:YouTube動画を要約してメール送信

今回は、Youtubeの動画音声をダウンロードし、文字起こし&要約するPythonスクリプトを作成したので、そちらを紹介していきます

プロジェクト概要

YouTubeの動画から音声を取得し、その内容を文字起こし&要約するPythonスクリプトを作成しました
具体的には、以下の2つのステップで処理を行います

  1. YouTube動画の音声をMP3ファイルとしてダウンロード
  2. Gemini 2.0 を使用して文字起こしと要約を実施

使用した主な技術

  • pytube
    YouTubeの音声をダウンロードするための強力なツール。動画のURLを指定することで、音声ファイルをMP3形式で保存できます
  • Gemini 2.0-Flash
    Googleの最新AIモデルを使用し、音声の文字起こしと要約を実施。高精度な自然言語処理が可能で、長文の音声内容も効率的に整理できます

この後のセクションでは、それぞれの処理を詳しく解説していきます。

YouTube動画ダウンロード

今回Youtubeの動画をダウンロードするため、以下の2つの方法を試しました

  1. yt_dlp ライブラリを用いる方法
  2. pytube ライブラリを用いる方法

最終的には後者を用いることにしたわけですが、それぞれの説明と併せて、そこに至った経緯を記しておきたいと思います

yt_dlp ライブラリ

yt_dlpライブラリは`youtube-dl`を改良した強力なツールで、高機能かつ更新が早く、Youtube以外のサイト(ニコニコ動画、Twitterなど)にも対応していることが特徴です

サンプルコード

import os
import yt_dlp
from urllib.parse import urlparse, parse_qs


def get_video_id(url):
   parsed_url = urlparse(url)
   video_id = parse_qs(parsed_url.query).get("v", [""])[0]
   return video_id


def download_audio(video_url):
   video_id = get_video_id(video_url)
   os.makedirs("data", exist_ok=True)
   audio_path = f"data/{video_id}.mp3"

   ydl_opts = {
       "format": "bestaudio/best",
       "outtmpl": audio_path,
       "postprocessors": [
           {"key": "FFmpegExtractAudio", "preferredcodec": "mp3", "preferredquality": "192"},
       ],
   }

   with yt_dlp.YoutubeDL(ydl_opts) as ydl:
       ydl.download([video_url])

   return audio_path


# 動画URL
video_url = "https://www.youtube.com/watch?v=XXXX"

# 音声ダウンロード
downloaded_file = download_audio(video_url)

print("保存先:", downloaded_file)  # data/XXXX.mp3

実行結果

yt_dlp.utils.DownloadError: ERROR: Postprocessing: ffprobe and ffmpeg not found. Please install or provide the path using --ffmpeg-location

このエラーはffmpegがインストールされていないことが原因のものなので、インストールしましょう

Macの場合

brew install ffmpeg

Windowの場合

scoop install ffmpeg

Ubuntuの場合

sudo apt update
sudo apt install ffmpeg

インストールしたらもう一度実行をかけると…

[youtube] Extracting URL: https://www.youtube.com/watch?v=O8ApXMKy0sY
[youtube] O8ApXMKy0sY: Downloading webpage
[youtube] O8ApXMKy0sY: Downloading tv client config
[youtube] O8ApXMKy0sY: Downloading player 9c6dfc4a
[youtube] O8ApXMKy0sY: Downloading tv player API JSON
[youtube] O8ApXMKy0sY: Downloading ios player API JSON
[youtube] O8ApXMKy0sY: Downloading m3u8 information
[info] O8ApXMKy0sY: Downloading 1 format(s): 251
[download] data/O8ApXMKy0sY.mp3 has already been downloaded
[download] 100% of   14.59MiB
[ExtractAudio] Destination: data/O8ApXMKy0sY.mp3
Deleting original file data/O8ApXMKy0sY.orig.mp3 (pass -k to keep)
保存先: data/O8ApXMKy0sY.mp3

Process finished with exit code 0
画像:ダウンロードされたファイル

無事ダウンロードに成功しました!

応用

以下のように書くとYoutubeのプレイリストもダウンロードが可能です

import yt_dlp

playlist_url = "https://www.youtube.com/playlist?list=XXXXXXX"
ydl_opts = {"outtmpl": "%(title)s.%(ext)s"}

with yt_dlp.YoutubeDL(ydl_opts) as ydl:
    ydl.download([playlist_url])
print("downloaded playlist")

pytubeライブラリ

こちらのライブラリはPythonでYouTube動画をダウンロードするためのライブラリです
使用時には、pip install pytubeでインストールします

サンプルコード

import os
from pytube import YouTube
from urllib.parse import urlparse, parse_qs


def get_video_id(url):
   parsed_url = urlparse(url)
   video_id = parse_qs(parsed_url.query).get("v", [""])[0]
   return video_id


def download_audio(video_url):
   yt = YouTube(video_url)
   video_id = get_video_id(video_url)

   # data フォルダを作成(存在しない場合)
   os.makedirs("data", exist_ok=True)

   # 音声ストリームをダウンロード
   audio_stream = yt.streams.filter(only_audio=True).first()
   audio_path = f"data/{video_id}.mp3"
   audio_stream.download(filename=audio_path)

   return audio_path


# 動画URL
video_url = "https://www.youtube.com/watch?v=XXXXXX"

# 音声ダウンロード
downloaded_file = download_audio(video_url)

print("保存先:", downloaded_file)

実行結果

Traceback (most recent call last):
  File "/Users/engineer-mac-015/Git/youtube_transcript/test.py", line 31, in <module>
    downloaded_file = download_audio(video_url)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^

〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜 # 長いので省略

"/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/urllib/request.py", line 643, in http_error_default
    raise HTTPError(req.full_url, code, msg, hdrs, fp)
urllib.error.HTTPError: HTTP Error 403: Forbidden

HTTP Error 403 Forbiddenと出てしまいました

調べてみると、pip install pytubefixでインストールすればうまくいくという記事を見つけたので、コードのインポート部分もfrom pytubefix import YouTube

と修正して試してみます。

参考記事:https://stackoverflow.com/questions/79226520/pytube-throws-http-error-403-forbidden-since-a-few-days

実行結果2

保存先: data/O8ApXMKy0sY.mp3

Process finished with exit code 0

成功しました

なので今回、`pytube`を用いたと書きましたが、正確には`pytubefix`を用いたということになりますね

今回は音声部分のみ抽出できれば良かったのでaudio_stream = yt.streams.filter(only_audio=True).first()としていますが、stream = yt.streams.get_highest_resolution()とし、出力ファイルの拡張子を.mp4とすれば、最高画質での動画ファイルダウンロードも可能です

応用

ループ処理は必要ですが、pytubeにもプレイリストダウンロードの機能は備わっています

from pytube import Playlist

playlist_url = "https://www.youtube.com/playlist?list=XXXXXXX"
playlist = Playlist(playlist_url)

for video in playlist.videos:
   video.streams.get_highest_resolution().download()
   print(f"{video.title} をダウンロードしました!")

pytubeとyt_dlpの比較表

項目 pytube yt_dpl
シンプルさ シンプルで簡単 やや複雑
機能 動画・音声DL 音声・音声DL, Youtube以外にも対応
更新頻度 たまに止まることあり 更新頻度が高く安定
プレイリストDL ループ処理が必要 1コマンドで可能
おすすめ用途 シンプルなYoutube動画・音声のダウンロード 高機能なダウンロード

今回の使用技術の選定理由

以上の2つの方法から今回は`pytube`を選択したわけですが、決め手としては「シンプルさ」です
今回は音声ファイルがダウンロードできれば事足りますし、そこまで複雑なことをする必要もないということでこちらを使用しました

!注意点!

どちらのライブラリも非公式であり、使用する際はYouTubeの利用規約と著作権法を遵守する必要があります
動画を無断ダウンロードし、配布する行為は法的に許されていない場合がほとんどですので、利用する際は個人利用に限り、自己責任で行いましょう

ダウンロードファイルの文字起こし&要約

MP3ファイルの文字起こしと要約には、2025年2月5日から一般提供が開始された Gemini 2.0 Flash を用いました


Gemini 2.0 Flash
Gemini 2.0 Flash 詳細

ある程度無料で使用できる上、画像にある通り、音声・動画の入力に対応しており、今回のプロジェクトにうってつけだということでこちらを採用しました
ということで実装していきます。

Gemini設定

import os
import google.generativeai as genai
from logging import getLogger, basicConfig, INFO

basicConfig(level=INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = getLogger(__name__)

# API key は環境変数で渡す
GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
genai.configure(api_key=GOOGLE_API_KEY)
model = genai.GenerativeModel("gemini-2.0-flash-001") # 安定版の gemini-2.0-flash-001 を選択

文字起こしパート

def transcript(audio_path):
   logger.info(f"Transcribe file:{audio_path}")

  # gemini に音声ファイルを渡す
   audio_file = genai.upload_file(path=audio_path)

   # プロンプトの準備
   response = model.generate_content(
       [
           "次の音声ファイルの内容を文字起こししてください。",
           audio_file
       ]
   )

   logger.info(f"Transcription result: {response.text}")

   return response.text

要約パート

def summary_response(txt, title, link):
   logger.info("Summarize")

   prompt = f"次のテキストの内容を日本語で要約してください。\n「{txt}」"
   response = model.generate_content(prompt).text

   logger.info(f"Summary result:\n{response}")

   return response

テスト実行

実装が完了したので、音声ファイルを渡してみます

テストとして、Global AI summit starts in Paris | BBC Newsを文字起こし&要約してもらいます
動画タイトルは「「紙ストロー廃止」の大統領令」です

文字起こし結果

2025-02-11 19:01:50,436 - INFO - Transcription result:
アメリカ の トランプ 大統領 は 10 日 、 バイデン 前 政権 に よる プラスチック 製 から 紙 ストロー へ の 推進 を 中止 する 大統領 令 に 署名 し まし た 。 トランプ 大統領 は 先週 X に バイデン 氏 が 推進 し た 馬鹿げ た 紙 の ストロー を 終わら せる ため に 大統領 令 に 署名 する など と 投稿 し て い まし た 。 アメリカ 国内 で は 脱 プラスチック の 取り組み が 広がっ て い まし た が 、 トランプ 氏 は 環境 へ の 影響 も 問題 ない と の 見 方 を 示し まし た 。

要約結果

2025-02-11 19:01:51,755 - INFO - Summary result:
トランプ大統領は、バイデン前政権が進めていたプラスチック製ストローから紙製ストローへの移行を中止させる大統領令に署名しました。これは、トランプ氏が以前から批判していた政策で、環境への影響も問題ないと主張しています。アメリカ国内で広がりつつあった脱プラスチックの動きに逆行する形です。

問題なく文字起こしされ、要約結果も文字起こし内容と相違なく、うまく要約されていることがわかります

テスト実行(英語動画)

英語の動画だとどうなるでしょうか

続いてBBC Newsの ニュース動画を渡してみましょう
動画タイトルは「Global AI summit starts in Paris | BBC News」です

文字起こし結果

2025-02-11 19:10:22,735 - INFO - Transcription result:
A two-day global summit is underway in Paris to discuss the future of artificial intelligence. The AI Action Summit is billed as a discussion on how to balance the development of the technology with its impact on society and the environment. Among those attending are US Vice President JD Vance, who arrived at Orly Airport in the French capital with family members on board Air Force 2. The Indian Prime Minister Narendra Modi is also in Paris, as well as China's vice premier Ding Shi, as well as the bosses of Google and OpenAI. Our AI correspondent, Mark Chisla reports. A host of world leaders, top tech executives and AI experts are gathering in Paris for the AI Action Summit. Discussions on AI safety and regulation will likely take a back seat as event organizers say the summit will be more concerned with immediate issues such as AI's effect on jobs and the environment, as well as AI's development for the betterment of humanity or the public good. For a week ahead of the AI action summit, the French government has laid on a number of scientific and cultural events like this art exhibition. A lot of the work here explores the opportunities, but also talks about the race to become an AI global superpower, a theme that's likely to emerge at the summit itself. Against this background of global competition, what does the UK hope to get from the summit? All these senior people from government are coming together and they're going to be colliding together with the the tech sector, but also those investors out there. So there's going to be a lot of conversations about where we are with safety, where the technology is going, the relationships, the global relationships because AI is becoming an increasingly important factor in global power. But my job is also to be going around to all of these people, telling them about the scale of British ambition, to talk about the investment opportunities that we have back there in Britain and ensuring that everybody who comes here leaves knowing full well just what the what the potential of Britain is in the digital age. The summit's hosted by French President Emmanuel Macron. Congratulations Mr. Vice President. In attendance will be US Vice President JD Vance and tech CEOs Sam Altman of Open AI and Demis Sabis of Google Deep Mind, as well as Chinese vice premier Jao Ching and Indian Prime Minister Narendra Modi co-chairing the whole event. But as policy makers debate the future, a new disruptive Chinese AI model Deepseek has shaken up the industry with its cheaper approach to developing the technology. Industry experts worry that competition between nations and tech companies could be to the detriment of developing this technology safely. The biggest challenge in Paris will likely be balancing this competition against the summit's ambition to support AI technology that benefits all humanity. Mark Chisla, BBC News. And let's join Mark live in Paris now. And Mark, just picking up on that point you made at the end of your report. there's a bit of an arms race, isn't there frankly in terms of AI development. So has the horse already bolted from the stable of regulation? Well, at the moment, one of the most interesting aspects of this particular summit has been looking at all of the vying for position, seeing who is going to become the next global AI superpower. When we think about regulation and safety, a lot of those things have taken a bit of a back seat at this summit in comparison to the first AI summit at Bley Park in 2023. That was that was labeled as an AI safety summit. This is an AI action summit. But most interestingly, a lot of the industry experts that I've spoken to, people at the very, very top of their game, quite a few of them have said that safety is still a massive concern. It's an issue that hasn't yet been fully resolved. Yes, because if they say that part of the mission for these two days is to think about AI's impact on society, on the environment. Well, naturally safety is is part of that, isn't it? Um so how consequential will these two days be? Can we expect any sort of joint uh statement at the end of them? Yeah, there's very likely there'll be a declaration at the end of the event, but there isn't going to be anything new on regulation. The most important thing about events like these is bringing together people from politics, bringing together people from technology, people that have the ability to change the dial, the decision makers, putting them all in the same space and letting them have a conversation about where this technology might go in the next few years, and more importantly, how it might benefit all of our lives.

要約結果

2025-02-11 19:10:26,104 - INFO - Summary result:
パリで2日間のAIアクションサミットが開催されており、AIの発展と社会・環境への影響のバランスを議論しています。各国の首脳、テック企業の幹部、AI専門家が集まり、AIの安全性や規制よりも、雇用や環境への影響、人類の向上といった喫緊の課題に焦点が当てられます。サミットでは、AIのグローバルな覇権争いがテーマになる可能性があり、各国がAI開発競争を繰り広げる中で、安全な技術開発とのバランスが課題となります。イギリスは、投資機会をアピールし、AIにおけるイギリスの潜在能力を示すことを目指しています。サミットの最後には宣言が出される見込みですが、規制に関する新たな動きはなく、政治、技術、投資分野のリーダーたちが集まり、AI技術の将来と人類への貢献について議論することが重要視されています。専門家の中には、AIの安全性が依然として懸念事項であると指摘する声も上がっています。

文字起こししてみると、非常に長い文章が出力されましたが、要約結果では要点に絞って要約されている上、プロンプトに「日本語で」と入れていたおかげで翻訳もしてくれていますね
精度も申し分ないようなので、大成功です

少し機能追加

ここまででYoutube動画のダウンロードと文字起こし、要約を行いました
ということで目的は達成できたわけですが、せっかくなので、実行日にアップロードされた最新動画のリンクを取得した上で、それらの内容を要約し、自分のGmailに送信してくれるように機能追加してみました

動画リンク取得

動画リンクの取得にはPlaywrightを用いました

以下が動画リンク取得用に作成したコードです

from playwright.sync_api import sync_playwright


def get_youtube_video_links(channel_url):
   video_info_list = []

   with sync_playwright() as p:
       # Chromiumブラウザを起動(ヘッドレスモード)
       browser = p.chromium.launch(headless=True)
       page = browser.new_page()

       # YouTubeのチャンネル動画ページを開く
       page.goto(channel_url)

       # 動画一覧が表示されるまで少し待つ
       page.wait_for_selector(".ytd-rich-grid-media a#thumbnail", timeout=5000)

       video_cover_els = page.query_selector_all(".ytd-rich-grid-media")
       for video_el in video_cover_els:
           thumbnail_el = video_el.query_selector("a#thumbnail")
           if thumbnail_el:
               link_txt = thumbnail_el.get_attribute('href')
               if link_txt and '/watch?' in link_txt:
                   uploaded_date_el = video_el.query_selector("#metadata-line .inline-metadata-item:nth-of-type(2)")
                   uploaded_date = uploaded_date_el.text_content().strip() if uploaded_date_el else "不明"
                   if '時間前' in uploaded_date or '分前' in uploaded_date or '秒前' in uploaded_date:
                       title = video_el.query_selector("#video-title").text_content()
                       videos_info = {
                           "title": title,
                           "link": f"https://www.youtube.com{link_txt}",
                           "uploaded_date": uploaded_date
                       }
                       video_info_list.append(videos_info)

       browser.close()

       return video_info_list


if __name__ == "__main__":
   get_youtube_video_links("https://www.youtube.com/@sample/videos")

24時間以内にアップロードされた動画には「〜時間前」「〜分前」「〜秒前」という表記がつくので、それを元にその日アップロードされた動画を判別します

Gmail送信処理

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from logging import getLogger, basicConfig, INFO

basicConfig(level=INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = getLogger(__name__)


def send_email(body, is_html=True):
   sender_email = "your-email@gmail.com" # 送信用メールアドレス
   receiver_email = "your-email@gmail.com" # 受信用メールアドレス
   password = "xxxx xxxx xxxx xxxx"  # アプリケーションパスワードをペースト

   subject = "タイトル"

   # メールのヘッダー部分を作成
   message = MIMEMultipart()
   message["From"] = sender_email
   message["To"] = receiver_email
   message["Subject"] = subject

   # HTMLとプレーンテキストの両方をメールに追加
   if is_html:
       # HTML形式のメール
       message.attach(MIMEText(body, "html"))
   else:
       # プレーンテキスト形式のメール
       message.attach(MIMEText(body, "plain"))

   try:
       # GmailのSMTPサーバーに接続
       server = smtplib.SMTP("smtp.gmail.com", 587)
       server.starttls()  # TLS接続を開始
       server.login(sender_email, password)  # ログイン
       text = message.as_string()  # メールを文字列に変換
       server.sendmail(sender_email, receiver_email, text)  # メールを送信
       logger.info("Email sent successfully")
   except Exception as e:
       logger.error(f"Error: {e}")
   finally:
       server.quit()  # サーバーから切断


if __name__ == "__main__":
   html_content = """<html><body><h1>Hello!</h1><p>This is an <b>HTML</b> email.</p></body></html>"""
   plain_text_content = "Hello!\nThis is a plain text email."

   # HTMLメールとして送信
   send_email(html_content, is_html=True)

   # プレーンテキストメールとして送信
   send_email(plain_text_content, is_html=False)

PythonスクリプトからGmailを送信する場合はアプリケーションパスワードを設定する必要があるので、こちらを参考に設定して上記コードにペーストしてください

GmailはHTML形式で渡すことで、整形されたメールを送信することができるので、動画内容を要約する際のプロンプトを以下のように変更し、HTML形式でメール送信されるように改良しました

次のテキストの内容を日本語で要約し、HTML形式で出力してください。<h1>にはリンクとして「{link}」を埋め込んでください。\n動画タイトル「{title}」\n本文「{txt}」

では、ここまでで作成した各パートをmain.pyで段階的に実行して、全体の流れを見てみましょう

main.py

from audio_downloader import download_audio
from audio_transcript import summary_response, transcript
from crawl_videos import get_youtube_video_links
from gmail_sender import send_email

videos_info = get_youtube_video_links("https://www.youtube.com/@BBCNews/videos")

msg = ''

for i in range(len(videos_info)):
   video_info = videos_info[i]
   # audioファイル(mp3)ダウンロード
   audiofile_path = download_audio(video_info.get("link"))

   # 文字起こし & 要約
   news_summary = summary_response(transcript(audiofile_path), video_info.get("title"), video_info.get("link"))

   msg += news_summary
   msg += '<hr>'

# メール送信
send_email(msg)

今回は24時間以内にアップロードされたBBCニュースの要約をGmailに送信してもらいます

実行結果

Gmail送信結果

無事綺麗に整形された状態で送信されました!
タイトルには元動画のリンクを埋め込んでもらったので、クリックすれば元動画に飛ぶこともできます

まとめ

ということで、Youtube動画を要約して、Gmailに送信する試みでした
今後やるとすれば、せっかく最新動画を取得して要約できるようにしたので、定期実行できるようにするなどですかね
また、MP3ファイルさえあれば要約が可能なので、ZOOM会議の音声ファイルを渡して、要約してもらうなどといった使い方もできそうです
まだまだ可能性広がりそうなので、色々試してみようと思います
では、また次の記事で。

この情報は役に立ちましたか?


フィードバックをいただき、ありがとうございました!

関連記事

カテゴリー:

ブログ

情シス求人

  1. チームメンバーで作字やってみた#1

ページ上部へ戻る