OBSでLeague Of Legendsの自動クリップツールを作る

LoL自動クリップツール記事アイキャッチ LeagueOfLegends
LoL自動クリップツール記事アイキャッチ

はじめに

 League Of Legendsでいいキルができても、その瞬間を見ていないリスナーとは感動を共有できません。

「そんなのもったいないじゃん!」ということで、キルシーンが自動でリプレイされる自動クリップツールを作りました。これで「見逃した!!」なんてことがなくなるはずです。

作り方

 LoL自動クリップツールは以下3つの手順で作ります。

  1. OBS WebSocketでリプレイバッファを開始
  2. LoL LiveClientDataAPIでキルを検知
  3. OBS WebSocket保存したクリップを再生

 「そもそも何を言っているかわからない・・・」という方は以下の記事を見てください。

最小構成

 設定情報のプロパティファイル化や、クリップファイル名のリネームなどいろいろやりたいことは出てきますが、ここではまず「キルが発生したらクリップを保存」「保存したクリップをOBSで再生」の2つに絞って説明します。

前提

 まず、OBS WebSocketとLiveClientDataAPIを使うためには以下が必要です。

  • OBS Studio:28.0以降 ※OBS WebSocket v5のため
  • Python:3.9以上
  • obsws-python

最低限上記3つが揃っていればLeague Of Legends自動クリップツールが作れます。

サンプルコード

 以下サンプルコードでLeague Of Legends自動クリップツールができます。

 以下3つは各環境に合わせて修正してください。

# OBS接続情報
password = "**********"         #OBS WebSocketのパスワード

# シーン名とソース名
scene_name = "シーン名"          #クリップを表示するシーン名
source_name = "メディアソース名" #クリップを再生するメディアソース名

#player_nameを管理
player_name = "summoner_name#TAG" #プレイヤーのriotId

 以下がサンプルコードの全量です。

# OBS WebSocketのインポート
import obsws_python as obsws

# HTTPリクエスト用のインポート
import requests

# urllib3のインポート    
import urllib3

# LiveClientData APIのURL
API_URL = "https://127.0.0.1:2999/liveclientdata/allgamedata"

#URL検証なし
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


# OBS接続情報
password = "**********"         #OBS WebSocketのパスワード

# シーン名とソース名
scene_name = "シーン名"          #クリップを表示するシーン名
source_name = "メディアソース名" #クリップを再生するメディアソース名

#player_nameを管理
player_name = "summoner_name#TAG" #プレイヤーのriotId

# クリップの状態を管理
clip_flag = False  # クリップ有: True, クリップ無: False

# キル数を管理
current_kills = 0  # 現在のキル数
previous_kills = 0 # 前回のキル数

# リプレイファイル名を管理
current_file_name = ""
previous_file_name = ""


# APIデータ格納用変数
all_game_data = None

# OBS WebSocketクライアントの作成
ws = obsws.ReqClient(host="localhost", port=4455, password=password)

# リプレイバッファの開始
replay_status = ws.base_client.req(
    "StartReplayBuffer"
    )
print("リプレイバッファを開始しました。",flush=True)

# クリップのシーンアイテムIDを取得
clip_item_id = ws.base_client.req(
                    "GetSceneItemId",
                    {
                        "sceneName": scene_name,
                        "sourceName": source_name
                    }
                )

# 無限ループでキル発生を監視
while True:
    try:
        # すべての情報を取得
        all_game_data = requests.get(API_URL, verify=False)
    except requests.exceptions.RequestException:
        # 例外発生時はゲーム開始待ちとみなす
        print("ゲーム開始待ち")
        all_game_data = None

    # データが取得できれば、試合開始または試合中と判断
    if all_game_data:
        # riotidから現在のキル数を取得
        all_players = all_game_data.json()['allPlayers']
        for player in all_players:
            if player['riotId'] == player_name:
                current_kills = player['scores']['kills']
                break

        # キル数の変化をチェック
        if current_kills > previous_kills:
            # キル数が増加した場合
            print(f"キル発生: {previous_kills} -> {current_kills}",flush=True)
            ws.base_client.req("SaveReplayBuffer") # リプレイバッファを保存
            clip_flag = True # クリップフラグを立てる
            previous_kills = current_kills # 前回のキル数を更新

        # クリップフラグが立っている場合、クリップの保存処理を行う
        if clip_flag:
            # リプレイファイル名を取得
            try:
                res = ws.base_client.req(
                    "GetLastReplayBufferReplay"
                    )
                current_file_name = res["responseData"]["savedReplayPath"] 
            except Exception as e:
                print(f"リプレイファイル名の取得に失敗しました: {e}",flush=True)

            # クリップ保存の完了を確認
            if current_file_name != previous_file_name:
                print(f"クリップ保存完了: {current_file_name}",flush=True)
                ws.base_client.req(
                    "SetInputSettings",
                        {
                        "inputName": source_name,
                        "inputSettings":
                            {
                            "local_file": current_file_name
                            },
                        "overlay": True
                        }
                    )

                previous_file_name = current_file_name
                clip_flag = False # クリップフラグをリセット

    else:
        clip_flag = False # クリップフラグをリセット
        kills = 0 # キル数をリセット

# OBSから切断
ws.disconnect()

解説

 サンプルコードを実行すれば自動クリップツールは動きますが、さらなる改良したい方のためにポイントを解説します。

リプレイバッファ開始

 自動クリップツールを実行してもリプレイバッファが開始していないと、うまくクリップが保存・再生できません。そのため、ツール内でリプレイバッファ開始をさせています。

 自分でリプレイバッファを開始させる場合は下記コードは不要となります。

# リプレイバッファの開始
replay_status = ws.base_client.req(
    "StartReplayBuffer"
    )
print("リプレイバッファを開始しました。",flush=True)

LoL LiveClientDataAPIでキルを検知

 キル数は以下で検知しています。

  1. LiveClientDataAPIでプレイヤーのキル数を取得
  2. 今回キル数>前回キル数であればキル発生

 LiveClientDataAPIのallPlayersからplayer_nameでチャンピオンのキル数を取得します。
 今回キル数(current_kills)前回キル(previsous_kills)を比較し、今回キル(current_kills)が大きければ、キル発生とします。

キルが発生したらリプレイバッファを保存し、クリップ再生用のフラグ(clip_flag=True)とします。

       # riotidから現在のキル数を取得
        all_players = all_game_data.json()['allPlayers']
        for player in all_players:
            if player['riotId'] == player_name:
                current_kills = player['scores']['kills']
                break

        # キル数の変化をチェック
        if current_kills > previous_kills:
            # キル数が増加した場合
            print(f"キル発生: {previous_kills} -> {current_kills}",flush=True)
            ws.base_client.req("SaveReplayBuffer") # リプレイバッファを保存
            clip_flag = True # クリップフラグを立てる
            previous_kills = current_kills # 前回のキル数を更新

保存したクリップを再生

 キル発生したらクリップが保存されているので、OBS上でクリップを再生させます。

 クリップを再生するためには以下2つを実施します。

  1. リプレイファイル名を取得
  2. 取得したファイル名をメディアソースに設定

リプレイファイル名を取得

 クリップを再生するためには動画ファイルのフルパスが必要になります。

 OBSWebSocketのGetLastReplayBufferReplayを実行することで、前回保存したリプレイファイル名のフルパスが取得できます。

        # クリップフラグが立っている場合、クリップの保存処理を行う
        if clip_flag:
            # リプレイファイル名を取得
            try:
                res = ws.base_client.req(
                    "GetLastReplayBufferReplay"
                    )
                current_file_name = res["responseData"]["savedReplayPath"] 
            except Exception as e:
                print(f"リプレイファイル名の取得に失敗しました: {e}",flush=True)

取得したファイル名をメディアソースに設定

 クリップファイル名をメディアソースに設定します。

 OBSWebSocketのSetInputSettingsを使うことで、メディアソースにクリップファイルを設定できます。設定すると自動で再生されます。
 メディアソースがOBS上非表示になっていると再生されないので、表示状態にしておく必要があります。

 OBSWebSocketの仕様だと思いますが、クリップファイル名は即時に反映されないようです。
 そのため、今回ファイル名(current_file_name)と前回ファイル名(previsous_file_name)を比較して、異なるクリップファイルが流れてきたら再生するようにしています。
 ※上記を入れておかないと、1つ前のクリップが再生されることがあります。

            # クリップ保存の完了を確認
            if current_file_name != previous_file_name:
                print(f"クリップ保存完了: {current_file_name}",flush=True)
                ws.base_client.req(
                    "SetInputSettings",
                        {
                        "inputName": source_name,
                        "inputSettings":
                            {
                            "local_file": current_file_name
                            },
                        "overlay": True
                        }
                    )
                previous_file_name = current_file_name
                clip_flag = False # クリップフラグをリセット

改善点

 解説し易いようにシンプルな構成にしたため、いくつか改善点があります。

プレイヤーネームの取得

 今回、プレイヤーネームは定数として設定していますが、LiveClientDataAPIから取得することができます。

 プレイヤーネームはLiveClientDataAPIのactivePlayer→riotIdで管理されています。
 サンプルコードは以下となります。

# プレイヤーネーム取得
while True:
    try:
        # すべての情報を取得
        all_game_data = requests.get(API_URL, verify=False)
    except requests.exceptions.RequestException:
        # 例外発生時はゲーム開始待ちとみなす
        print("ゲーム開始待ち")
        all_game_data = None

    if all_game_data:
        player_name = all_game_data.json()['activePlayer']['riotId']
        print(f"プレイヤーネーム:{player_name}",flush=True)
        break    

メディアソースの有効化

 自動クリップツールはメディアソースが非表示だと再生されません。
 特に配信している時は配信画面を見ていないのでクリップが再生されているか気付き難いです。

 そのため、ツール内でメディアソースを有効化しておくことで上記を対策できます。
 OBSでの有効化(=表示)はSetSceneItemEnableでできます。

                ws.base_client.req(
                    "SetSceneItemEnabled",
                    {
                        "sceneName": scene_name,    # シーン名を指定
                        "sceneItemId": clip_item_id,  # ソース名を指定
                        "sceneItemEnabled": True  # ソースを表示
                    }
                )

コメント