Terrarium

いわゆる掃き溜めの ありふれた有象無象

今日更新されたYouTube動画を(ほぼ)自動でiPhoneにダウンロードしたい

通勤通学中にYouTube動画をオフラインで見たい

私は家から大学まで電車で通っているのですが,特に帰りの電車ではYouTubeの動画を見て時間を潰すことが日課になっています. しかし毎日YouTubeの動画を見ていてはパケ死してしまいます. そこである時から帰る前にあらかじめ動画をiPhoneにダウンロードしておくようになりました. ただその作業を毎日やるのは面倒なため,なるべく自動化できないか,と試行錯誤した記事です.

iPhoneに動画をダウンロードする

YouTubeから動画をダウンロードするのはどうにかなるのですが,iPhoneに動画をダウンロードする方法もいくつかあり,それぞれ試してみました.

  1. DropboxやGoogleDriveなどのクラウドストレージ経由でオンラインにアップし,iPhoneの対応するアプリからオフライン利用できるように設定する.
  2. AirDrop経由でiPhoneに転送する.
  3. iTunesから動画をライブラリに読み込み,iPhoneをUSB接続して同期する.

それぞれ試してみた結果は次の通りです.

  1. クラウドストレージ経由:PCとiPhoneの両方の操作が必要.同期が非常に遅い.
  2. AirDrop経由:以前は良かったのだがHighSierraにアップグレードしたあたりからAirDropで動画が送れなくなった…
  3. iTunes経由:PCの作業のみではあるが,iPhoneをPCに接続し,ライブラリに追加,同期ボタンを押す手間がある.

実用性を考えると,3番のiTunes経由による同期が一番良さそうに思いました. USB接続する面倒さはありますが,充電ついでということで.

ただこれを毎日やるのは手間がかかる

自分はスプラトゥーンの実況動画をよくみているのですが,毎日更新されるものが多いです. なので動画のダウンロードは意外と手間がかかります. 手順は次の通りです.

  1. YouTubeの登録チャンネルのうち今日更新された動画を全て新しいタブで開く.
  2. 動画ページのURLをコピーする.
  3. ターミナルを開き,youtube-dlを用いて動画をダウンロードする.
  4. 2, 3を全ての新しい動画に対して繰り返す.
  5. iTunesを開き,「ライブラリに追加」からダウンロードした動画を全て選択し追加する.
  6. iPhoneをつなぎ,同期ボタンを押す.

特に1,2,3,4の手間が大きいと感じました. そこでなんとか自動化できないかと試行錯誤した結果を次に述べます.

(なるべく)自動化する

今回は動画のダウンロードにyoutube-dlを使っているので,シェルスクリプトを使えば自動化はできそうです. 最初のポイントは,「今日アップロードされたスプラトゥーンの動画のみ」のURLをどう取得するか.

今日アップロードされた動画のURLを取得する

これはYouTubeAPIを使えばできそうです. 調べてみると,動画の検索を行うサンプルスクリプトが見つかりました. 以下URLのPythonによるサンプルの部分です.

Search: list  |  YouTube Data API (v3)  |  Google Developers

調べると日付やキーワード,チャンネルを指定して検索でき,対応するビデオIDとタイトルを取得できるそうです. ということは,見たいチャンネルIDをあらかじめ持っておけば,キーワードとして「スプラトゥーン」,日付にその日の0時を指定すれば,その時点までに更新された動画のIDが取得できるはず.

次にチャンネルIDを取得したいのですが,よくわからず色々試してみると,どうやらチャンネルトップのURL内の文字列がIDみたいです. https://www.youtube.com/channel/UCXqocGp-RQ_sTw8EpPDgxxxならUCXqocGp-RQ_sTw8EpPDgxxxというわけです. これでチャンネルIDがわかったので,動作確認をしてみました. DeveloperKEYはGoogleのページから取得できます.

承認の認証情報を取得する  |  YouTube Data API (v3)  |  Google Developers

from apiclient.discovery import build
from apiclient.errors import HttpError
from oauth2client.tools import argparser

from datetime import datetime
datetime.now().strftime("%Y/%m/%d %H:%M:%S")


# Set DEVELOPER_KEY to the API key value from the APIs & auth > Registered apps
# tab of
#   https://cloud.google.com/console
# Please ensure that you have enabled the YouTube Data API for your project.
DEVELOPER_KEY = "xxxx"
YOUTUBE_API_SERVICE_NAME = "youtube"
YOUTUBE_API_VERSION = "v3"

def youtube_search(options):
    print("options.q", options.q)
    print("options", options)
    youtube = build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION,
            developerKey=DEVELOPER_KEY)

    # Call the search.list method to retrieve results matching the specified
    # query term.
    search_response = youtube.search().list(
        q=options.q,
        part="id,snippet",
        maxResults=options.max_results,
        channelId=options.channel_id,
        publishedAfter=options.published_after
        ).execute()

    videos = []
    channels = []
    playlists = []

# Add each result to the appropriate list, and then display the lists of
# matching videos, channels, and playlists.
    for search_result in search_response.get("items", []):
        if search_result["id"]["kind"] == "youtube#video":
            videos.append("%s (%s)" % (search_result["snippet"]["title"],
                search_result["id"]["videoId"]))
        elif search_result["id"]["kind"] == "youtube#channel":
            channels.append("%s (%s)" % (search_result["snippet"]["title"],
                search_result["id"]["channelId"]))
        elif search_result["id"]["kind"] == "youtube#playlist":
            playlists.append("%s (%s)" % (search_result["snippet"]["title"],
                search_result["id"]["playlistId"]))

    print("Videos:\n", "\n".join(videos), "\n")
    print("Channels:\n", "\n".join(channels), "\n")
    print("Playlists:\n", "\n".join(playlists), "\n")


if __name__ == "__main__":
    channel_id= "UC8VYesWbdGT6kP4B6vju-rQ"
    date = datetime.now().strftime("%Y-%m-%dT00:00:00Z")
    argparser.add_argument("--q", help="Search term", default="")
    argparser.add_argument("--max-results", help="Max results", default=25)
    argparser.add_argument("--channel-id", help="Channel ID", default=channel_id)
    argparser.add_argument("--published-after", help="Published After", default=date)
    args = argparser.parse_args()
    print("args", args)

    try:
        youtube_search(args)
    except HttpError as e:
        print("An HTTP error %d occurred:\n%s" % (e.resp.status, e.content))

これで最新の動画のビデオIDの取得ができたので,次にyoutube-dlを利用するためにその動画のURLを取得する必要があります. 動画のURLはhttps://www.youtube.com/watch?v=DIQBLtuA3sgのようになっているのですが,よくみると最後の文字列がIDっぽいです. 実際に取得したIDを使ったURLでアクセスできたので,これで間違ってないみたいです.

以上より,YouTubeAPIを用いて最新の動画のURLの取得ができました. 次に動画のダウンロードを行います.

youtube-dlをPythonから利用する

動画のダウンロードのために,Python経由でyoutube-dlを呼び出す必要があります. 普段はターミナルから

youtube-dl -f mp4 "https://www.youtube.com/watch?v=ビデオID"

と打てば実行できますが,APIPython経由で叩いているため,このコマンドもPython経由で実行します. そのためには,Pythonのsubprocessモジュールを使います.

17.5. subprocess — サブプロセス管理 — Python 3.6.4 ドキュメント

import subprocess

...

url = "動画のURL"
result = subprocess.check_output(["youtube-dl", "-f", "mp4", url])
print(result)

check_outputメソッドは,引数に取ったコマンドを実行しその出力を返してくれます. なので,結果であるresultを受け取りprint(result)することで結果も表示できます.

以上で動画のダウンロードも自動化できます. 最後に,1番の壁であるiPhoneにどう移すかを検討します.

iPhoneに動画を転送する

iPhoneに動画を転送するにはiTunesAirDropなどmacOS自身に深く関わっている機能を使う必要があり,自動化できなさそうです. AppleScriptなどを使えばできる…?のですが底が深そうなのでやめておきます. なので,できる限り手間を省くにはどうすればいいかを考えました.

と言ってもiTunesを使ったことがある方なら「iTunesに自動的に追加」なるフォルダの存在をご存知の方は多いと思います. そうです,このフォルダに音楽や動画ファイルを入れておけば,勝手にiTunesのライブラリに追加されます. すなわち,ターミナルで次のコマンドを実行すればいいわけです.

mv *.mp4 ~/Music/iTunes/iTunes\ Media/Automatically\ Add\ to\ iTunes.localized

これをsubprocessでやってみようと思ったのですが,これでは*.mp4のようなワイルドカード表現が使えないことがわかりました.

stackoverflow.com

shell=Trueオプションを使えばできるらしいのですがセキュリティ上よくなさそうなので,Pythonのファイル管理モジュールを使うことにしました. ここで登場するのが,パスを管理するpathlibとshellの機能を使えるshutilです.

pathlibにはファイルを検索できるメソッドがあり,shutilにはファイルを移動させるメソッドがあります. この2つを組み合わせると,次のようなPyhtonコードで動画ファイルの移動ができます.

11.1. pathlib — オブジェクト指向のファイルシステムパス — Python 3.6.4 ドキュメント

11.10. shutil — 高水準のファイル操作 — Python 3.6.4 ドキュメント

path = "動画をダウンロードした絶対パス"
_path = pathlib.Path(path)

# globメソッドでmp4形式のファイルを検索しそのパスのリストを取得
video_paths = list(_path.glob("*.mp4"))

dist_path = pathlib.Path("/Users/ユーザー名/Music/iTunes/iTunes Media/Automatically Add to iTunes.localized")

for video_path in video_paths:
    # 動画のパスをdist_pathへmove
    shutil.move(str(video_path), str(dist_path))

実行してみると動画がきちんと移されることが確認できました.

まとめ

以上のスクリプトを使うと,6つだった手順が3つに削減されます.

  1. スクリプトを実行する.
  2. iTunesを開き,動画がライブラリに追加されていることを確認する.
  3. iPhoneをつなぎ,同期ボタンを押す.

また,今回作成したスクリプトGitHubに公開しています.

github.com

スクリプトで自動化したおかげで,動画を探す手間が省けました. ダウンロードの並列化(効果あるのかは知らない)などが今後の課題ですね. このようなライフハック系のコーディングは楽しいので,今後も色々試してみる予定です.