2020年3月19日木曜日

OpenLPのテキストをOBS Studioへ送る方法

はじめに

このサイトのメインテーマからは外れるのですが、歌詞をプロジェクタで映すのに我々はOpenLPというソフトを使用しています。 この記事では、OpenLPのテキストをOBS Studioへオーバーレイする方法を紹介します。

OpenLPには遠隔操作のプラグインが組み込まれていて、JSONをHTTP経由でやり取りするプロトコルになっています。一方で、OBS Studioにはローカルに置かれたテキストファイルを読み込み、オーバーレイして表示する機能がついています。 この間に入って、OpenLPからテキストを取り出しローカルファイルに保存すれば、OpenLPのテキストをOBS Studioへ送ることが簡単にできます。

OpenLPの設定

遠隔操作プラグインを有効にしましょう。 もしOpenLPとOBS Studioが別のホスト上で動いている場合、OpenLPが動作しているホストのIPアドレスを固定しておくべきです。 さらに、ファイアウォールの4316番ポートを開けておく必要があります。

スクリプト

OpenLPとOBS Studioの間に入るスクリプトは、Pythonで書きます。 たったの160行です。
#! /bin/env python
# -*- coding: utf-8 -*-

import re

url_base = 'http://127.0.0.1:4316'
url_text = url_base + '/api/controller/live/text'
url_poll = url_base + '/api/poll'

fname_out = '/dev/shm/olp.txt'
fname_tmp = fname_out+'~'

n_maxchar = 50

flg_print = False

r_ascii = re.compile('[0-9a-zA-Z: ()]')
def my_count(s):
        ret = 0
        for c in s:
                ret += 1 if r_ascii.match(c) else 2
        return ret

def get_olp_text():
        import urllib
        import json
        data = urllib.urlopen(url_text).read()
        #print data
        j = json.loads(data)
        if flg_print: print j
        for x in j['results']['slides']:
                if 'img' in x:
                        return ''
                if x['selected']:
                        return x['text']
        return ''# return j['results']['slides'][0]['text']

def olp_poll():
        import urllib
        import json
        j = json.loads(urllib.urlopen(url_poll).read())
        if flg_print: print j
        is_blank = j['results']['blank']
        is_theme = j['results']['theme']
        is_display = j['results']['display']
        if is_blank or is_theme or is_display:
                return False
        return j

j_poll_prev = None
def get_olp():
        global j_poll_prev
        while True:
                j = olp_poll()
                if j != j_poll_prev:
                        j_poll_prev = j
                        break
                from time import sleep
                sleep(0.1)
        if j:   
                return get_olp_text()
        else:   
                return ''

def obs_output(data):
        import codecs
        with codecs.getwriter('utf8')(open(fname_tmp, 'w')) as f_tmp:
                f_tmp.write(data)
        import os
        os.rename(fname_tmp, fname_out)

_r_nowrap = re.compile(u'[。、」]')
_r_alnum = re.compile(u'[A-Za-z0-9,.]')
_r_wrap = re.compile(u'[「]$')
def my_split(data, n_th):
        data = re.sub(u"[ \t ]+", ' ', data)
        line = u''
        lines = []
        n_line = 0
        c_prev = ''
        for c in data:
                n = my_count(c)
                if \
                                n_line + n <= n_th or \
                                _r_nowrap.match(c) or \
                                (_r_alnum.match(c) and _r_alnum.match(c_prev)) :
                        line += c; n_line += n
                else:   
                        t = _r_wrap.search(line)
                        if t:   
                                t = t.start()
                                lines.append(line[0:t])
                                line = line[t:] + c
                                n_line = my_count(c)
                        else:   
                                lines.append(line)
                                line = c; n_line = n
                c_prev = c
        if len(line):
                lines.append(line)
        return lines

def split_long_line(v, n):
        v1 = []
        for s in v:
                v2 = my_split(s, n)
                for t in v2:
                        v1.append(t)
        return v1

def myfilter(s):
        import re
        s = re.sub(u'\([^()]*\)', '', s)
        s = re.sub(u'!', '! ', s)
        s = re.sub(u"[  ]\+", ' ', s)
        s = re.sub(u' $', '', s)
        s = re.sub(u'1', '1', s)
        s = re.sub(u'2', '2', s)
        s = re.sub(u'3', '3', s)
        s = re.sub(u'4', '4', s)
        s = re.sub(u'5', '5', s)
        s = re.sub(u'6', '6', s)
        s = re.sub(u'7', '7', s)
        s = re.sub(u'8', '8', s)
        s = re.sub(u'9', '9', s)
        s = re.sub(u' *×[1-9]', '', s) # 繰り返しを「x2」と書くことがあるけれど、消してしまう。
        n = n_maxchar
        v = s.split('\n')
        v = split_long_line(v, n)
        i=0; s=''
        for s1 in v:
                i1 = my_count(s1)
                if i + i1<n:
                        s = s+' ' + s1 if len(s) else s1
                        i += i1 + 1
                else:   
                        s += '\n' + s1 if len(s) else s1
                        i = i1
        return s

def main():
        s_prev = ''
        while True:
                try:
                        s_org = get_olp()
                        if s_org != s_prev:
                                s_prev = s_org
                                s = myfilter(s_org)
                                if flg_print: print u'display: %s'%s
                                obs_output(s)
                        from time import sleep
                        sleep(1)
                except:
                        # OpenLPが起動していない場合など
                        obs_output(u'')
                        from time import sleep
                        sleep(10)

if __name__=='__main__':
        main()
get_olpで、OpenLPからテキストを取得してきます。 OpenLPが別ホストで走っている場合は、url_baseとを書き換えましょう。 0.1秒毎に変更がないかポーリングし、変更があった場合はテキストを取得します。 ブランクかどうかの情報も取得し、ブランクならば空文字 ('') を返します。

myfilterで表記ゆらぎを修正し、改行を整えます。改行する文字数は、n_maxcharで設定できます。 行頭に句読点を入れないなど、禁則処理も行います。 英語の場合は、文字によって文字幅が異なるので、OBS Studioに改行処理をやらせたほうが良いでしょう。

最後に、obs_outputでファイルへ書き出します。 必要に応じて、出力先fname_outを書き換えましょう。 どのタイミングでファイルが読み込まれても良いように、一時ファイル fname_tmp へ書き出した後、renameを使ってファイルを置き換えます。 LinuxやMac OS Xではこの操作のアトミック性が保証されており、OBS Studioは書き換え途中のファイルを開くことはなく、書き換え前・書き換え後いずれかを開くはずです。

OBS Studioの設定

シーンにテキストを追加し、以下のようにファイルからの読み取りにします。

課題

OBS Studioでは、ファイルのタイムスタンプを1秒毎にチェックし、更新があれば読み出すようになっています。 テキストファイルを介するのでなくwebsocketを使ってテキストを送るスクリプトを作成しました。いずれ公開するかもしれません。

0 件のコメント:

コメントを投稿

色彩の印象について

このページに、PCCSトーンとその印象についてわかりやすく書かれていて参考になった。こんど映像を編集する機会があれば、このページを参考にしたいと思う。 著者は大学で映像制作を学んだ方のようで、基礎をわかりやすく説明されているように感じる。他の記事も読みたい。 【映像制作者が...