2
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Fountain の台本をパースしてデータ化する

Last updated at Posted at 2019-12-31

前置き

Fountain は台本を書くことに特化した記法で、Markdown のような良さがあります。
Fountain で書かれた台本をパースしてデータ化する仕組みを、開発中の Web アプリに追加したので、その紹介をします。

この記事を書いたあとに playscript というパッケージを作って、fountain (日本式) を採用しました。

作っているもの

演劇の稽古プランを補助するツールを作っています。
配役、出番、稽古の出欠といったデータから、いつどのシーンが稽古できるか、可視化するものです。
-> GitHub のリポジトリ (Python 3.8, Django 3.0.2)

Fountain を日本語で書く

Fountain を採用する上での問題は、英語の台本を書くことが前提となっていることです。
日本語で書く場合については、以下で考察しています。

記法

日本語で書く場合の詳しい考察については上記のリンクを御覧ください。
ここでは私が採用することにした記法について、各要素の例を紹介します。
この記法で書いたサンプルが こちら にあります (日本語のサンプルがないので自分の作品で恐縮です)。

題名と著者名

Title: アンダーコントロール
Author: 沙汰青豆

登場人物のブロック

# 登場人物

捨村: 保存容器会社の社員
京野: 保存容器会社の社員
荻島: 捨村たちの先輩

柱は階層化できます。

# 第一幕
## シーン1
### 研究室

セリフ

空行に続けて、人物名の行とセリフの行を書きます。

@京野
置く場所ねえぞこれ。

ト書き

パターン定義されていない書き方の行は、ト書きになります。

舞台中央やや奥めにデスク。左右から座れるようになっている。

台本から作るデータ

このツールのために台本から作れるようにしたいデータは、以下の3つです。

  • 登場人物のリスト
  • シーンのリスト
  • 各シーンでの、各登場人物の出番 (セリフ数)

セリフ数を記録する理由

一般的な香盤表では、その人物が出ているかどうかを「○」印などで表します。
私のツールでは、役者の出欠状況から「どのシーンを稽古すべきか」を判断できるように出番に重みをつけていて、その指標としてセリフ数を記録しています。

パース

パース用モジュール

Fountain のパース用のモジュール を公開している方がいたので、これを使わせてもらうことにしました。
このモジュールを使って Fountain 記法の文字列をパースすると、Fountain クラスのオブジェクトになります。
パースするには、コンストラクタに文字列を渡します。

# text は Fountain 形式の文字列
f = fountain.Fountain(string=text)

Fountain オブジェクト

Fountain オブジェクトには、以下の属性があります。
この記事のタスクでは、elements 属性の内容からデータを作ります。

  • metadata
    • Title Page にある key: value 形式のデータをパースしたもの。
    • 題名や著者名が (元の文書にあれば) 含まれます。
  • elements
    • FountainElement オブジェクトのリスト。
    • FountainElement は、柱、セリフ、ト書きといった要素を表します (下記参照)。
  • contents
    • パースする前の文字列。

FountainElement オブジェクト

FountainElement オブジェクトは、元の Fountain 形式の台本の、行に対応します (連続する複数行に対応する場合もあります)。
この記事のタスクでは、FountainElement オブジェクトの属性のうち、以下の属性を使います。

  • element_type
    • 柱、セリフ、ト書きといった、要素の種類を表します。
  • element_text
    • 具体的な内容です (柱ならシーン名、セリフならセリフの内容など)。

パースの例

from fountain import fountain

text = '''
# シーン1

@京野
置く場所ねえぞこれ。
'''

f = fountain.Fountain(string=text)

for e in f.elements:
    print(e.element_type, e.element_text)

結果

Section Heading シーン1
Empty Line
Character 京野
Dialogue 置く場所ねえぞこれ。

データを作る

欲しいデータを作るための関数を作って、以下のように呼び出すことにしました。

characters, scenes, appearance = data_from_fountain(text)

print(characters)
print(scenes)
print(appearance)

欲しい返り値

上記の呼び出しの結果、欲しい返り値は以下のようになります。
characters は登場人物名のリスト、scenes はシーン名のリストです。
appearance は、シーンごとの "出番" をリストにしたものです。
"出番" は人物名をキー、セリフ数を値とした dict です。

['京野', '捨村', '荻島', '二人', '容器']
['シーン1', 'シーン2', 'シーン3', 'シーン4', 'シーン5', 'シーン6', 'シーン7']
[{'京野': 26, '捨村': 26}, {'荻島': 14, '捨村': 9, '京野': 9}, {'京野': 24, '捨村': 22}, {'捨村': 10, '荻島': 11, '京野': 3}, {'捨村': 21, '京野': 20, '二人': 1, '容器': 1}, {'京野': 6, '捨村': 5}, {'京野': 10, '捨村': 12, '荻島': 20}]

関数の中身

さほど難しいコードでもないので、丸ごと載せてしまいます。
ポイントは、登場人物のブロックをカウントしないようにしている所です。
登場人物名は、登場人物ブロックからは取らずに、セリフの人物名から集めます。

また、日本語用のルールとして、Scene HeadingSection Heading の両方を「シーンの見出し」として扱っています。

def data_from_fountain(text):
    '''Fountain フォーマットの台本からデータを取得
    
    Parameters
    ----------
    text : str
        台本のテキストデータ
    
    Returns
    -------
    characters : list
        Character 行から取得した登場人物名のリスト
    scenes : list
        Scene Heading 行または Section Heading 行から取得したシーン名のリスト
    appearance : list
        シーンごとの、出番 (dict) のリスト
    '''
    
    # パース
    f = fountain.Fountain(string=text)
    
    characters = []
    scenes = []
    appearance = []
    scn_apprs = {}
    
    for e in f.elements:
        # セリフ主の行
        if e.element_type == 'Character':
            # 少なくとも1個のシーンが検出されていれば
            if scenes:
                # 今のシーンのその人物のセリフ数を出番としてカウント
                char_name = e.element_text
                current_count = scn_apprs.get(char_name, 0)
                scn_apprs[char_name] = current_count + 1
                # 登場人物に登録
                if char_name not in characters:
                    characters.append(char_name)
            continue
        # シーン見出し
        if e.element_type in ('Scene Heading', 'Section Heading'):
            # 少なくとも1個のシーンが検出されていれば
            if scenes:
                # 今のシーンが「登場人物」なら、削除
                if scenes[-1] == '登場人物':
                    scenes.pop()
                else:
                    # ここまでの出番を今のシーンの出番とする
                    appearance.append(scn_apprs)
                # 出番をクリア
                scn_apprs = {}
            # 新しいシーンを追加
            scenes.append(e.element_text)
    
    # 最後のシーンの出番をセット
    if scenes:
        if scenes[-1] == '登場人物':
            scenes.pop()
        else:
            appearance.append(scn_apprs)
    
    return characters, scenes, appearance

Web アプリに組み込む

Web アプリに組み込むところの具体的な説明は割愛します。
理由は、Web アプリについても説明が必要になってしまい、この記事の主題から外れるからです。

興味のある方は GitHub のリポジトリ を御覧ください。
といってもまだドキュメントを整備していませんが…。

という訳で、何をやっているかの簡単な説明だけさせて頂きます。

やっていること

今までは「公演」>「新規作成」とやっていた公演の作成を、台本の画面からも出来るようにしました。

この一番下のリンクを押すと、「台本から公演を作成」という画面になります。

「作成」を押すと、公演を作成すると同時に、その公演の登場人物、シーン、出番のデータを追加します。

登場人物のデータが出来ています。

シーンのデータも出来ています。

出番は、「登場人物の詳細」や「シーンの詳細」から確認・編集できます。


出番は香盤表で一覧できます。

課題

  • 登場人物ブロックの見分け方をもう少し柔軟にすることで、'# 登場人物' 以外の書き方も出来るようにしたいです。
  • 登場人物ブロック内の情報を活かして、キャスト順の定義 (現状は登場順) や、正式名とセリフ用の短縮名の対応付けをしたいです。
  • 出はけの情報を書き込んで、セリフのない人物の出番を把握できるようにしたいです。
  • 他に良い記法がなければ、もっと Fountain を使えるようにしたいので、日本語用のプレビューアやレンダラーを作れたら良いなぁと思っています。
    • 記法についての考察は こちら でまとめています。
2
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?