【ちょいプロ】Youtubeスキップボタン押下を自動化する

背景

日頃Youtubeにはお世話になっています。作業をやるときなどはPCで音楽やゲーム実況を垂れ流すことが多いです。

そんなYoutubeにつきものといえば広告。

プレミアプラン入れば広告とは無縁ですが、ケチな自分は広告と切っては切れない関係です。広告が流れた後、数秒後に現れるスキップボタンをクリックして飛ばすのがせめてもの抵抗です。

最近、筋トレやストレッチをするときにもYoutubeを流すとこの広告スキップ問題にぶち当たります。周りにマウスは無いし、クリックするのにトレーニングを止めて一々パソコンの近くに戻らないといけない。そんなの面倒くさい。

ということでPythonを使ってスキップボタンが現れたら押してくれる自動機能作ってみました。広告ブロックではないので悪しからず。

プレミアムプランに入れ笑

プログラム

基本Geminiを使ってたたき台を作りましたが、途中でこれ以上は上手くいかないと判断して少し加筆しました。すべて自動化したかった...

使用環境・ライブラリ

  • python==3.10.14
  • numpy==2.2.5
  • opencv-python==4.13.0
  • pillow==12.1.1
  • mouse==0.7.1
  • pyautogui==0.9.54

※Youtubeはシアターモードでの動作を想定しています。スキップボタンが透過して後ろの色が映ると検出できないためです。VLMとかでの実装も考えましたが、毎フレーム判定させるのは重そうなので止めました。

処理の流れ

ざっくりフロー図が以下です。いたってシンプル!ただ、こうして俯瞰してみると大分無駄が多くて、まだまだ改善点ありだなと感じる今日この頃。LLMへの指示が難しいなぁ。

Youtube画面を取得し、スキップボタンを押下するプログラムの全体フロー図
フロー図

プライベートで使うものなので、基本的に無限ループで監視を続けます。Ctrl+Cでやめる感じですね。

事前にスキップボタンの画像をスナップショットしていく必要があります。

  1. ウィンドウの選択:Youtubeを流すウィンドウを選択します
  2. ディスプレイのスクショ:PCが繋いでいるディスプレイ全画面スクショします
  3. スキップボタンのテンプレートマッチング:スクショした画像から、スキップボタン画像を探してあるか、あったら位置を取得します
  4. ウィンドウが最小化されている場合、最前面に戻します
  5. スキップボタンをクリック:テンプレートマッチングで見つけた位置をクリックさせます

ここで思ったのがウィンドウが最小化だけの条件ってあまり汎用的ではないなということ。ウィンドウが被った時点で使えないが、プライベートでの使用なのでまあいいか。

PyAutoGUIの落とし穴

最初、PyAutoGUI内の関数で勝手にテンプレートマッチングしてくれるメソッドlocateOnScreen()というものがあるためそれを使っていました。

が、どうやらPyAutoGUIはマルチディスプレイに対応していないみたいです。参考→pyautogui GitHubpyscreeze GitHub

ソースを見てみると、PyAutoGUIの.locateというメソッドはそのままpyscreezeのlocateOnScreen()メソッドを呼んでいますね。そして、pyscreezeの方を見てみるとlocateOnScreen()を呼び出していて、さらにそこではメソッドscreenshot()を呼び出しているみたいです。

screenshot()関数はpyscreezeのREADMEに載っているようにPillow、PILの画像オブジェクトを戻り値として、regionで範囲を一応指定できます。しかし、locateOnScreenで呼ばれているものはregion=Noneで固定値です。複数ディスプレイ繋いだうえで確認したところ以下の感じでした。

import pyscreeze

im = pyscreeze.screenshot(region=None)
im.save('./output.jpg')

screezeを使用して取得したスクショ画像
pyscreeze スクショ画像

メインディスプレイだけがスクリーンショットされるみたいです。ということで、この部分をマルチディスプレイ対応するためにGeminiから出されたものでは満足できなかったためちょちょっとプログラムを修正しました。

ソースコード

作成した関数群は以下の3つです。あとはメイン関数で全体の流れと、検出画像位置とマウス位置のオフセットを計算させクリックさせます。

1. PIL→OpenCV変換の関数

def pil2cv(image):
    ''' PIL型 -> OpenCV型 '''
    new_image = np.array(image, dtype=np.uint8)
    if new_image.ndim == 2:  # モノクロ
        pass
    elif new_image.shape[2] == 3:  # カラー
        new_image = cv2.cvtColor(new_image, cv2.COLOR_RGB2BGR)
    elif new_image.shape[2] == 4:  # 透過
        new_image = cv2.cvtColor(new_image, cv2.COLOR_RGBA2BGRA)
    return new_image
    

単純に変換するだけの関数です。

2. クリックされたウィンドウ情報の取得関数

def get_target_window_by_click():
    """クリックされるまで待ち、クリックされた瞬間のウィンドウ情報を返す"""
    print("【待機中】監視したいウィンドウを一度クリックしてください...")
    
    # マウスの左クリックが押されるまでループ
    while True:
        if mouse.is_pressed(button='left'):
            # クリックされた瞬間のウィンドウを取得
            time.sleep(0.1) # フォーカスが移るのをわずかに待つ
            hwnd = win32gui.GetForegroundWindow()
            title = win32gui.GetWindowText(hwnd)
            
            # デスクトップや空のタイトルを除外
            if title and title != "Program Manager":
                return hwnd, title
        
        time.sleep(0.01)

3. テンプレートマッチングで合致した画像位置の取得関数

def get_template_location(screenshot, temp_img_str, threshold):
    try:
        new_img = pil2cv(screenshot)
        img = cv2.cvtColor(new_img, cv2.COLOR_BGR2GRAY)
        template = cv2.imread(temp_img_str, 0)
        tmpl_w, tmpl_h = template.shape[::-1]

        result = cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED)
        loc = np.where(result >= threshold)

        result_list = list()
        for pt in zip(*loc[::-1]):
            print([int(pt[0]), int(pt[1])], [int(pt[0])+tmpl_w, int(pt[1])+tmpl_h])
            result_list = [int(pt[0]), int(pt[1]), tmpl_w, tmpl_h]
        
        return result_list
    except:
        return None

OpenCVにはテンプレートマッチングの関数があるのでそれを流用しました。

メイン関数

def main():
    # 1. クリック待ち
    target_hwnd, target_title = get_target_window_by_click()
    if not target_hwnd:
        print("選択がキャンセルされました。")
        exit()

    print(f"--- ターゲット確定 ---")
    print(f"対象: {target_title}")
    print(f"監視を開始します...(QまたはEscで終了)")
    print("-----------------------")

    # ボタンが離されるのを待つ(これがないとクリックが連打扱いになるため)
    while mouse.is_pressed(button='left'):
        time.sleep(0.1)

    # 2. 監視ループフェーズ
    
    try:
        pyautogui.useImageNotFoundException(False)
        while True:
            x_offset = win32api.GetSystemMetrics(win32con.SM_XVIRTUALSCREEN)
            y_offset = win32api.GetSystemMetrics(win32con.SM_YVIRTUALSCREEN)    
            # 画像を探す
            full_screen_image = ImageGrab.grab(all_screens=True)

            location = get_template_location(full_screen_image, TARGET_IMAGE, THRESHOLD)
            print(location)
            print("待ち")
            if location is not None:
                # 最小化されていたら元に戻す
                if win32gui.IsIconic(target_hwnd):
                    win32gui.ShowWindow(target_hwnd, win32con.SW_RESTORE)
                
                try:
                    act_x = location[0] + x_offset
                    act_y = location[1] + y_offset

                    time.sleep(0.2)
                    
                    # クリック実行
                    pyautogui.click(act_x + location[2]/2, act_y + location[3]/2)
                    print(f"[{time.strftime('%H:%M:%S')}] ターゲットをクリックしました。")
                    
                    time.sleep(INTERVAL_CLICK) # 連続クリック防止

                except pyautogui.ImageNotFoundException:
                    print(location)
                    pass
                except Exception as e:
                    print(f"Error:{e}")
                    pass

            time.sleep(CHECK_INTERVAL)

    except Exception as e:
        print(f"エラー: {e}")
        

大文字のTAGET_IMAGEなどは定数で、メイン関数のスコープ外に書いています。別途スキップボタンの画像ファイルを指定してあげる必要あるので注意です。ちなみに以下がパラメータです。

  • TARGET_IMAGE : 画像ファイルのパス
  • CHECK_INTERVAL : 監視頻度(秒)
  • THRESHOLD : テンプレートマッチングでの認識したとするしきい値
  • INTERVAL_CLICK : 連続クリック防止のためのインターバル

動作確認

実際に動かしている映像を載せることができたらよかったのですが、著作権に引っ掛かりそうなので割愛。

実行して、Youtubeを垂れ流し...。

スキップボタンが現れると何も触らずとも一瞬でスキップができました!

スキップ時の画面は載せられないですが、クリック時のログだけ載せておきます。Errorが出ているところはスクショ画像上でテンプレートマッチングに失敗しているからですね。上手く動いていそう。

Youtube画面にスキップボタンが現れたときの実際の自動化プログラムのログ

まとめ

ということで、今回は日頃作業時に面倒だったYoutubeスキップをクリックする動作を自動化してみました。

使用している技術・知識としてはPythonと少し画像処理ですかね。実際に動作確認もして、上手く動作していることを確認できました。

ただ、もっと改良の余地があるなと思ったところでもあります。例えば、スキップボタンを押した後カーソルの位置がずれてしまうため一々戻す必要があります。また、ウィンドウが重なっている場合などはスキップボタンを押せないです。そのためウィンドウの取得があまり活かせていない状況だなとしみじみ。

一旦、筋トレやストレッチ時にマウスの近くまで戻る手間が減ったので満足です。

Pythonや画像処理の基礎的な記事もこれから書いていきたいですし、このような身近な不便を解消するちょっとしたプログラムも継続して書いて共有できればと思います。ではでは、ありがとうございます!

参考文献

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です