さんだすメモ

さメモ

技術ブログでは、ない・・・

アニメのセリフを収集してみる

pr1m3 v1d30 の字幕から集めてみました。
表にまとめるのを目標にし、話し手が7割くらい合う結果になりました。

各字幕

*.ttml2 を見てみると、次のように字幕が羅列されていることが分かります。
また、字幕の開始・終了時も抽出できます。

<p begin="00:00:00.459" end="00:00:03.921" style="s1"><span style="s2">(園児)か~くれんぼする人<br />この指 とまれ!</span></p>
<p begin="00:00:04.004" end="00:00:04.755" style="s1"><span style="s2">(園児たち)やる~!</span></p>
<p begin="00:00:04.839" end="00:00:06.424" style="s1"><span style="s2"><span style="s3"><span style="s4">後藤</span><span style="s5">ごとう</span></span>ひとり)<span style="s6">私なんかが…</span></span></p>

スタイルについて

<p begin="00:00:04.839" end="00:00:06.424" style="s1"><span style="s2"><span style="s3"><span style="s4">後藤</span><span style="s5">ごとう</span></span>ひとり)<span style="s6">私なんかが…</span></span></p>

各行に span が大量にありますが、 style="s○" の意味については、ヘッダを見てみると次のように解釈できます。
なお、この s○ は動画によって変わるので毎回抽出する必要があります。

<!-- ルビ部分の全体 -->
<style tts:ruby="container" xml:id="s3"></style>

<!-- ルビ -->
<style tts:ruby="text" tts:rubyPosition="before" xml:id="s5"></style>
<style tts:ruby="text" tts:rubyPosition="after" xml:id="s8"></style>

<!-- ルビ部分の本文 -->
<style tts:ruby="base" xml:id="s4"></style>

<!-- その他 -->
<style tts:textAlign="center" xml:id="s1"></style>
<style tts:textAlign="start" xml:id="s2"></style>
<style tts:fontShear="16.78842%" xml:id="s6"></style>
<style tts:fontShear="16.78842%" tts:textAlign="center" xml:id="s7"></style>

ルビは不要なので削除しました。

全角括弧

話し手などの情報は全角括弧で示されています。
(<話し手>)<セリフ> または (<効果音>) または <セリフ> が連なっていると考えてよさそうです。
なお、<br/> の扱いを考える必要がありますが、その後に違う文が続くときは必ず全角括弧があるようでした。

<p begin="00:02:25.437" end="00:02:27.106" style="s1"><span style="s2">(玄関のチャイム)<br />(ひとり)ハッ!</span></p>
<p begin="00:02:27.189" end="00:02:29.149" style="s1"><span style="s2">ぼっちちゃん 来たよ~</span></p>

※ span や br などのタグの部分を消す

(玄関のチャイム)(ひとり)ハッ!
ぼっちちゃん 来たよ~

その他

半角括弧

今回は半角括弧で囲まれた部分は読みを表しているようだったので削除しました。

(ひとり)きの う 虹夏(にじか)ちゃんから

名前の省略

二度目以降は名前が省略されるので、事前に組み合わせでまとめておくとよいと思います。

(後藤ひとり)あっ あっ あ…
(ひとり)イ… イエ~イ!

最終的なコードの概要

ひとまずの抽出部分を書きました。

# p タグについてのループ
for p in div:
    # 開始・終了時の取得
    begin = p.attrib.get('begin')
    end = p.attrib.get('end')

    # ルビの削除
    for span in p.findall(f'.//{{{namespace}}}span'):
        if span.attrib.get('style') in id_exclusion_list:
            span.text = ''

    # 現在の p タグの要素を文字列として取得
    s = ET.tostring(p, encoding='utf-8').decode().strip()

    # タグを削除
    s = re.sub(r'<[^<>]*>', '', s)

    # 半角括弧と中身を削除
    s = re.sub(r'\([^()]*\)', '', s)

    # 全角括弧をまとまりの起点として、正規表現で話し手やセリフを抽出
    for m in re.finditer(r'(?P<brace>((?P<in_brace>[^()]*)))(?P<line>[^()]*)|(?P<line2>[^()]+)', s):
        # (効果音)の形
        brace = m.group('brace')
        line = m.group('line')
        if brace != None and line == None:
            print(f'効果音: {m.group("se")}')
            continue

        # (キャラ)セリフの形
        in_brace = m.group('in_brace')
        if line != None:
            print(f'キャラ: {in_brace}, セリフ: {line}')
            continue

        # セリフのみの形
        line2 = m.group('line2')
        if line2 != None:
            print(f'セリフ: {line2}')
            continue

        print('exception')

キャラは直近のものを採用するなど、適切な処理を加えてしまえば、あとは出力するだけです。

その後

喋っているキャラが画面から明らかに判別できる場合は、全角括弧でキャラ名を示されないようです。
そのため、今回の方法ではいくつもキャラ名を取りこぼします。
良い方法で修正しても、最後は手作業が必要でしょう。

自分の場合は、プルダウンでキャラを選択して修正できるようにしました。
適当に音声を録音して、編集中に自在に流せるようにしておくと便利です。
また、選択に応じてテキストの色が変わるようにすると、認識が楽になりました。

良き収集ライフを——。