discord.pyでYouTubeの動画をボイスチャットで再生する(Replit)

# はじめに

この記事では、discord.pyの`FFmpegPCMAudio`を利用してYouTube動画をボイスチャットで再生するために必要な手順について解説します。

([前回の記事](https://qiita.com/vex12853/items/9fc0331d062fcdcd6e45)の応用内容です。)

## この記事の対象者

- discord.pyでYouTubeの動画をボイスチャットで再生したい
- [前回の記事](https://qiita.com/vex12853/items/9fc0331d062fcdcd6e45)の応用をしたい

## 前提知識

- [Replitでdiscord.pyのFFmpegPCMAudioを使う](https://qiita.com/vex12853/items/9fc0331d062fcdcd6e45) の内容(discord.pyで音声を再生する方法)

# 手順

[前回の記事](https://qiita.com/vex12853/items/9fc0331d062fcdcd6e45)と同じ作業が必要なのでここでは省きます

## 1. 必要なライブラリをインストールする

YouTubeの動画を参照するため、動画のソースを取得する手段が必要です。ここでは[pytube](https://pypi.org/project/pytube/)を扱いますが、必要に応じて[youtube_dl](https://pypi.org/project/youtube_dl/)等のライブラリで代用することができます。

### 1. pytubeをpipコマンドでインストールする

以下のコマンドをシェルから実行してください:
```bash
pip install -U pytube
```

### 2. インストールを確認する
```bash
pip show pytube
```
```
Name: pytube
Version: 15.0.0
Summary: Python 3 library for downloading YouTube Videos.
Home-page: https://github.com/pytube/pytube
Author: Ronnie Ghose, Taylor Fox Dahlin, Nick Ficano
Author-email: hey@pytube.io
License: The Unlicense (Unlicense)
Location: /xxx/.pythonlibs/lib/python3.10/site-packages
Requires: 
Required-by:
```
この記事の執筆時点では`15.0.0`が最新バージョンですが、更新される可能性があります。


## 2. 実際のコードに組み込む

### 1. pytubeライブラリの使い方

```python
from pytube import YouTube

# URLの形が不正であるとインスタンスを作成した時点で pytube.exceptions.RegexMatchError が発生します
yt = YouTube("youtu.be/eXaMpLe")

# pytube.YouTubeのインスタンスはstreams属性からpytube.query.StreamQueryのリストを返しますが、
# 動画が利用できない場合(idに合う動画が見つからないなど)は pytube.exceptions.VideoUnavailable が発生します
streams: list = yt.streams

# ストリームを音声のみでフィルターし、音質の良い順に並び替え、最初の項目を取得します
# これにより最高音質のオーディオストリームを取得できます
audio = streams.filter(only_audio=True).order_by('abr').last()

# pytube.query.StreamQueryのurl属性からソースのリンクを取得します
# 音声をダウンロードしてio.BytesIOで再生する方法もありますが、帯域を節約するためにリンクを使います
url = audio.url
```

### 2. discord.pyを使って再生する
```python
# discord.FFmpegPCMAudioを使って再生するための音声ソースを作成します
source = discord.FFmpegPCMAudio(url)

# discord.PCMVolumeTransformerを使って音声を調節します(任意)
# volumeの値は音量の倍率を指定し、1の場合はそのままの音量で、0の場合は無音になります
source = discord.PCMVolumeTransformer(source, volume=0.5)

# discord.VoiceClientのplayメソッドを利用して音声ソースを再生します
vc.play(source)
```

## 3. 最終的なコードの例

### 1. pytubeを利用した書き方

```bash:shell
pip install -U discord.py[voice] pytube
```

```python:main.py
import os

import discord
from discord.ext import commands
from pytube import YouTube

intents = discord.Intents.all()
bot = commands.Bot(command_prefix='!', intents=intents)
tree = bot.tree
discord.opus.load_opus('libopus/lib/libopus.so')


@bot.command()
async def play(ctx, url, volume=1.0):
    try:
        volume = float(volume)
    
    except ValueError:
        ctx.send("音量は数字で指定してください")
        return
    
    if ctx.guild.voice_client is None:
        vc = await ctx.author.voice.channel.connect(self_deaf=True)
    
    else:
        vc = ctx.guild.voice_client
    
    try:
        yt = YouTube(url)
        streams = yt.streams
        audio = streams.filter(only_audio=True).order_by('abr').last()
        source = discord.FFmpegPCMAudio(audio.url)
        source = discord.PCMVolumeTransformer(source, volume=volume)
        vc.play(source)
        await ctx.send(f'再生開始: {yt.title}, 音量: {volume*100}%')
        
        while vc.is_playing():
            await asyncio.sleep(1)
        
        await vc.disconnect()
      
    except Exception as e:
        await ctx.send("再生中にエラーが発生しました")
        raise e


bot.run(os.environ['TOKEN'])
```

### 2. youtube_dlを利用した書き方
```bash:shell
pip install -U discord.py[voice] youtube_dl
```
```python:main.py
import os

import discord
from discord.ext import commands
from youtube_dl import YoutubeDL

intents = discord.Intents.all()
bot = commands.Bot(command_prefix='!', intents=intents)
tree = bot.tree
discord.opus.load_opus('libopus/lib/libopus.so')

@bot.command()
async def play(ctx, url, volume=1.0):
    try:
        volume = float(volume)
    except ValueError:
        await ctx.send("音量は数字で指定してください")
        return

    if ctx.guild.voice_client is None:
        vc = await ctx.author.voice.channel.connect()
    else:
        vc = ctx.guild.voice_client

    ydl_opts = {
        'format': 'bestaudio/best',
        'postprocessors': [{
            'key': 'FFmpegPCMAudio',
            'preferredcodec': 'mp3',
            'preferredquality': '192',
        }],
    }

    with YoutubeDL(ydl_opts) as ydl:
        info_dict = ydl.extract_info(url, download=False)
        url2 = info_dict['formats'][0]['url']

        source = discord.FFmpegPCMAudio(url2)
        source = discord.PCMVolumeTransformer(source, volume=volume)
        vc.play(source)
        await ctx.send(f'再生開始: {info_dict["title"]}, 音量: {volume*100}%')

        while vc.is_playing():
            await asyncio.sleep(1)

        await vc.disconnect()

bot.run(os.environ['TOKEN'])
```

# 終わりに

最後まで読んで頂きありがとうございます。不備があればコメントで指摘して下さい。