経緯
趣味でモザイクアートをPythonで作成するプログラムを書いていて、素材が大量に必要になった。
元画像はとあるVtuberのものを使用する気でいたので、どうせならその人のサムネとキャプチャで
作成すると思った。
でも手で毎回キャプチャするのは論外として、Bandicamのようなツールを使おうとしても毎回動画を
切り替えるのもめんどくさい。
なら自分で作るかという経緯。
処理の流れ
- 対象youtube の動画一覧ページのURLをinputとして渡す
例)https://www.youtube.com/channel/xxxxxxxxxxxxx/videos - 下にひたすらスクロール。上記URLでは下にスクロールしないと古い動画が表示されない。
そのため新しい動画が表示されなくなるまでスクロールを続ける
1.全動画のサムネと動画へのURLを取得する
この時のサムネの画像サイズは336×188だが素材画像はどうせリサイズするのでこのまま保存 - 動画のURLの数だけ以下をループ
- 動画ページへ遷移
- (初回のみ)音量オフ
- 広告スキップ
- フルスクリーンにする
- pyautoguiを使用しシークバーを移動させる
- キャプチャを撮る
実装
以下実際の実装
# coding:utf-8
import configparser
import datetime
from time import sleep # ブラウザ待機用
import pyautogui
import requests
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import common
###################設定###################
# 設定ファイルから情報を取得
config = configparser.RawConfigParser()
config.read('appSetting.ini',encoding="utf-8")
# 1動画当たりから何枚のキャプチャを撮るか
thumbnail_cap_count = 50
# フルスクにした時のシークの開始位置
x_start,y_start = (-1895,1024)
# フルスクにした時のシークの最終位置
x_end,y_end = (-28,1020)
##########################################
'''
# youtubeのサムネと動画URLを取得する
'''
def __get_thumbnail(browser,is_get_thumbnail):
videos = []
# 新しい動画が出てこなくなるまでスクロールしまくる
for i in range(30):
browser.find_element_by_tag_name('body').send_keys(Keys.END) # Use send_keys(Keys.HOME) to scroll up to the top of page # サムネ取得
common.__wait_browser(browser)
video_doms = browser.find_element_by_xpath("//ytd-grid-renderer[@class=\"style-scope ytd-item-section-renderer\"]")
video_dom = video_doms.find_elements_by_id("dismissable")
for video in video_dom:
# inputが0ならサムネを取得する
if is_get_thumbnail == "0":
thumbnail = video.find_element_by_tag_name("img").get_attribute("src")
# サムネイル保存
request = requests.get(thumbnail)
date = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
with open(config.get("path","thumbnail_paht") + '\\' + date + ".jpg" ,'wb') as f:
f.write(request.content)
sleep(1)
# 動画のURLを取得
video_url = video.find_element_by_id("video-title").get_attribute("href")
videos.append(video_url)
return videos
'''
# 動画をキャプチャする
'''
def __video_capture(browser,video_urls):
volume_off = True
for video_url in video_urls:
# 動画ページへ遷移
browser.get(video_url)
common.__wait_browser(browser)
# 音量オフ
if volume_off:
browser.find_element_by_xpath("//button[@class=\"ytp-mute-button ytp-button\"]").click()
volume_off = False
# 広告スキップ
__skip_advertising(browser)
# フルスクリーンをクリック
browser.find_element_by_xpath("//button[@class=\"ytp-fullscreen-button ytp-button\"]").click()
# 画面キャプチャ
# 撮る枚数からシークバーの移動幅を計算
move_seek = ((x_end) + (-x_start)) / thumbnail_cap_count
# pyautoguiを使用してシークバーを20回移動しキャプチャする
for i in range(thumbnail_cap_count):
pyautogui.moveTo(x_start + (move_seek * i), y_start)
pyautogui.click()
common.__wait_browser(browser)
__skip_advertising(browser)# 広告が入った場合はスキップ
# キャプチャ
video_item = browser.find_element_by_xpath("//video[@class=\"video-stream html5-main-video\"]").screenshot_as_png
with open(config.get("path","screen_shot") + "/" +datetime.datetime.now().strftime('%Y%m%d%H%M%S') + ".png", "wb") as f:
f.write(video_item)
def __skip_advertising(browser):
# 広告が出たらスキップ
if len(browser.find_elements_by_xpath("//button[@class=\"ytp-ad-skip-button ytp-button\"]")) > 0:
sleep(5) # 広告がSkipできるまで待機
browser.find_element_by_xpath("//button[@class=\"ytp-ad-skip-button ytp-button\"]").click()
common.__wait_browser(browser)
# 連続で広告が出てきた場合は再帰でスキップ
if len(browser.find_elements_by_xpath("//button[@class=\"ytp-ad-skip-button ytp-button\"]")) > 0:
__skip_advertising(browser)
if __name__ == "__main__":
#selenium用の設定もろもろ
webdriver_path = config.get("web_driver","chrome_driver")
browser = webdriver.Chrome(executable_path=webdriver_path)
thumbneil_url = input("サムネと動画を取得するyoutubeのURL\n")
is_get_thumbnail = input("サムネを取得するか 0:取得する 1:取得しない\n")
browser.get(thumbneil_url)
common.__wait_browser(browser)
video_urls = __get_thumbnail(browser,is_get_thumbnail)
# デバッグ用
# del video_urls[0:166]
__video_capture(browser,video_urls)
解説
①動画ページへ遷移後の動作
# 新しい動画が出てこなくなるまでスクロールしまくる
for i in range(30):
browser.find_element_by_tag_name('body').send_keys(Keys.END) # Use send_keys(Keys.HOME) to scroll up to the top of page # サムネ取得
common.__wait_browser(browser)
video_doms = browser.find_element_by_xpath("//ytd-grid-renderer[@class=\"style-scope ytd-item-section-renderer\"]")
video_dom = video_doms.find_elements_by_id("dismissable")
流れの項目で記載しているが、youtubeの動画一覧ページは下にスクロールすることで過去の動画が表示されるようになっている。
つまり、遷移だけでは全動画の要素を所得することはできない。
そこで、'body'に対して.send_keysでキーボードの「END」キーを連続で送っている。
30回というのは適当。動画数が多い場合はもっと増やせば良い。
video_doms = browser.find_element_by_xpath("//ytd-grid-renderer[@class=\"style-scope ytd-item-section-renderer\"]")
が全動画のコンテナ要素
video_dom = video_doms.find_elements_by_id("dismissable")
が各動画の要素
②動画のサムネイルとURLを取得
for video in video_dom:
# inputが0ならサムネを取得する
if is_get_thumbnail == "0":
thumbnail = video.find_element_by_tag_name("img").get_attribute("src")
# サムネイル保存
request = requests.get(thumbnail)
date = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
with open(config.get("path","thumbnail_paht") + '\\' + date + ".jpg" ,'wb') as f:
f.write(request.content)
sleep(1)
# 動画のURLを取得
video_url = video.find_element_by_id("video-title").get_attribute("href")
videos.append(video_url)
return videos
①で取得した各動画の要素分ループする。
thumbnail = video.find_element_by_tag_name("img").get_attribute("src")
# サムネイル保存
request = requests.get(thumbnail)
date = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
with open(config.get("path","thumbnail_paht") + '\\' + date + ".jpg" ,'wb') as f:
f.write(request.content)
sleep(1)
サムネを取得し、時分秒をファイル名として保存する。
1秒スリーブしているのは特に意味は無し。
動画一覧のサムネは336×188のサイズ。
# 動画のURLを取得
video_url = video.find_element_by_id("video-title").get_attribute("href")
videos.append(video_url)
動画のURLを取得しListに詰めている。
③キャプチャを撮る
音量をオフにする
# 音量オフ
if volume_off:
browser.find_element_by_xpath("//button[@class=\"ytp-mute-button ytp-button\"]").click()
volume_off = False
動画ページへ遷移すると動画が再生されるが、音がでかい。というか
キャプチャが欲しいのに音はいらないためオフにする
幸い音量ボタンはButtonのため.click()でオフとなる。
また、
volume_offフラグをFalseにすることで2ループ目以降は音量オフのままキャプチャを取得する
広告スキップ
# 広告が出たらスキップ
if len(browser.find_elements_by_xpath("//button[@class=\"ytp-ad-skip-button ytp-button\"]")) > 0:
sleep(5) # 広告がSkipできるまで待機
browser.find_element_by_xpath("//button[@class=\"ytp-ad-skip-button ytp-button\"]").click()
common.__wait_browser(browser)
# 連続で広告が出てきた場合は再帰でスキップ
if len(browser.find_elements_by_xpath("//button[@class=\"ytp-ad-skip-button ytp-button\"]")) > 0:
__skip_advertising(browser)
youtube再生に付き物なのが広告。あれをスキップする。
広告のスキップボタンにも要素がちゃんとある。
HTML上に「ytp-ad-skip-button ytp-button」があるか判定し、スキップが押下できるまでの5秒間を待機後
上記要素を.click()することで広告のスキップが可能。
まれに2連続、3連続で広告が表示されるが、当メソッドを再帰処理することで対応している。
キャプチャを撮るためにフルスクリーンにする
# フルスクリーンをクリック
browser.find_element_by_xpath("//button[@class=\"ytp-fullscreen-button ytp-button\"]").click()
youtbeのフルスクリーンボタンの要素を.click()してるだけ。
シークバーを操作し、キャプチャを撮る
# 画面キャプチャ
# 撮る枚数からシークバーの移動幅を計算
move_seek = ((x_end) + (-x_start)) / thumbnail_cap_count
# pyautoguiを使用してシークバーを移動しキャプチャする
for i in range(thumbnail_cap_count):
pyautogui.moveTo(x_start + (move_seek * i), y_start)
pyautogui.click()
common.__wait_browser(browser)
__skip_advertising(browser)# 広告が入った場合はスキップ
# キャプチャ
video_item = browser.find_element_by_xpath("//video[@class=\"video-stream html5-main-video\"]").screenshot_as_png
with open(config.get("path","screen_shot") + "/" +datetime.datetime.now().strftime('%Y%m%d%H%M%S') + ".png", "wb") as f:
f.write(video_item)
ここではpyautoguiを使用してマウスポインタを移動し、脳筋的に処理している。
pyautoguiについて勉強しとことは後ほど別記事に記載予定(多分)
# 撮る枚数からシークバーの移動幅を計算
move_seek = ((x_end) + (-x_start)) / thumbnail_cap_count
シークバーの初めの座標と最後の座標をキャプチャ枚数で分割し、区切る座標を計算する
# pyautoguiを使用してシークバーを移動しキャプチャする
for i in range(thumbnail_cap_count):
pyautogui.moveTo(x_start + (move_seek * i), y_start)
pyautogui.click()
common.__wait_browser(browser)
__skip_advertising(browser)# 広告が入った場合はスキップ
キャプチャする数だけループし
pyautogui.moveTo()で上記で計算した座標を足していき、クリックしている。
もし広告が入った場合はスキップする
(実はこれだと50枚のキャプチャ指定で49枚しか取れないけどまあええやろ...)
# キャプチャ
video_item = browser.find_element_by_xpath("//video[@class=\"video-stream html5-main-video\"]").screenshot_as_png
with open(config.get("path","screen_shot") + "/" +datetime.datetime.now().strftime('%Y%m%d%H%M%S') + ".png", "wb") as f:
f.write(video_item)
キャプチャを撮る。
Seleniumでキャプチャを撮るには.screenshot_as_pngが使用できる。
それを日時をファイル名にして保存
まとめ
おそらく使う人はいないであろうツール。
これの最大の問題がpyautoguiのマウス座標を使用してしまっていること。
これを使用しているせいでこのツールを使用中は他の作業ができない。(マウスカーソルが持っていかれるため)
外部ライブラリでストリーミング動画を扱えるようなものを見つけれれば良かったが
私には見つけられなかった。
もしお勧めのものがあればご教授いただければ幸いです。
いずれpywinautoでマウス座標を使わないように使用と思います