obsws-pythonのテキスト編集で沼った話

## obsws-pythonとは
obsws-pythonとは、OBS StudioのWeb Socketを使って
プログラムから色々なOBS上の操作ができるライブラリです。

https://pypi.org/project/obsws-python/1.1.0/

…と言っても[当該ライブラリのGitHub](https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md)を見てもいまいちわからなかった上に日本語の情報がかなり少なかったので非常に沼りました。
本記事では数ある機能の中でも主に**テキストソースの編集**にフォーカスして、私が確認できた内容を残そうと思います。

`obsws-python`は以下でインストール可能です。
```bash
pip install obsws-python
```

:::note info
結果だけ知りたい方はGitHubに概要とコードサンプルを載せているので[こちら](https://github.com/t4ned4/obsws-python-settings)を参照ください。
:::


## 経緯
今回私は別件の開発を行う過程でobsws-pythonを用いて
ただ**ソースの表示/非表示**をしたかったのですが、
「どうせならテキストの色とかも変えられたらいいよな…」
と思ったのが発端でした。

そこでテキストソースなどの設定編集に関わる
`set_input_settings`を見てみたところ…

```Python
def set_input_settings(self, name, settings, overlay):
        """
        Sets the settings of an input.

        :param name: Name of the input to set the settings of
        :type name:  str
        :param settings: Object of settings to apply
        :type settings:  dict
        :param overlay: True == apply the settings on top of existing ones, False == reset the input to its defaults, then apply settings.
        :type overlay:  bool
        """
```
設定の変更に関わるのは上記の`settings`なのですが、
*Object of settings to apply*...?

***いやその中身が知りたいんだよ！***

ところが、どこを探してもその「適用する設定の対象」とやらの中身が
いまいちわかりませんでした。

## 解決…？

前置きが長くなりましたが、上記について私なりに色々調べてみた結果が以下のとおりです。

まずは`obsws-python`を以下のようにセットアップします。
```Python
import obsws-python as ws


password = 'OBS_WS_PASSWORD'
host = 'OBS_WS_HOST'
port = 'OBS_WS_PORT'

ws = ws.ReqClient(host=host, port=port, password=password)
```

これでOBS Studioとの基本的な連携は完了です。
各項目に入力する内容はYGPuzzleGTANT氏の[こちらの記事](https://note.com/213414)がわかりやすいと思います。
続いて下記がテキストソースの設定変更についてです。

```Python
settings = {
            "font": {"size": 128, "face": "Arial"},
            "text": "test",
            "vertical": False,
            "color": 0,
            "opacity": 100,
            "gradient": False,
            "gradient_color": 0,
            "bk_color": 0,
            "bk_opacity": 0,
            "align": "right",
            "valign": "bottom",
            "outline": True,
            "outline_size": 5,
            "outline_color": 0,
            "outline_opacity": 0,
            }

        ws.set_input_settings(name=source_name,
                           settings=settings,
                           overlay=True)
```

各項目については[こちら](https://github.com/t4ned4/obsws-python-settings/tree/main)のREADME.mdで書いているので割愛します。

上記でテキストの編集に限っては大抵のことができると思いますが、
他にもソースの位置やクロップなどを設定できる項目もあります。
確認できた内容を全て載せると冗長になるので、
他の項目を更に調べたい方はOBS Studioで「ファイル」>「設定フォルダを表示」>「basic」>「scenes」より`scene_name.json`から確認できます。
(このファイルにたどり着くまでが一番苦労しました)

しかしそれでもすべてを発見には至っていません。
日本語の情報が少ないライブラリだと思うので有識者の方に追加の知識求む…という感じです。

ちなみに発見した中でも曲者だったのが`color`関連の項目です。
*#1A2B3C*みたいな感じでカラーコードを直接書き込むと予想していたのですが、
実際にはRGBをBGRに直し、10進数に変換した`int`で入力する必要がありました。
(例の場合だと、*1A2B3C(16)*⇒*3C2B1A(16)*⇒*3943194(10)*)

上記の変換についても地味に面倒だったので、以下のコードを作りました。
カラーコードを入力すればOBS Studioに適した10進数を返してくれます。
```Python
def convert_to_decimal(hex_num: str) -> int:
    hex_num = hex_num
    decimal_num = int(hex_num, 16)

    return decimal_num


def reverse_by_pairs(hex_num):
    if hex_num[0] == "#":
        hex_num = hex_num[1:]

    pairs = [hex_num[i:i+2] for i in range(0, len(hex_num), 2)]
    reversed_pairs = pairs[::-1]

    return "".join(reversed_pairs)


if __name__ == "__main__":
    color_code = reverse_by_pairs(input("Please input the color code.\n"))
    result = convert_to_decimal(color_code)
    print(f"color code: {result}")

```
これにて、`obsws-python`を用いてOBS Studioのテキストソースの編集なら大抵のことはできるはずです。

## 総括

今回、`set_input_settings`の`settings`の中身がわからず非常に苦戦しました。
私が見た限り日本語で情報があったものといえばテキスト内容の編集くらいだったので、私と同じように苦戦している方の助けになればという想いでまとめました。

変更可能な項目が多くまとめきれないので*Object of settings to apply*と記載していたのかなと思いますが、それにしてもどこで設定項目の名称確認ができるかくらいはどこかに書いていてほしいですよね。
(書いていたのかもしれませんが)

今回のことは、いつか自分がライブラリを作ることがあったらユーザー視点も考えた設計にしようと思えたいい機会でした。
ライブラリのソースコードやGitHubのIssuesやDiscussionsなど、
情報の深い部分に潜って試行錯誤したり解決策を導く過程はいい勉強になったと思います。

ちなみにカラーコードの煩雑さは何かというところの憶測ですが、映像系(とまとめていいのかわかりませんが)ではBGRを採用することが多いようで、GUI的にはRGBのほうがわかりやすいからそちらを採用しつつ内部的にBGRに戻して10進数でデータを保管しているのかな、と思いました。

## おまけ
最後におまけなんですけれども、皆さんお気づきでしょうか。
実はテキストの編集方法については上記で記載しましたが、
結局私の当初の目的だったソースの表示/非表示の方法については書いていません。
おまけでは上記についての簡単な説明を行います。

`obsws-python`を使ってソースの表示/非表示を切り替えるには
`set_item_enabled`を使って以下のように記述します。

```Python
ws.set_scene_item_enabled(scene_name=scene_name,
                       item_id=item_id,
                       enabled=True)
```
上記の`enabled=`を`True`にすれば表示、`False`にすれば非表示になるのですが…
はい、わかります。
皆さんの気持ち、わかりますよ。

**`scene_name`はともかく、`item_id`ってなんぞ**

という感じですよね。

`scene_name`は文字通りシーンの名前を入れるだけでいいのですが、`item_id`が若干曲者です。
これはなにかというと、OBSのソース上に並んでいる要素の上から順に0, 1, 2...みたいな単純なお話ではありません。
おそらくシーン内で作成されたソース順に**1から**振り分けられているようです。
ちなみにこれも`scene_name.json`で一応確認できます。
しかし、

「ソースを作成した順番なんて覚えていない！」
「`item_id`を`scene_name.json`から見つけるのは面倒！」

…という方の気持ちも大いに理解しています。
というか私がそうでした。

安心してください。
以下のコードで`item_id`の一覧が取得できます。
```Python
output_source_id(scene_name):
        print("ID: sourceName")
        dicts = ws.get_scene_item_list(name=scene_name).scene_items
        
        for dict in dicts:
            print(f"{dict["sceneItemId"]}: {dict["sourceName"]}")
```
`get_scene_item_list`という機能を使って指定したシーン内のソースIDを確認することができます。
上記の出力例は以下のようになります。
```bash
ID: sourceName
1: text1
2: text2
3: image
4: bgm
```
はい、これで`item_id`がわかりましたので
自由自在にソースの表示/非表示ができるはずです。
これにてOBS上で表示トランジションや非表示トランジションを設定することで
見栄えの良い(?)ソース操作が行えるようになると思います。

配信の開始やその他の情報の参照など、`obsws-python`でできることはたくさんあるようですし、
今のトランジション設定についてもプログラムから設定できると思いますが、
それはあくまでまた別のお話ということで、皆さんもぜひ活用してみてください。

今回公開しているコードの詳しい使い方や補足はGitHubに公開しています。
当記事での内容でわからないことがあればそちらもあわせてご参照ください。

https://github.com/t4ned4/obsws-python-settings