linebotとyoutube_dlとpydriveを連携させてみた

#Linebotの活用法を考える
youtubeなどの動画をダウンロードするときなにつかってますか？
スマホからだとダウンロードしにくいし、広告多くて...ということでLineからダウンロードするbotを作ってみました。
くそ適当なコードです。
※著作権等に配慮してください。
※技術目的であり、違法DLなどの助長目的ではありません。

##必要なもの
PyDrive
google-api-python-client
youtube-dl
ffmpeg
httplib
mutagen
もしかしたらまだ必要なのあるかも。。

##youtueから動画を取得する
youtubeから動画を取得するにはyoutube_dlというものを使用します。
>https://github.com/ytdl-org/youtube-dl

こちらを使い適当にコードを書きます。

```python:hoge.py
url = "hogehoge.jp"
outtmpl = '%(title)s.%(ext)s'
down_dir = "download/"
os.makedirs(down_dir, exist_ok=True)
ydl_opts = {'outtmpl': down_dir + outtmpl,
            'ignoreerrors': True}
  with youtube_dl.YoutubeDL(ydl_opts) as ydl:
      ydl.download([url])

```

上記のコードでdownloadというディレクトリにダウンロードされるとおもいます。
エラーが出る場合はディレクトリを予め作成してください。
これがダウンロードする際の基本的な形になります。

##pydriveの準備
pydriveとはPythonでGoogleDriveを操作するものになります。
pydriveを使うためにOAuthクライアントID取得を取得する必要があります。

https://console.developers.google.com/

にアクセスして
新しいプロジェクトを作成します。
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1084345/ea069e38-092b-d211-aeb2-a5588b0def8c.png)
名前は適当で大丈夫だと思います。
組織は組織なしで大丈夫です。

次にOAuth 同意画面が出るので、外部を選択（正直よくわからない）


そしたらアプリの情報を入れる画面になりますので、
アプリ名、ユーザサポートメール、デベロッパーの連絡先情報のメールアドレスを入力して次へ
![2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1084345/aa8f0def-0da1-767a-6535-d38d35ebca4b.png)

スコープの設定になりますが、そのまま次へ
![3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1084345/69f694d0-bd9c-3acb-8a6f-84dc6fa0a5b9.png)

テストユーザの設定もそのまま次へ
![4.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1084345/9cc2eebf-ef07-f4d2-1712-0545efb01eeb.png)

これでOAuth 同意画面はOKなので次に
認証情報のOAuth 2.0 クライアント IDを作成します。
![5.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1084345/cbb69ed6-9b1a-f187-4d46-0c513c23a091.png)

アプリの種類の選択画面になりますが、これはデスクトップアプリを選択します。
名前は適当で大丈夫です。
![6.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1084345/b2914f6e-4542-e276-808c-45f28a989ecf.png)

OAuth クライアントを作成しましたと表示されて、クライアントIDとクライアントシークレットが表示されるのでメモしておきます。

最後にライブラリからGoogle Apps APIのDrive APIを有効化させておいてください。

次に設定ファイルを作成します。

settings.yamlという名前で

```yaml:settings.yaml
client_config_backend: settings
client_config:
  client_id: "ここにGoogleClientID"
  client_secret: "ここにSecretID"

save_credentials: True
save_credentials_backend: file
save_credentials_file: credentials.json

get_refresh_token: True

oauth_scope:
  - https://www.googleapis.com/auth/drive.file
  - https://www.googleapis.com/auth/drive.install

```

このようなファイルを作成してください。
client_id:とclient_secret:は先ほど取得した自分のものに書き換えてください。

最後に自分のGoogleDriveアクセスして、
GoogleDrive上で右クリックして新しいフォルダを作成します。
名前はわかりやすいものにしてください。
次に名前を付けたフォルダを右クリックして、共有可能なリンクを取得を選択。
 [制限付き▼]をクリックしリンクを知っている全員に変えておいてください。
完了したら、そのフォルダをダブルクリックしてフォルダを開いてください。
URLにてhttps://drive.google.com/drive/folders/以降～
の以降～の部分がフォルダIDになっているのでメモしておきます。

これでpydriveの準備は完了です。

##アップロードを試しに実行してみる

実際にダウンロードおよびアップロードをテストしてみます。
まず、適当なディレクトリを作成します。
今回は[youtube]という名前でやります。
そしたら、次にダウンロードしたファイルを保存するディレクトリを作成します。
今回は[download]にしてあります。

[フォルダ構成]
youtube
├─download
├─settings.yaml
└─実行ファイル.py（今から作るもの）

```python:down.py
import youtube_dl
import re
import os
from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive

#ここのurlを好きなものに変える
url = "hogehoge.jp"
outtmpl = '%(title)s.%(ext)s'
down_dir = "download/"
os.makedirs(down_dir, exist_ok=True)
ydl_opts = {'outtmpl': down_dir + outtmpl,
            'ignoreerrors': True}
  with youtube_dl.YoutubeDL(ydl_opts) as ydl:
      ydl.download([url])
folder_id = 'ここに先ほどメモしてフォルダID'
gauth = GoogleAuth()
gauth.CommandLineAuth()
drive = GoogleDrive(gauth)
file_sum = os.listdir(down_dir)
　   for i in range(len(file_sum)):
　　　　f = drive.CreateFile({'title': file_sum[i],
　　　　　　'mimeType': mimetype,
　　　　　　'parents': [{'kind': 'drive#fileLink', 'id':folder_id}]})
　　　　f.SetContentFile(down_dir + file_sum[i])
　　　　f.Upload()
　　　　os.remove(down_dir + file_sum[i])

```

このような感じです。
mimeTypeというのはファイルの拡張子によって変わるので適度変えてください。
代表的なものは
mp4の場合
'mimeType': "video/mpeg"
webmの場合
'mimeType': "video/webm"
aviの場合
'mimeType': "video/x-msvideo"
mkvの場合
'mimeType': "video/x-matroska"
になります。

実行すると初回はgoogleのurlが表示されると思うので、アクセスして、
認証コードを入力すれば大丈夫です。
うまくアップロードできましたでしょうか？
もしコードがおかしかったらすみません。

うまくいけば後はlinebotと連携させるだけです。

##linebotとの連携
```python:down.py
from flask import Flask,request,abort
from linebot import LineBotApi,WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import MessageEvent,TextMessage,TextSendMessage,LocationMessage
from linebot.exceptions import LineBotApiError
from linebot.models import CarouselColumn,CarouselTemplate,FollowEvent,LocationMessage,MessageEvent,TemplateSendMessage,TextMessage,TextSendMessage,UnfollowEvent,URITemplateAction,AudioSendMessage,VideoSendMessage,FlexSendMessage
import math
import os
import re
from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive
from mutagen.mp3 import MP3
import youtube_dl

app=Flask(__name__)
#環境変数の取得
YOUR_CHANNEL_ACCESS_TOKEN = os.environ["YOUR_CHANNEL_ACCESS_TOKEN"]
YOUR_CHANNEL_SECRET = os.environ["YOUR_CHANNEL_SECRET"]
line_bot_api=LineBotApi(YOUR_CHANNEL_ACCESS_TOKEN)
handler=WebhookHandler(YOUR_CHANNEL_SECRET)

#テスト用
@app.route("/")
def hello_world():
   return "hello_world"

@app.route("/callback",methods=["POST"])
def callback():
    signature=request.headers["X-Line-Signature"]

    body=request.get_data(as_text=True)
    app.logger.info("Request body"+body)

    try:
        handler.handle(body,signature)
    except InvalidSignatureError:
        abort(400)
    return "OK"

@handler.add(MessageEvent,message=TextMessage)
def handle_message(event):
    #入力された文字を取得
    text_in = event.message.text
    if "$move" in text_in:
      url = text_in[6:]
      download_start = "動画のダウンロードを開始します。"
      line_bot_api.reply_message(event.reply_token,TextSendMessage(text=download_start))
      outtmpl = '%(title)s.%(ext)s'
      down_dir = "download/"
      os.makedirs(down_dir, exist_ok=True)
      ydl_opts = {'outtmpl': down_dir + outtmpl,
                  'ignoreerrors': True}
        with youtube_dl.YoutubeDL(ydl_opts) as ydl:
             ydl.download([url])
      folder_id = 'ここに先ほどメモしてフォルダID'
      gauth = GoogleAuth()
      gauth.CommandLineAuth()
      drive = GoogleDrive(gauth)
      file_sum = os.listdir(down_dir)
           for i in range(len(file_sum)):
              f = drive.CreateFile({'title': file_sum[i],
                  'mimeType': mimetype,
                  'parents': [{'kind': 'drive#fileLink','id':folder_id}]})
              f.SetContentFile(down_dir + file_sum[i])
              f.Upload()
              os.remove(down_dir + file_sum[i])
      end_text = "アップロードが完了しました。\n\nhttps://drive.google.com/drive/folders/" + folder_id
      profile = line_bot_api.get_profile(event.source.user_id)
      line_bot_api.push_message(profile.user_id,TextSendMessage(text=end_text))
　  else:   #ほかの文字はオウム返しする
      line_bot_api.reply_message(event.reply_token,TextSendMessage(text=event.message.text))

if __name__=="__main__":
    port=int(os.getenv("PORT",5000))
    app.run(host="0.0.0.0",port=port)
```
これでLINE側から[$move https://hogehoge]
と入力してやればうまく動くと思います。

##思ったこと
実はmimetypeを判別するようにしてるんですが、コードが汚い為だれか直してくれると嬉しいです。(´;ω;｀)

```python:down.py
      if ".mp4" in os.path.splitext(down_dir + file_sum[i])[1]:
         mimetype = "video/mpeg"
      elif ".webm" in os.path.splitext(down_dir + file_sum[i])[1]:
         mimetype = "video/webm"
      elif ".avi" in os.path.splitext(down_dir + file_sum[i])[1]:
         mimetype = "video/x-msvideo"
      elif ".mkv" in os.path.splitext(down_dir + file_sum[i])[1]:
         mimetype = "video/x-matroska"
      else:
         mimetype = "video/mpeg"
      print(mimetype)
```
```python:down.py
for i in range(len(file_sum)):
```

の次に入れて一応動いてます。

またyoutube_dlのコマンドもちょっとわかりにくいので載せておきます。

MP3に変換して保存するやつ（要FFmpeg)

```python:down.py
 ydl_opts = {'format': 'bestaudio/best',
                  'outtmpl': down_dir + outtmpl,
                  'postprocessors': [
                     {'key': 'FFmpegExtractAudio',
                      'preferredcodec': 'mp3',
                      'preferredquality': '192'},
                      {'key': 'FFmpegMetadata'},
                     ],
                  'ignoreerrors': True}
```
m4aで保存するやつ

```python:down.py
 ydl_opts = {'format': 'bestaudio[ext=m4a]',
                  'outtmpl': down_dir + outtmpl,
                  'ignoreerrors': True}
```

mp4で保存するやつ

```python:down.py
ydl_opts = {'outtmpl': down_dir + outtmpl,
                  'postprocessors': [
                     {'key': 'FFmpegVideoConvertor',
                      'preferedformat': 'mp4'},
                     ],
                  'ignoreerrors': True}
```

このような感じです。
またm4aでダウンロードした場合など最後に、

```python:down.py
      file_id = f['id']
      length = MP4(down_dir + file_sum[0]).info.length
      dur = math.floor(length * 1000)
      up_end = "アップロードが完了しました。\n\nhttps://drive.google.com/uc?id=" + file_id
      audio_url = "https://drive.google.com/uc?id=" + file_id
      line_bot_api.push_message(profile.user_id,AudioSendMessage(original_content_url=audio_url, duration=dur))
```

などとつけてあげるとボイスメッセージとしてbotから送信されます。（プレイリストなどをやるとおかしくなるかも）

##おわりに
ここまで読んでいただきありがとうございました。
この部分こうやって書いたほうがとかありましたら是非教えてくださいorz

あと、、、youtube_dlの429errorの直し方わかる方いらしたら教えてください。