技術ドキュメント: images2md_tags.py

プログラムの動作

images2md_tags.py は、指定された画像ファイルからサムネイル画像を生成し、それらを埋め込んだMarkdown形式のテキストを標準出力に生成するPythonスクリプトです。このプログラムは、大量の画像をMarkdownドキュメント(例えば、ブログ記事や技術ドキュメント、画像ギャラリーなど)に整理して埋め込むプロセスを自動化することを目的としています。

主な機能は以下の通りです。

  • 画像ファイルの検索: ワイルドカードパターンを使用して、指定されたディレクトリ内の画像ファイルを効率的に検索します。

  • サムネイルの生成: 検索された各画像ファイルに対し、指定された幅のアスペクト比を維持したサムネイル画像を生成し、元のファイルと同じディレクトリに保存します。

  • 既存サムネイルの管理: 既にサムネイルが存在する場合、上書き、更新(元のファイルが新しい場合のみ)、またはスキップのオプションを提供します。

  • Markdown出力: 生成されたサムネイルと元の画像へのリンクを含むMarkdown形式のテキストを標準出力に出力します。

これにより、手動でサムネイルを作成し、個々の画像のMarkdownリンクを記述する手間を省き、効率的なドキュメント作成を支援します。

原理

このプログラムは、主に以下の原理とアルゴリズムに基づいて動作します。

  1. 画像リサイズアルゴリズム:

    • Pillow ライブラリの Image モジュールを使用して画像を開き、その元の幅 \(w\) と高さ \(h\) を取得します。

    • コマンドライン引数 --width で指定されたターゲットの幅 \(\text{width}\) を使用し、アスペクト比を維持して新しい高さ \(\text{new\_h}\) を計算します。この計算は以下の数式に基づいています。

      \[\text{new\_h} = \text{int}\left(h \cdot \frac{\text{width}}{w}\right)\]
    • Image.LANCZOS フィルターを用いて画像を新しいサイズ \((\text{width}, \text{new\_h})\) にリサイズします。このフィルターは高品質なリサンプリングに適しています。

    • リサイズされた画像を新しいファイル名で保存します。

  2. ファイルパス操作:

    • Pythonの pathlib モジュールを使用して、オペレーティングシステムに依存しない形でファイルパスを操作します。

    • 元の画像ファイルの stem (拡張子を含まないファイル名) と suffix (拡張子) を取得します。

    • サムネイルファイル名は、元のファイルの stem-s を追加し、元の suffix を維持する形式で生成されます (例: image.jpgimage-s.jpg)。

  3. サムネイルの更新ロジック:

    • --overwrite オプションが 1 の場合、既存のサムネイルファイルは常に上書きされます。

    • --update オプションが 1 の場合、既存のサムネイルファイルが元の画像ファイルよりも古い場合にのみ更新(再生成)されます。ファイルの日付情報 (stat().st_mtime) を比較して判断されます。

    • どちらのオプションも指定されていない場合、既存のサムネイルファイルはスキップされ、再生成は行われません。

  4. Markdownテキスト生成:

    • 各画像ファイルに対して、Markdown形式のブロックテキストを生成します。

    • テキストは textwrap.dedent を使用してインデントを整形し、f-string と3重引用符で記述されたテンプレートによって構成されます。

    • 生成されるMarkdownは以下の形式です。

    ### [画像ファイル名(拡張子なし)]
    [元のファイル名](元のファイル名)  
    [![画像ファイル名(拡張子なし)](サムネイルファイル名)](元のファイル名)
    
    • これは、画像の説明、元の画像への直接リンク、そしてサムネイル画像をクリックすると元の画像にリンクする形式の埋め込み画像を生成します。

必要な非標準ライブラリとインストール方法

このプログラムは、画像処理のために非標準ライブラリ Pillow を使用します。他のモジュール(argparse, glob, os, pathlib, textwrap)はPythonの標準ライブラリに含まれています。

Pillow のインストールは、Pythonのパッケージマネージャー pip を使用して行います。

pip install Pillow

必要な入力ファイル

このプログラムは、Pillowがサポートする任意の画像ファイルを処理できます(例: JPEG, PNG, GIF, BMP, TIFFなど)。

  • ファイルの種類: プログラムは、glob モジュールによって指定されたワイルドカードパターンに一致するすべての画像を検索します。

  • ファイル名: 特に厳密な命名規則はありませんが、プログラムは *-s.* の形式のファイルを既に生成されたサムネイルとみなし、処理対象から除外します。これは、自身の生成したサムネイルを誤って再処理することを防ぐためです。

  • ファイルの場所: コマンドラインで指定するワイルドカードパターン (pattern) に基づいてファイルが検索されます。カレントディレクトリ、サブディレクトリ、または絶対パスを指定できます。

生成される出力ファイル

images2md_tags.py は、以下の2種類の出力を生成します。

  1. サムネイル画像ファイル:

    • ファイル名: 元の画像ファイル名に -s が追加された形式になります。例えば、photo.jpg というファイルに対しては photo-s.jpg というサムネイルが生成されます。

    • 保存場所: 元の画像ファイルと同じディレクトリに保存されます。

    • 内容: 指定された幅 (--width オプション) にリサイズされ、アスペクト比が維持された画像データです。

  2. Markdownテキスト:

    • 出力先: プログラムの標準出力 (stdout) に出力されます。通常は、シェルのリダイレクト機能 (>) を使ってファイルに保存することが推奨されます。

    • 内容: 検索された各画像ファイルと、それに対応する生成されたサムネイルを参照するMarkdown形式のテキストブロックです。各ブロックは以下の構造を持ちます。

    ### [画像のベース名]
    [元の画像ファイル名](元の画像ファイル名)  
    [![画像のベース名](サムネイル画像ファイル名)](元の画像ファイルファイル名)
    

    ここで、「画像のベース名」は画像ファイル名から拡張子を除いた部分です。この構造により、Markdownレンダラーで表示した際に、クリック可能なサムネイル画像と、その下に元の画像ファイル名へのリンクが表示されます。

コマンドラインでの使用例 (Usage)

プログラムの基本的な使用方法は以下の通りです。

usage: images2md_tags.py [-h] [--width WIDTH] [--update UPDATE] [--overwrite OVERWRITE] pattern

positional arguments:
  pattern                検索するワイルドカードパターン

options:
  -h, --help             show this help message and exit
  --width WIDTH          生成するサムネイルの幅(ピクセル)。デフォルトは200。
  --update UPDATE        1を指定すると、元の画像ファイルよりも新しいサムネイルが存在しない場合にのみ更新する。デフォルトは0。
  --overwrite OVERWRITE  1を指定すると、既存のサムネイルファイルを強制的に上書きする。デフォルトは0。

基本的な実行コマンドは以下の形式です。

python images2md_tags.py <pattern> [オプション]
  • <pattern>: 必須引数で、検索対象の画像ファイルを指定するワイルドカードパターンです(例: *.jpg, images/*.png)。

  • --width <WIDTH>: 生成するサムネイルのピクセル幅を指定します。デフォルトは 200 ピクセルです。

  • --update <UPDATE>: 1 を指定すると、元の画像ファイルよりもタイムスタンプが古い既存のサムネイルのみを更新(再生成)します。デフォルトは 0(更新しない)です。

  • --overwrite <OVERWRITE>: 1 を指定すると、既存のサムネイルファイルがあっても強制的に上書きして再生成します。デフォルトは 0(上書きしない)です。

コマンドラインでの具体的な使用例

以下の例では、現在のディレクトリに image1.jpgimage2.png という画像ファイルが存在すると仮定します。

  1. 基本的な使用例(すべてのJPG/PNG画像からサムネイルを生成し、Markdownに出力)

    python images2md_tags.py "*.jpg" "*.png" --width 150 > gallery.md
    

    実行結果の説明:

    • カレントディレクトリ内のすべての .jpg および .png ファイルが検索対象となります。

    • 各画像ファイルに対し、幅150ピクセルのサムネイル(例: image1-s.jpg, image2-s.png)が生成され、元の画像と同じディレクトリに保存されます。

    • サムネイルが生成された、またはスキップされた旨のメッセージが標準出力に表示されます(例: [生成] image1-s.jpg)。

    • 生成されたMarkdownテキストは標準出力から gallery.md ファイルにリダイレクトされて保存されます。

    gallery.md の内容例:

    ### image1
    [image1.jpg](image1.jpg)  
    [![image1](image1-s.jpg)](image1.jpg)
    
    ### image2
    [image2.png](image2.png)  
    [![image2](image2-s.png)](image2.png)
    
  2. 既存のサムネイルをすべて上書きして再生成する場合

    python images2md_tags.py "photos/*.jpeg" --width 100 --overwrite 1 > new_photos_gallery.md
    

    実行結果の説明:

    • photos/ ディレクトリ内のすべての .jpeg ファイルが対象となります。

    • --overwrite 1 が指定されているため、既に photos/photo-s.jpeg のようなサムネイルが存在していても、強制的に幅100ピクセルで再生成され上書き保存されます。

    • Markdownテキストは new_photos_gallery.md に保存されます。

  3. 元の画像が更新されている場合にのみサムネイルを更新する場合

    python images2md_tags.py "documents/*.tiff" --update 1 > updated_docs.md
    

    実行結果の説明:

    • documents/ ディレクトリ内のすべての .tiff ファイルが対象となります。

    • --update 1 が指定されているため、既存のサムネイルファイル doc-s.tiff が存在する場合、そのタイムスタンプが元の画像ファイル doc.tiff よりも古い場合にのみサムネイルが再生成されます。

    • サムネイルが新しく、元の画像が更新されていない場合は、再生成はスキップされます。

    • Markdownテキストは updated_docs.md に保存されます。

注意点: プログラムのメッセージ([生成] ...[skip] ...)は標準出力に出力されます。そのため、Markdown出力をファイルにリダイレクトする場合、これらのメッセージも同時にリダイレクトされてしまう可能性があります。メッセージを標準エラー出力に分離したい場合は、プログラムの該当箇所を print(...) から sys.stderr.write(...) に変更する必要があります。現在の実装では、上記のようにMarkdownをリダイレクトすると、出力ファイルにメッセージも含まれてしまいます。