PNGの仕様を眺める

このブログは現在、Hugo という静的サイトジェネレータを使用してページを生成しているのですが、このHugoには多くの便利機能があり、そのうちの一つがImage processing です。 テンプレートの中で、ほんの数行のコードを書くだけで、画像のサイズを調整したり、切り抜いたり、あるいはぼかしたりすることもできます。これまでのところ、このブログでは画像のサイズは結構適当で、場合によっては表示の幅が変わってしまって見栄えが良くないということが(たまに)ありました。 そこで今回、HugoのImage processing機能のうち、Resizeを使用して、実際にサイトが表示されるときの画像を良い感じにしようと思ったのですが、サイト全体の画像描画部分にhookをかけたところ、エラーが出てビルドができなくなってしまいました。エラーメッセージの大半はファイルパスで、今回は本筋ではない上に長いので端折るとして、重要そうなところを抜き出すと次の様な感じ: 1 2 execute of template failed at <$image.Resize>: error calling Resize: : resize : png: invalid format: invalid checksum 要するに、Resizeしようとしたときに、checksumが合わないので対象のpng画像ファイルがおかしい、ということらしいんですよね。しかしこれまでこのブログを更新してきて、(パスとかが間違っていない場合に)画像が表示されなくて困ったこともないし、そもそもpngファイルにchecksumがあるなんてことも知らないし・・・ golang/go#43382 での会話を見たところによるとどうやら、pngファイルには「チャンク」と呼ばれるモノが存在して、これが無視できることも多い(特に実際に表示する時には不要なモノが多い)けれど、Goのimage/pngは今のところはそれらを無視せず、エラーを吐くことになっている、ということらしい。 エラーの対象となったファイルを、issueの説明に書かれているようにpngcheckにかけてみると次のようにCRCエラーがあることが分かりました: 1 2 3 4 5 6 7 $ pngcheck -v arch-1.png File: arch-1.png (272304 bytes) chunk IHDR at offset 0x0000c, length 13 1082 x 778 image, 32-bit RGB+alpha, non-interlaced chunk zTXt at offset 0x00025, length 188903, keyword: mxGraphModel CRC error in chunk zTXt (computed c3f0b5f1, expected 1ce878d9) ERRORS DETECTED in arch-1.png なるほど、zTXtというチャンクがあって、そこのCRCが間違っている、ということらしいですね。キーワードはmxGraphModelということで、分からないけど多分グラフに関連したモノが入っているのでしょう。確かにこのファイルはdraw.ioで書き出したもので、書き出しの時に編集情報のようなものを埋め込むオプションを有効にした気がするので、それがどこかのタイミングで壊れ、今回のエラーにつながった、という流れの様です。 ...

2024-02-16 · nasa9084

BlogカードShortcodeを実装した

例えば、はてなブログだとブログカードと呼ばれるこういうの: が有ったり、wordpressだとプラグインでこういうの: が有り、リンクをなんだか良い感じに表示してくれます。このブログでつい先日まで使っていたGhostでも、こういうの: が有りました。 一方、現在使っているHugoには標準機能でこういったものを表示する機能はありません(twitterとかYoutubeはあるんですけど・・・)。しかし、無ければ作れば良いじゃない、ができるのがHugoの良いところです。 Hugoにはshortcode という機能があり、例えば標準のtwitter shortcodeだと、 {{< tweet user="nasa9084" id="1519598305554362370" >}} と書くと 牛乳はさ、牛乳-like飲物よりうまいんだよな — nasa9084@某某某某(0x1e) (@nasa9084) April 28, 2022 の様に展開されます。なので今回は {{< web-embed url="https://example.com" >}} というshortcodeを作ってみようと思います。調べてみると同様の実装をしている人もいましたので、それを参考にしつつ実装していきます。 まず、URLからデータを取得してくるにはHugoのgetJSON を使うと良さそうです。残念ながらOGP情報などを取得する方法は用意されていないようなので、指定したURLからOGP情報をとってきてJSONとして返す様なプロキシ的なサーバが必要そうです。cloud functions for firebase + javascript で実装している人もいれば、Netlify Functions + javascript でやっている人もいるという感じでしたが、やはり個人的にはGoがシュッと読み書きできて早いし、Cloud Functionsなどで常時稼働させておくには認証とかのことも考える必要がありありそう(まぁ無くてもいいっちゃいいけど、よくわからん踏み台にされても面白くない)で面倒だな、ということでちょっと困ったんですが、OGPプロキシサーバは特に状態を持っておらず、hugo buildする間だけ存在してくれればいいので、GitHub Actionsのサービスコンテナとしてプロキシを動かすことにしました。ローカルでテストビルドするときもdocker runすれば良いだけなので簡単です。 ハンドラの実装は次の通りです: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 url := r.URL.Query().Get("url") if url == "" { http.Error(w, `{"message": "url parameter is required"}`, http.StatusBadRequest) return } log.Printf("request URL: %s", url) ogp, err := opengraph.Fetch(url) if err != nil { http.Error(w, fmt.Sprintf(`{"message": "error fetching OGP", "error": "%s"}`, err.Error()), http.StatusInternalServerError) return } if err := ogp.ToAbs(); err != nil { http.Error(w, fmt.Sprintf(`{"message": "error converting relative URLs to absolute URLs", "error": "%s"}`, err.Error()), http.StatusInternalServerError) return } var body bytes.Buffer if err := json.NewEncoder(&body).Encode(ogp); err != nil { http.Error(w, fmt.Sprintf(`{"message": "error encoding OGP info to JSON", "error": "%s"}`, err.Error()), http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) body.WriteTo(w) コード全体はここ にあります。リポジトリを作ったときにhugo用のディレクトリをルートディレクトリではなく一段掘ったサブディレクトリにしておいたおかげで気軽にディレクトリを追加できたので良かったですね。 ...

2022-04-29 · nasa9084

Migrate Ghost to Hugo

いつの頃からだったか、もう記憶もあやふやではあるけれど、ブログプラットフォームとしてGhost を使っていた。twitter を見る限り、2017年の11月頃には既にGhostを使っていて、確かこの時はDockerでセットアップしていた様な記憶がある。 Ghost、すごい勢いでアップデートされてってて結構アプデが手間 — nasa9084@某某某某(0x1e) (@nasa9084) November 9, 2017 Ghostは結構更新が頻繁で、特にdocker-composeとかも使わずに運用していたので(使っても良かったんだけど、当時はDBもsqliteを使っていてコンテナ一つと永続ボリューム一つ、という単純な構成だったので使わなくて良いか、と思っていた)微妙にイメージの更新が面倒で、container-up というツールを書いてみたりもした。 その後自宅にKubernetesクラスタをセットアップしてKubernetes管理になり、データベースもMySQLに切り替え、最終的にはGCPのfree tierを使ってon VMで運用していた。 Ghostを使い始めた頃はバージョンもまだ1系だったけど、今となっては4系になって、相も変わらず活発に開発され、admin UIも大分変化した。 時代の流れとしては当然といえば当然なのだけれど、Ghost 5.0ではMySQL 8が必須となるということで、最近MySQLの更新をしたところ、頻繁に外形監視がfailする様になった。どうやらリソース不足でレスポンスを返せなくなっていたようだった。free tierのVMなのでe2-microインスタンスを使っているため、さもありなんといった感じ。 もちろん多少のお金を払ってもう少し良いVMにしても良いのだけれど、それほど頻繁に書いているわけでもないブログを運用するためだけに月数千円の出費はいかがなものか、大して書いてもいないのだから静的ページ生成でも良いのではないか、静的ページ生成ならデータベースもいらないしGitHub pagesで配信できて無料ではないか、などと思い、k8s.io でも使っているHugo に乗り換えることにした。 参考にしたのはこのページ 。多少古い記事だけど多少調整すればなんとかなるだろう、と思い見切り発車した。結果なんとか移行はうまくいき、このページが表示されています。 移行手順 まず、ghostToHugo をダウンロードして、Ghostから出力したjsonファイルをHugoにインポート。(ghostToHugoはDarwin_x86_64のバイナリを使ったけど、apple siliconのmacOSでもrosettaで普通に問題無く動いた) 1 $ ./ghostToHugo -p blog.web-apps.tech something-tech.ghost.2022-04-22-02-57-56.json Google Cloud Storageにアップロードしていたバックアップから画像ファイルを取り出してimagesディレクトリに配置した。 1 $ cp ${PATH_TO_BACKUP}/content/images ./blog.web-apps.tech/images イメージのパスをちょっと調整。 1 2 $ find . -name '*.md' | xargs sed -ie 's/__GHOST_URL__//g' $ find . -name '*.md' | xargs sed -ie 's/\/content\/images\//\/images\//g' front-matterをYAMLに変更。 1 2 3 $ cd blog.web-apps.tech $ hugo convert toYAML $ cd ../ そのままではすべての記事が年のディレクトリ以下にまとまって入っていて画像管理が大変そうなので次のスクリプトで構成変更。 ...

2022-04-23 · nasa9084