🗨

Playwrightで会話風画像を生成してブログに埋め込む

2023/02/28に公開

この記事について

Zennブログ用のmarkdownに、以下の記述をすることで…

![会話内容を…](/images/article_20230228-1.png) <!-- {: .l-fuki .ria} -->
![こんな感じで…](/images/article_20230228-2.png) <!-- {: .r-fuki .ria_sad} -->
![画像表示するようにしてみたよ!](/images/article_20230228-3.png) <!-- {: .l-fuki .ria_gld} -->

会話内容を…
こんな感じで…
画像表示するようにしてみたよ!

モチベーション

絵を描くのが趣味なので、以前ははてなブログでCSSをカスタマイズし、キャラクターの会話風ブログを書いたりしていました。
ZennブログにデザインとGitHub連携に魅力を感じて引っ越ししましたが、CSSのカスタマイズができず何とか会話風にしたいと思っていたところ、Playwrightの存在を知り、試すことに!

Playwrightについて

PlaywrightはWebブラウザのテストを自動化できるAPIです。
同じような機能を持ったPuppeteerというNode.jsのAPIもありましたが、対応言語が多いPlaywrightを選定しました。

実装までの流れ

なんとかmarkdownの記述のみで画像生成、HTMLへの反映ができるようにしました。

  1. markdownの会話記述部分を抽出し、インプット情報を取得する
  2. インプット情報から、会話風CSSを表示するHTMLを作成する
  3. Playwrightで上記HTMLのスクリーンショットを取得する

スクリーンショットを、markdownで指定した画像パスに出力すればOKってことね?

markdownから会話記述の抽出

会話風のCSS設定については割愛しますが、HTMLの指定としては以下になります。

<p class="r-fuki ria_gld">ここにフキダシの内容を記載</p>

ちなみに、r-fukiがフキダシを描画するためのclass属性、ria_gldがフキダシの隣に表示する画像を指定するclass属性です。(CSSファイルに独自名で定義しているclass名)

フキダシ画像を生成するために必要な要素としては、このclass属性フキダシ内容、そして生成した画像のフルパス。この3つを記事markdownの中で1行に表現したものが次の例。

article.md
![ここにフキダシの内容を記載](/images/article_20230228-x.png) <!-- {: .r-fuki .ria_gld} -->

今回は画像参照のmarkdown記法と、コメントの記述を利用してみました。
これで、()内のファイル名で画像を生成すれば自然とmarkdownが読み込んでくれます。
なお、Zennの画像ファイル参照指定に従うので、ファイルパスは絶対パスになってます。

それぞれの要素を、pythonで以下のように取得します。

markdown_parser.py
import re

# markdownファイルを読み込み
with open(markdown_file_name, "r", encoding="utf-8") as input_file:
    lines = input_file.readlines()

# コメント行を抽出する
for line in lines:
    if "< !--" in line:
        
        # フキダシ内容の抽出
        text_reg = '(?<=\[).+?(?=\])'
        # ファイルパスの抽出
        file_reg = '(?<=\().+?(?=\))'
        # class属性の抽出
        class_reg = '{.*?}'

        text_ret = re.findall(text_reg, line)
        file_ret = re.findall(file_reg, line)
        class_ret = re.findall(class_reg, line)

全部カッコの種類が違うから、正規表現でイイカンジに抽出できるね!らくちん!

正規表現はググりました!

会話風CSSを適用したHTMLを作成

次に、抽出したclass属性フキダシ内容から、以下のHTMLを生成します。

<p class="r-fuki ria_gld">ここにフキダシの内容を記載</p>

ここでは、python-markdownの拡張機能が使えそうだったので選定。
classやidを指定したいブロックの最終行に{: #id名 .class名}のように記述すると、ブロックにその要素を追加してくれます。

ここにフキダシの内容を記載
{: .r-fuki .ria_gld} 

これをpythonで実装して、目的のHTMLを生成します。

markdown_parser.py
import os
import markdown

# markdownを生成
md = text_ret + "\n" + class_ret

# htmlファイルに出力
html = '<html><head><meta charset="utf-8">'
html += '<link rel="stylesheet" type="text/css" href="' + os.getcwd() + '/playwright/markdown/webpage/css/base.css" />'
html += '</head><body>'
html += markdown.markdown(md, extensions=['attr_list'])
html += "</body></html>"

with open('tmp.html', 'w') as f:
    f.write(html)

HTMLのガワ部分やCSSファイルの参照をゴリゴリやって、python-markdownの結果も組み込ました。生成されるHTMLは以下。必要なのはスクリーンショットなので、このHTMLは一時的なものです。

tmp.html
<html><head><meta charset="utf-8"><link rel="stylesheet" type="text/css" href="/work/playwright/markdown/webpage/css/base.css" /></head><body><p class="r-fuki ria_gld">ここにフキダシの内容を記載</p></body></html>

生成したHTMLのスクリーンショットを取得

メインのPlaywrightですが、HTMLの生成時点でほぼ見た目は完成しているので、ほとんどチュートリアルの内容でおしまいです。

markdown_parser.py
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.webkit.launch()
    page = browser.new_page()

    html_file_path = os.getcwd() + "/tmp.html"
        
    # 生成したhtmlをplaywrightで開く
    page.goto("file:///"+html_file_path)

    # スクリーンショットを取得
    clip_dict={'x': 0, 'y':10 ,'width': 740, 'height': 115}
    page.screenshot(path=file_ret, scale="css", clip=clip_dict)
    browser.close()

ポイントとしては、page.goto()のURL指定をローカルファイルのHTMLにするため、file:///にしていることと、生成したフキダシ部分が収まるようにpage.screenshot()clipオプションで範囲を指定したくらいです。また、pathオプションには最初に取得した画像のフルパスを渡してあげます。

これで完成だね!

おまけ

Playwright用のDockerfile

今回、以下のDockerfileで開発環境を構築しました。

Dockerfile
FROM mcr.microsoft.com/playwright/python:v1.30.0-focal
WORKDIR /work
RUN pip install markdown
RUN git clone https://github.com/googlefonts/zen-marugothic.git \
&& cp ./zen-marugothic/fonts/ttf/ZenMaruGothic-Regular.ttf /usr/local/share/fonts/

Playwrightの公式イメージがそのまま使えましたが、日本語のフォントが微妙だったので別途GoogleFontを活用しました。あとはpython-markdownを追加。

今後の課題

  • GitHubActionsを活用して、画像生成してGitへのPushを自動化する。
  • フキダシ内の改行が増えるとスクリーンショット範囲から見切れてしまうので、y軸の範囲を可変にする。
  • キャラクターの画像を増やす…。

Discussion