Difyでyoutubeの字幕を簡単に取得した

## 最近、Difyが楽しい！(2024/10/04)
#### Difyの詳細な情報については、別途調べてもらうとして、今回は私が実際にWindows 11上でWSL2、Docker、Difyをインストールした際に活用させてもらったURLをシェアしたいと思います。（参考にさせていただいた皆様、ありがとうございました！）

#### 良くなったこと
YouTubeTranscriptApiを使うことで、APIの利用制限が無くなり、好きなだけ字幕の取得をできるようになりました。

#### DSLファイル
https://github.com/sinzy0925/Dify_Youtube_Youyaku

#### 〇 YouTubeの字幕を取得したい理由
YouTube上にはDifyに関する情報がたくさんありますが、1つ1つの動画を視聴するのは大変ですよね。
せっかくDifyを使うなら、YouTubeの字幕をDifyで取得し、さらにLLMで要約してもらうことで、事前に動画の内容を把握できるはず！と思い、挑戦してみました。

#### 試したこと
数日前までは、以下の手順でYouTubeから字幕を取得していました。
(A) YouTube APIを使って動画を検索し、videoIdを取得
(B) 取得したvideoIdを使って字幕を取得
ただし、(B)の部分では、DifyにビルトインされているSearch APIのYouTube Transcripts APIを使って字幕を取得できるのは100回までという制限がありました。
GCPのYouTube Data API v3を使う方法もあるのですが、こちらも手間がかかるため、どうしようかと悩んでいました。

#### そんな中、今朝、次の記事に出会いました：

【Python】YouTubeTranscriptApiでYouTube動画の字幕情報を抽出する
https://note.com/python_lab/n/n4fe90ffe1099

この記事を参考にして、APIを使うだけでなくライブラリでも字幕を取得できることを知りました！
ただし、SaaS版Difyではライブラリのインストールができないため、ローカル環境にDifyをインストールし、必要な依存関係も含めてセットアップを進めました。

#### インストール手順
〇 WSL2インストール
Windows 11にWSL2をインストールする
https://qiita.com/SAITO_Keita/items/148f794a5b358e5cb87b

〇 Dockerインストール
Windows11(WSL2)上のUbuntuにDocker EngineをインストールしてLinuxコンテナを実行してみる
https://qiita.com/SAITO_Keita/items/069715dde090f6584c33


〇 Difyインストール
https://qiita.com/vb-long/items/97d8ab51ff6c44a85cd4
インストール時にエラーが発生したので、ChatGPTに相談して解決しました。
$docker compose up -d　でエラー発生　ChatGPTの解答(1. Dockerの権限エラーに対処)の手順を実施して正常に起動した。
エラーの詳細は(C)に記載しています。

〇 Dify依存関係のインストール
Difyでコードブロックにおける「高度な依存関係」非表示時の問題解決マニュアル
https://note.com/ai_tarou/n/n4899f0058fe5
youtube-transcript-api他（私が必要な物）をインストールするには、以下のコマンドを実行：
#サンドボックスコンテナにアクセス
`docker exec -it docker-sandbox-1 /bin/bash`
#python-requirements.txt存在確認
`find / -name "python-requirements.txt"`
#内容確認
`cat /dependencies/python-requirements.txt`
#書き込み
`echo "youtube-transcript-api" > /dependencies/python-requirements.txt`
`echo "youtube-search-python" >> /dependencies/python-requirements.txt`
`echo "langdetect" >> /dependencies/python-requirements.txt`

#インストールが正しく行われたかを確認するために、依存関係をチェック
`pip check`
#古いキャッシュが影響している可能性があるため、pipキャッシュをクリア
`pip cache purge`
ホスト環境に戻り、コンテナを再起動して依存関係を反映させる
`exit`
`docker restart docker-sandbox-1`
他の手順は、上記のリンク通りに実行しました。

〇Difyのコードに記述した
Python(youtube-transcript-api)

```python
from youtube_transcript_api import YouTubeTranscriptApi
def main(videoId):
    video_id = videoId
    transcript_list = YouTubeTranscriptApi.list_transcripts(video_id)

    transcript = transcript_list.find_transcript(['ja'])
    transcript_data = transcript.fetch()
    text1 = ''
    for index, item in enumerate(transcript_data, start=1):
        text = item['text'].replace('\n', '')  # 改行を削除
        text1 += text


    return {
        "result": text1
    }
```
Python(youtube-search-python)
```python
from youtubesearchpython import VideosSearch
from langdetect import detect   
import re
import json

def main(search_query,limit0):

    def get_many_videos(search_query, limit=100):
        videos_search = VideosSearch(search_query, limit=limit)
        results = []
        
        while len(results) < limit:
            batch = videos_search.result()["result"]
            results.extend(batch)
            
            if not videos_search.next():
                break
        
        return results[:limit]

    def determine_language(text):
        try:
            lang = detect(text)
            return lang if lang in ['ja', 'en'] else 'unknown'
        except:
            return "unknown"

    # 検索したいキーワードを指定
    #search_query = "dify ai"

    # 検索を実行
    #videos_search = VideosSearch(search_query, limit = 1000)  # より多くの結果を取得
    results = get_many_videos(search_query, limit=100)

    # 結果を取得
    #results = videos_search.result()["result"]

    # publishedTimeに年が含まれていないかチェックする関数
    def is_recent_video(published_time):
        if published_time is None:
            return False
        return "year" not in published_time.lower() and "years" not in published_time.lower()

    # タイトルに日本語が含まれているかチェックする関数
    def contains_japanese(text):
        pattern = r'[ぁ-んァ-ン\s.,!?()[\]{}:;"\'`@#$%^&*_+=|~<>-]'
        return bool(re.search(pattern, text))
    def contains_english(text):
        pattern = r'[a-zA-Z\s.,!?()[\]{}:;"\'`@#$%^&*_+=|~<>-]'
        return bool(re.search(pattern, text))
    from langdetect import detect

    def determine_language(text):
        try:
            lang = detect(text)
            return lang if lang in ['ja', 'en'] else 'unknown'
        except:
            return "unknown"

    # フィルタリング
    filtered_results = [
        video for video in results 
        if 'publishedTime' in video and is_recent_video(video['publishedTime'])
    ]

    # sorted_resultsの各要素に対してlanguageキーを追加
    for video in filtered_results:
        try:
            video['language'] = determine_language(video['title'])
        except Exception as e:
            print(f"言語の判定中にエラーが発生しました: {e}")
            video['language'] = 'Unknown'

    # 言語が'unknown'でないエントリーのみをフィルタリング
    filtered_results = [result for result in filtered_results if result.get('language') != 'unknown']


    # viewCountでソート
    sorted_results = sorted(filtered_results, key=lambda x: int(x['viewCount']['text'].split()[0].replace(',', '')), reverse=True)
    list_results = list(sorted_results)

    array_results = []
    for result in sorted_results:
        array_results.append({'title': result['title'],'videoId': result['id'],'viewCount': result['viewCount'],'publishedTime': result['publishedTime'],'channel': result['channel'],'language': result['language']})

    print(array_results[limit0])

    return {
        "result": json.dumps(array_results[limit0], ensure_ascii=False)
    }

```


〇ちなみに、Search APIのYouTube Transcripts APIで取得した字幕と、YouTubeTranscriptApiで取得した字幕をdiffしたところ、ほぼ一緒でした。

Search APIのYouTube Transcripts APIでは&を`&amp;`と表示して
YouTubeTranscriptApiでは、&を&と表示してます。
違いはそれだけでした。


#### エラー内容と解決方法
(A) YouTube検索APIのコード例
https://www.googleapis.com/youtube/v3/search?part=snippet&maxResults={start+5}&q={arg1}&order=viewCount&key={API_KEY}

(B) ロジックは特に無し（videoIdとlanguageを設定するだけ）

(C)Dockerのエラー内容と解決方法
WARN[0000] The "CERTBOT_EMAIL" variable is not set. Defaulting to a blank string.
WARN[0000] The "CERTBOT_DOMAIN" variable is not set. Defaulting to a blank string.
permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.47/containers/json?all=1&filters=%7B%22label%22%3A%7B%22com.docker.compose.config-hash%22%3Atrue%2C%22com.docker.compose.project%3Ddocker%22%3Atrue%7D%7D": dial unix /var/run/docker.sock: connect: permission denied

ChatGPTに「difyをローカルで起動しようとすると上記のエラーが出たので対応方法を日本語で教えて」と依頼

ChatGPTの解答
///////////////////////////////////////////
1. Dockerの権限エラーに対処
エラーメッセージの中に「permission denied while trying to connect to the Docker daemon socket」とあります。これは、現在のユーザーがDockerデーモンにアクセスする権限がないことが原因です。以下の方法で解決できます。
手順：
Dockerグループにユーザーを追加 現在のユーザーを docker グループに追加することで、このエラーを回避できます。以下のコマンドを実行してください：
sudo usermod -aG docker $USER
その後、システムにログアウトして再度ログインするか、以下のコマンドでグループ変更を適用します：
newgrp docker
Dockerソケットのパーミッションを確認 docker.sock ファイルのパーミッションが不適切である可能性があります。以下のコマンドでパーミッションを確認し、適切に変更しますsudo chmod 666 /var/run/docker.sock
2. 環境変数の設定（CERTBOT_EMAILとCERTBOT_DOMAIN）
メッセージにある「CERTBOT_EMAIL」と「CERTBOT_DOMAIN」がセットされていないことに関する警告は、SSL証明書の自動発行（Let's Encryptなど）に関連するものです。
対処方法：
環境変数をセット SSLを設定する場合は、必要な環境変数を設定してください。docker-compose.yml ファイルや環境設定ファイル（.env）を確認し、以下のように変数を設定します：
bash
コードをコピーする
export CERTBOT_EMAIL="your-email@example.com"
export CERTBOT_DOMAIN="yourdomain.com"


SSLを無効にする 開発環境でSSL証明書が不要な場合は、SSL関連の設定を無効にすることも検討できます。Difyの設定ファイルを確認し、SSLに関する部分を無効にしてみてください。
3. docker-composeの設定確認
エラーメッセージから、docker-compose.yml の設定に問題がある可能性もあります。特に CERTBOT_EMAIL や CERTBOT_DOMAIN の設定が含まれている場合、それをコメントアウトしたり無効にすることで、開発環境ではエラーを回避できる場合があります。
上記の手順を実行することで、Dockerデーモンや環境変数に関する問題が解決するはずです。
///////////////////////////////////////////
ChatGPTの解答 終わり