📰

PDFのページを画像化するシェルスクリプト

2024/04/25に公開

概要

LVMを使っていると「PDFを分析させたいけど画像しか対応していない」、「そもそもPDFだけど元がパワポで構造が複雑」みたいなことがあると思います。(私はありました)
資料の数もページ数も多いとさすがにスクリプト書かないと厳しかったので、

  1. PDFのページをすべて画像化
  2. フォルダ内のすべてのPDFに対して実行

の2つのスクリプトを書きました。

環境

  • WSL2 (Ubuntu 22.04.4)

準備

ImageMagick(画像変換全般のライブラリ・納品物に組み込むなどする場合ライセンスには注意してください)、poppler-utils(PDFのページ数カウントに使用)が必要になるのでインストールします。

sudo apt install imagemagick poppler-utils

Ubuntu環境がない人のためにWindowsでも使えるようにしようかと思ったのですが、ImageMagick内で使うGhostScriptも別でインストールする必要があるなど手間が多くて不便だったのでやめました。

Note

  • 一括変換しようとするとメモリオーバー
    • convert -density 300 $input $outputで一気に変換しようとするとメモリオーバー
    • メモリ16GB環境で30ページくらいの資料でもメモリが足りなくなりました(WSLのメモリ使用量を増やすのは焼け石に水)
    • 時間はかかりますがページ毎に変換する方針にしました
  • 変換画質
    • 資料によっては画質を落とせばいいのでdensity, qualityを下げればいいと思います
    • 今回は資料の性質から png かつdpi 200くらいが許容範囲だったのでコードの内容になっています
  • プログレスバー
    • 待っている間の進捗状況可視化

コード

convert_pdf_to_images.sh <input file>

  • 対象のファイルを画像化します。
  • カレントディレクトリに"images/<拡張子なしファイル名>"のディレクトリを作成し、"0埋め3桁のページ番号.png"で画像ファイルを出力します。
    • 親階層ディレクトリ"../"の文字列は消去するようにしていますが、inputのディレクトリ構造を保持するため絶対パスは使わない方がいいです。
convert_pdf_to_images.sh
start_time=$(date +%s)

show_progress() {
    local current=$1
    local total=$2
    bar_length=50

    # 進捗率に基づいたバーの長さを計算
    local filled_length=$((current * bar_length / total))
    
    # バーの表示部分を生成
    local bar=$(printf '%-*s' "$filled_length" '' | tr ' ' '=')
    local empty=$(printf '%*s' $((bar_length - filled_length)) '' | tr ' ' ' ')

    # プログレスバーの更新
    printf "\r[%s%s] %d%%" "$bar" "$empty" $((current * 100 / total))
}

input=$1
output_dir="images/${input%.*}"
output_dir="${output_dir//..\//}" # remove ../
# Check if the input file has a .pdf extension
if [[ ! $input =~ \.pdf$ ]]; then
    echo "$1 is not pdf file. skipped converting."
elif [[ -d $output_dir ]]; then
    echo "$output_dir already exists. Skipping converting."
else
    echo "output directory: $output_dir"
    mkdir -p $output_dir

    # ボツ: ページ数が多いファイルを一気に変換しようとするとメモリが足りなくなるため、ページごとに変換する
    # output="$output_dir/${input%.*}.png"
    # convert -density 300 $input $output

    pages=$(pdfinfo $input | grep Pages | awk '{print $2}')
    printf "convert pages: $pages\n"
    for i in $(seq 1 $pages); do
        show_progress $i $pages
        page_num=$(printf "%03d" $i)
        convert -density 200 -quality 100 $input[$((i-1))] $output_dir/${page_num}.png
    done
    printf "\n finish converting \n"
fi

end_time=$(date +%s)
exec_time=$((end_time - start_time))
printf "time: $exec_time seconds \n\n"

convert_all_pdf_to_images.sh <target dir>

  • 対象のディレクトリすべてを画像化します。
  • 並列化は諦めてしまったので別ターミナルを開いて複数プロセスで実行していました
    • そのため既に画像化されているファイルはスキップするようにしています
convert_all_pdf_to_images.sh
#!/bin/bash

# convert_pdf_to_png.shスクリプトの実行コマンドを設定
CONVERT_SCRIPT=". convert_pdf_to_images.sh"

# Get the list of PDF files in the current folder
if [ -z "$1" ]; then
    echo "No directory specified."
elif [ ! -d "$1" ]; then
    echo "Directory does not exist."
else
    # ディレクトリ内のPDFファイルのリストを取得
    PDF_FILES=$(find "$1" -type f -name "*.pdf")
    # PDFファイルのリストを表示
    echo "PDF files to convert:"
    echo "$PDF_FILES"
    # PDFファイルの数をカウント
    PDF_COUNT=$(echo "$PDF_FILES" | wc -l)
    # 変換を実行する前にユーザーに確認する
    read -p "上記の ${PDF_COUNT} ファイル を変換しますがよろしいですか? (y/n): " answer
    # レスポンスチェック
    if [[ $answer == "n" || $answer == "N" ]]; then
        echo "変換をキャンセルしました。"
    else
        echo "Converting PDF files..."
        # convert_pdf_to_png.shスクリプトを使用してリストに含まれるPDFファイルを変換
        for PDF_FILE in $PDF_FILES; do
            $CONVERT_SCRIPT "$PDF_FILE"
        done
    fi
fi
ヘッドウォータース

Discussion