フォルダー内のファイル一覧をCSVで作成する
サブフォルダーを走査し、ファイルパス、拡張子、サイズ、更新日時を表として記録します。元のファイルと既存の結果をそのまま保持したまま、まず4つの小さなサンプルファイルで確認します。
複数の画像を引き伸ばしたり元ファイルを置き換えたりせず、新しいフォルダへリサイズして保存します。小さな合成画像セットを使うため、想定される出力寸法を手作業でも簡単に計算できます。
この翻訳はAIで作成しました。コード、単位、数値は原文と併せて確認してください。各言語のネイティブ話者による校閲は、まだ完了していません。 English
対象読者各画像の縦横比を維持し、元画像を変更せずに、多数の画像の小さいコピーを作成したい人向けのガイドです。
目的は、縦横比を維持したまま、すべての画像を 300 by 300 pixel の範囲内に収めることです。すでにその範囲より小さい画像は拡大しません。元画像は変更せず、リサイズした各コピーは別の出力フォルダに保存します。
幅 W、高さ H の画像では、scale を 300/W, 300/H, 1 のうち最も小さい値として計算します。その1つの scale factor から新しい幅と高さを計算します。両方の寸法に同じ係数を使うことで、画像の引き伸ばしを防ぎます。
scale = min(300 / width, 300 / height, 1)
new_width = int(width * scale)
new_height = int(height * scale)以下のファイルは合成データであり、この記事のために作成したものです。セットアップスクリプトは、寸法が既知のシンプルな PNG 画像を4枚作成します。画像の見た目そのものは重要ではなく、想定される各リサイズ結果を手作業で確認できるよう寸法を設定しています。
from pathlib import Path
from PIL import Image
SOURCE = Path("outputs") / "image_resize_demo"
IMAGES = {
"landscape.png": ((800, 600), (220, 80, 80)),
"portrait.png": ((600, 800), (80, 120, 220)),
"small.png": ((200, 100), (80, 180, 100)),
"wide.png": ((1200, 300), (180, 120, 60)),
}
SOURCE.parent.mkdir(parents=True, exist_ok=True)
SOURCE.mkdir() # Stop if this synthetic source already exists.
for filename, (size, color) in IMAGES.items():
image = Image.new("RGB", size, color)
target = SOURCE / filename
image.save(target, format="PNG")
python create_resize_demo.py| ファイル | 元の幅 | 元の高さ | 縦横比 |
|---|---|---|---|
| landscape.png | 800 | 600 | 4:3 |
| portrait.png | 600 | 800 | 3:4 |
| small.png | 200 | 100 | 2:1 |
| wide.png | 1200 | 300 | 4:1 |
4枚の元画像には合計 1,240,000 pixels があります: 480,000 + 480,000 + 20,000 + 360,000。pixel 数はこの例を理解するのに役立ちますが、PNG 圧縮は画像内容に依存するため、bytes 単位のファイルサイズとは同じではありません。
landscape.png では制限となる寸法は幅です: 300/800 = 0.375。両方の寸法に 0.375 を掛けると 300 by 225 になります。portrait.png では高さが制限になります: 300/800 = 0.375 なので、225 by 300 になります。
small.png は両方の寸法がすでに 300 以下なので、そのまま収まります。scale は最大 1 に制限されるため、200 by 100 のままです。wide.png では幅が制限になります: 300/1200 = 0.25 なので、300 by 75 になります。
| ファイル | Scale | 想定出力 | 出力 pixels |
|---|---|---|---|
| landscape.png | 0.375 | 300 × 225 | 67,500 |
| portrait.png | 0.375 | 225 × 300 | 67,500 |
| small.png | 1 | 200 × 100 | 20,000 |
| wide.png | 0.25 | 300 × 75 | 22,500 |
したがって、リサイズしたコピーに含まれる pixel 数の合計は 177,500 です。元画像は別に残るため、この値はディスク容量の削減量を示すものではありません。
次のスクリプトを batch_image_resize.py として保存してください。SOURCE 直下にある PNG, JPEG, WebP ファイルを処理します。サブフォルダは再帰的に検索しません。保存先は exist_ok を付けずに mkdir で作成するため、出力ディレクトリがすでに存在する場合は、以前の結果と新しい結果を混在させずにスクリプトが停止します。
from pathlib import Path
from PIL import Image, ImageOps
SOURCE = Path("outputs") / "image_resize_demo"
OUTPUT_DIR = Path("outputs") / "image_resize_result"
MAX_WIDTH = 300
MAX_HEIGHT = 300
SUPPORTED = {".png", ".jpg", ".jpeg", ".webp"}
def target_size(width: int, height: int) -> tuple[int, int]:
scale = min(MAX_WIDTH / width, MAX_HEIGHT / height, 1)
new_width = max(1, int(width * scale))
new_height = max(1, int(height * scale))
return new_width, new_height
def main() -> None:
if not SOURCE.is_dir():
raise FileNotFoundError(f"Source folder not found: {SOURCE}")
if MAX_WIDTH < 1 or MAX_HEIGHT < 1:
raise ValueError("Maximum dimensions must be positive integers.")
source_resolved = SOURCE.resolve()
output_resolved = OUTPUT_DIR.resolve()
if source_resolved == output_resolved:
raise ValueError("SOURCE and OUTPUT_DIR must be different.")
OUTPUT_DIR.parent.mkdir(parents=True, exist_ok=True)
OUTPUT_DIR.mkdir() # Refuse to reuse an existing destination.
candidates = sorted(
path for path in SOURCE.iterdir()
if path.is_file() and path.suffix.lower() in SUPPORTED
)
resized_count = 0
unchanged_size_count = 0
for source_path in candidates:
output_path = OUTPUT_DIR / source_path.name
if output_path.exists():
raise FileExistsError(f"Output already exists: {output_path}")
with Image.open(source_path) as opened:
image = ImageOps.exif_transpose(opened)
image.load()
old_size = image.size
new_size = target_size(*old_size)
if new_size == old_size:
result = image.copy()
unchanged_size_count += 1
else:
result = image.resize(new_size, Image.Resampling.LANCZOS)
resized_count += 1
save_options = {}
if source_path.suffix.lower() in {".jpg", ".jpeg"}:
save_options["quality"] = 90
result.save(output_path, **save_options)
result.close()
print(f"{source_path.name}: {old_size[0]}x{old_size[1]} -> {new_size[0]}x{new_size[1]}")
print(f"Processed {len(candidates)} images.")
print(f"Resized: {resized_count}; already within limit: {unchanged_size_count}.")
print(f"Output folder: {OUTPUT_DIR.as_posix()}")
if __name__ == "__main__":
main()
ImageOps.exif_transpose は、EXIF orientation 情報が存在する場合、リサイズ計算の前に画像の向きを適用します。合成 PNG ファイルにはこの向き情報がないため、表示寸法は上記の寸法のままです。
ファイル名はソート順で処理されるため、想定されるコンソール行は landscape.png, portrait.png, small.png, wide.png の順です。3枚の画像は小さくなり、1枚は元の寸法のままです。
landscape.png: 800x600 -> 300x225
portrait.png: 600x800 -> 225x300
small.png: 200x100 -> 200x100
wide.png: 1200x300 -> 300x75
Processed 4 images.
Resized: 3; already within limit: 1.
Output folder: outputs/image_resize_resultこの想定出力は実行ログを取得したものではなく、手作業で導出したものです。重要な確認項目は寸法、ファイル数、元画像が outputs/image_resize_demo に残っていることです。
| 確認項目 | 想定値 |
|---|---|
| 元フォルダ内のファイル数 | 4 |
| 出力フォルダ内のファイル数 | 4 |
| リサイズされたファイル | 3 |
| すでに制限内だったファイル | 1 |
| 最大出力幅 | 300 |
| 最大出力高さ | 300 |
ファイル名やコンソール表示だけに頼らないでください。生成されたファイルを開いて寸法を確認します。以下の任意のチェッカーは両方のフォルダを読み取り、寸法を報告します。書き込み処理は行いません。
from pathlib import Path
from PIL import Image
SOURCE = Path("outputs") / "image_resize_demo"
OUTPUT_DIR = Path("outputs") / "image_resize_result"
for source_path in sorted(SOURCE.glob("*.png")):
output_path = OUTPUT_DIR / source_path.name
with Image.open(source_path) as original, Image.open(output_path) as resized:
print(
f"{source_path.name}: "
f"original={original.size}, output={resized.size}"
)
| 症状 | 確認すること |
|---|---|
| ModuleNotFoundError: No module named PIL | python -m pip install Pillow を使い、スクリプトを実行するのと同じ Python 環境に Pillow をインストールします。 |
| FileNotFoundError | SOURCE とターミナルの現在の作業ディレクトリを確認します。この例では、先に合成セットアップスクリプトを実行してください。 |
| FileExistsError | 出力フォルダがすでに存在します。自動上書きを許可せず、内容を確認して新しい保存先を選択します。 |
| Image cannot be identified | 拡張子は対応形式に見えても、内容が破損しているか、実際には画像ではない可能性があります。失敗したバッチは未完了として扱います。 |
| Unexpected rotation | orientation metadata と pixel orientation が異なる場合があります。リサイズ前に ImageOps.exif_transpose を使用していますが、重要な画像は目視でも確認してください。 |
| Unexpected output file size in bytes | pixel 寸法だけでは圧縮後のファイルサイズは決まりません。形式、画像内容、メタデータ、JPEG quality も影響します。 |
一部のファイルを書き込んだ後に処理が失敗しても、スクリプトはその部分的な結果を削除しません。調査のために保存先を残すか、問題を修正した後で新しい出力ディレクトリを使用してください。
この例では、1つのフォルダ直下にあるファイルだけを扱い、PNG, JPEG, WebP 拡張子をサポートします。ネストされたディレクトリ構造の再現、アニメーションフレームごとのリサイズ、すべての形式のメタデータ保持、カラーマネジメント情報が完全に同じ状態で残ることは保証しません。
リサンプリングは pixel データを変更します。画像の寸法が同じままでも、開いて保存することでエンコード済み bytes やメタデータが変化する場合があります。JPEG 出力は非可逆であり、このスクリプトでは JPEG コピーを quality 90 で保存します。元の JPEG bitstream や元の圧縮設定を保持するものではありません。
int 計算では小数寸法を切り捨てます。そのため任意の元サイズでは、画像寸法を整数にする必要があることから、結果の pixel 比率が数学的な比率とわずかに異なる場合があります。このガイドの合成寸法は割り切れるため、4つの想定結果に曖昧な丸めはありません。
2026-09-20 · 手作業で確認した例 · 対象: Python 3.12 · Pillow 必須 · 未実行
説明と例は独自に作成しました。関連する動作や概念は、以下の公式資料で確認できます。