🐥

大量のiPhone写真をネットプリントするために、HEIC→JPG変換を自動化した話

に公開

はじめに

先日、溜まりに溜まった家族写真を「そろそろ印刷しないと…」と思い立ちました。
対象は iPhone とデジカメ で撮影した写真で、合計すると数百枚あります。

せっかくなのでネットプリントで一括発注しようとしたところ、
iPhoneの写真がHEIC形式のため、そのままでは印刷できない という問題にぶつかりました。

ネットプリントは、ほとんどの業者が JPG形式のみ対応 しており、
HEIC形式の写真は事前に変換する必要があります。

変換サービスや専用ソフトもいくつかありますが、

  • 枚数に上限がある
  • 有料プランが前提
  • 写真を外部サービスにアップロードする点が気になる

といった理由から、今回は使いませんでした。

そこで本記事では、
HEIC形式の写真をJPGに一括変換する処理を、自力で自動化したときの記録をまとめます。


まずは写真を一箇所に集約する

大量の写真を扱う前に、まずやったのは整理です。

  • Googleフォトを有料契約(2TB)
  • iPhoneの写真を丸ごとバックアップ
  • デジカメ(Z50)の写真もPC経由でGoogleフォトへ追加
  • 印刷対象が分かりやすいよう 「印刷用アルバム」 を作成
  • そのアルバムを妻とファミリー共有
  • 「印刷したい写真はここに入れてね」という運用に

結果、最終的に 約500枚 になりました。


実店舗プリントは高い、ネットプリントは安い

これまでは、

  • 近所のFUJIFILM
  • カメラのキタムラ

などで印刷していましたが、
枚数が増えるとそれなりにお値段が張ります。

今回はコストを抑えたかったので、
ネットプリント を使うことにしました。

ネットプリントの比較サイト等色々ありますが、今回はDPE宅配便という業者に依頼することにしました。

使ってみた感想

  • ブラウザ経由でアップロード可能
  • 500枚まとめてアップしても途中で落ちない
  • 動作はかなり軽快

ただし、一点だけ問題がありました。


iPhoneの写真、HEIC問題

ネットプリントのアップロード形式は JPGのみ
iPhoneで撮った写真は HEIC形式 なので、そのままではアップロードできません。

選択肢として考えたこと

  • HEIC → JPG 変換ソフト
    → だいたいシェアウェア。最初の数枚だけ無料というものが多く、今回は枚数的に現実的ではありませんでした。
  • HEIC → JPG 変換してくれるサイト
    → プライバシーとかガバガバで怖い。不安。口コミ関係なく却下。
  • 自作スクリプトで変換する
    → どうやらWindowsストアに「HEIF画像表示オプション」なる拡張機能があり、これでHEIC形式が読めるようになるらしい。これでいく。

実際にやってみたが…

HEIC形式の画像を1枚ずつ、

  • 画像を開く
  • 「名前をつけて保存」
  • JPG形式を選んで保存
    という手順が必要でした。

当然ながら、これを数百枚やるのは現実的ではありません。


ということで自動化する

結論として、
スクリプトを書いて一括変換することにしました。

  • フォルダを指定
  • HEICをまとめてJPGに変換
  • 別フォルダに吐き出す

という単純なものです。

スクリプト

※ 細かいエラーハンドリングなどはしていない「自分用の雑スクリプト」です。

# HEIC → JPG 一括変換スクリプト

param(
  [Parameter(Mandatory=$true)][string]$InDir,
  [Parameter(Mandatory=$true)][string]$OutDir,
  [int]$Quality = 92,
  [switch]$Recurse
)

# WPFを使う(HEICデコードはWindowsのコーデックに依存)
Add-Type -AssemblyName PresentationCore

if (!(Test-Path $InDir)) { throw "入力フォルダが存在しません: $InDir" }
if (!(Test-Path $OutDir)) { New-Item -ItemType Directory -Path $OutDir | Out-Null }

# PowerShell 5.1互換:三項演算子を使わない
if ($Recurse) {
  $files = Get-ChildItem -Path $InDir -Filter *.heic -File -Recurse
} else {
  $files = Get-ChildItem -Path $InDir -Filter *.heic -File
}

$failed = New-Object System.Collections.ArrayList
$done = 0
$total = $files.Count

foreach ($f in $files) {
  try {
    $inPath  = $f.FullName

    # サブフォルダ構成を保ちたい場合(Recurse時)は相対パスを作る
    if ($Recurse) {
      $rel = $f.FullName.Substring($InDir.Length).TrimStart('\')
      $subDir = Split-Path $rel -Parent
      $outDir2 = if ([string]::IsNullOrEmpty($subDir)) { $OutDir } else { Join-Path $OutDir $subDir }
      if (!(Test-Path $outDir2)) { New-Item -ItemType Directory -Path $outDir2 -Force | Out-Null }
      $outPath = Join-Path $outDir2 ($f.BaseName + ".jpg")
    } else {
      $outPath = Join-Path $OutDir ($f.BaseName + ".jpg")
    }

    # 画像読み込み(HEIF拡張が入っていればここで読める)
    $stream = [System.IO.File]::OpenRead($inPath)
    try {
      $bmp = New-Object System.Windows.Media.Imaging.BitmapImage
      $bmp.BeginInit()
      $bmp.CacheOption = [System.Windows.Media.Imaging.BitmapCacheOption]::OnLoad
      $bmp.StreamSource = $stream
      $bmp.EndInit()
      $bmp.Freeze()
    } finally {
      $stream.Dispose()
    }

    # JPEGエンコード
    $encoder = New-Object System.Windows.Media.Imaging.JpegBitmapEncoder
    $encoder.QualityLevel = [Math]::Max(1, [Math]::Min(100, $Quality))
    $encoder.Frames.Add([System.Windows.Media.Imaging.BitmapFrame]::Create($bmp))

    $outStream = [System.IO.File]::Open($outPath, [System.IO.FileMode]::Create)
    try {
      $encoder.Save($outStream)
    } finally {
      $outStream.Dispose()
    }

    # タイムスタンプ合わせ(任意)
    (Get-Item $outPath).CreationTime  = $f.CreationTime
    (Get-Item $outPath).LastWriteTime = $f.LastWriteTime

    $done++
    if ($done % 10 -eq 0 -or $done -eq $total) {
      Write-Host ("{0}/{1} 完了" -f $done, $total)
    }
  }
  catch {
    [void]$failed.Add($f.FullName + " / " + $_.Exception.Message)
    Write-Warning ("NG: {0} / {1}" -f $f.FullName, $_.Exception.Message)
  }
}

if ($failed.Count -gt 0) {
  Write-Host "`n失敗一覧:" -ForegroundColor Yellow
  $failed | ForEach-Object { Write-Host $_ }
  exit 1
} else {
  Write-Host "`n全部完了" -ForegroundColor Green
}

動かしてみる。

Powershellを開き、以下のコマンドを実行する

powershell -ExecutionPolicy Bypass -File .\heicconv.ps1 `
  -InDir "C:\~略~\heic" `
  -OutDir "C:\~略~\heic\conv"

できた。


おわりに

技術的には大したことはしていませんが、
こういうちょっとした 「面倒くさい」を自力で潰すのは気持ちがいいですね。

同じように
「HEICが印刷できない…」
となっている人の参考になれば幸いです。

Discussion