PowerShellメモ　画像のExif情報を読み込む

# 概要
画像のExif情報を取得するサンプル。

# コード

## 例1. タグID一覧
JPEG画像のExifタグIDを取得する。

```PowerShell:例1
Add-Type -AssemblyName System.Drawing

# 画像読み込み
$img = New-Object Drawing.Bitmap("C:\Work\hoge.jpg") # フルパスで指定
# ExifタグID一覧を表示
$img.PropertyItems | Select-Object Id | Sort-Object {[int]$_.Id}
# 以下、実行結果
   Id
   --
    1
    2
    3
    4
  271
  272
  274
  282
  283
  296
  305
  531
20545
  :
```

```PowerShell
# 後始末
$img.Dispose()
$img = $null
```

## 例2. 撮影日
JPEG画像のExif情報から撮影日を取得する。

```PowerShell:例2
Add-Type -AssemblyName System.Drawing

# 画像読み込み
$img = New-Object Drawing.Bitmap("C:\Work\hoge.jpg")
# オリジナル画像データの生成日時を取得
$byteAry = ($img.PropertyItems | Where-Object{$_.Id -eq 36867}).Value
# 取得した日時を表示
[System.Text.Encoding]::ASCII.GetString($byteAry) # バイト配列を文字列に変換
# 実行結果
2016:06:04 09:34:44
```

```PowerShell
# 後始末
$img.Dispose()
$img = $null
```

## 例3. 実用サンプル
画像ファイルと動画ファイルを一括で日付つきのファイル名にリネームする。
JPEG画像の場合はExif情報から撮影日を取得する。
デジカメ・スマホ等で撮った画像・動画の整理に。

```PowerShell:例3
Add-Type -AssemblyName System.Drawing

if ((Test-Path Variable:\Exif_DateTimeOriginal) -eq $false)
{
    # Exifタグ：オリジナル画像データの生成日時
    New-Variable -Name Exif_DateTimeOriginal -Value 36867 -Option ReadOnly
}

<#
.SYNOPSIS
    指定フォルダに存在しないファイル名を取得

.PARAMETER filePath
    ファイルのパス

.PARAMETER baseFileName
    ファイル名先頭部分

.PARAMETER ext
    拡張子（ピリオド付き）

.OUTPUTS
    指定フォルダにまだ存在しないファイル名を返す。
    ファイル名取得失敗時はnullを返す。
#>
function GetUnusedFileName(
            [Parameter(Mandatory)][string]$filePath,
            [Parameter(Mandatory)][string]$baseFileName,
            [Parameter(Mandatory)][string]$ext)
{
    # ファイル名の先頭部分組み立て
    [string]$pathHead = Join-Path -Path $filePath -ChildPath ($baseFileName + "_")

    # 「ファイル名_連番.拡張子」の形式で連番を1～999までで未使用のものを探す
    for ([int]$i = 1; $i -le 999; $i++)
    {
        # 完全なファイルパス組み立て
        [string]$fullFilePath = $pathHead + $i.ToString("000") + $ext
        # ファイル存在チェック
        if ((Test-Path $fullFilePath) -eq $false)
        {
            # 見つからなかったのでこのファイル名を使用する
            return Split-Path $fullFilePath -Leaf
        }
    }

    # 取得失敗
    return $null
}

<#
.SYNOPSIS
    JPEG画像ファイルをリネーム

.PARAMETER fileItem
    ファイルオブジェクト

.PARAMETER ext
    リネーム後の拡張子（デフォルト ".jpg"）
#>
function RenameJpegFile(
            [Parameter(Mandatory)]$fileItem,
            [string]$ext = ".jpg")
{
    # ファイル更新日を取得
    $fileDate = $fileItem.LastWriteTime

    $img = New-Object Drawing.Bitmap($fileItem.FullName)

    # Exif情報取得
    [byte[]]$byteAry = ($img.PropertyItems | Where-Object{$_.Id -eq $Exif_DateTimeOriginal}).Value

    # 取得成功？
    if ($byteAry -ne $null)
    {
        # 「yyyy:MM:dd HH:mm:ss」 → 
        # 「yyyy/MM/dd HH:mm:ss」になるよう年月日の区切りを「/」で上書き
        $byteAry[4] = 47
        $byteAry[7] = 47

        # バイト配列を文字列に変換
        [string]$dateStr = [System.Text.Encoding]::ASCII.GetString($byteAry)

        # Exif情報の日付を設定
        $fileDate = [datetime]$dateStr
    }
    $img.Dispose()
    $img = $null

    # 新しいファイル名の先頭部分組み立て
    [string]$fileNameHeader = "img" + $fileDate.ToString("yyMMdd_HHmmss")
    # 新しいファイル名取得
    [string]$newFileName = GetUnusedFileName $fileItem.DirectoryName $fileNameHeader $ext

    if ($newFileName -ne $null)
    {
        # リネーム
        Write-Host ($fileItem.Name + " -> " + $newFileName) -ForegroundColor DarkGray
        Rename-Item -LiteralPath $fileItem.FullName -NewName $newFileName
    }
}

<#
.SYNOPSIS
    MOV動画ファイルをリネーム

.PARAMETER fileItem
    ファイルオブジェクト

.PARAMETER ext
    リネーム後の拡張子（デフォルト ".mov"）
#>
function RenameMovFile(
            [Parameter(Mandatory)]$fileItem,
            [string]$ext = ".mov")
{
    # ファイル更新日を取得
    $fileDate = $fileItem.LastWriteTime

    # 新しいファイル名の先頭部分組み立て
    [string]$fileNameHeader = "mov" + $fileDate.ToString("yyMMdd_HHmmss")
    # 新しいファイル名取得
    [string]$newFileName = GetUnusedFileName $fileItem.DirectoryName $fileNameHeader $ext

    if ($newFileName -ne $null)
    {
        # リネーム
        Write-Host ($fileItem.Name + " -> " + $newFileName) -ForegroundColor DarkGray
        Rename-Item -LiteralPath $fileItem.FullName -NewName $newFileName
    }
}

<#
.SYNOPSIS
    画像・動画ファイルをリネーム

.DESCRIPTION
    【概要】
    引数のファイルパス(ワイルドカード指定可)から
    特定の画像と動画ファイル(*.jpg, *.jpeg, *.mov)をリネームする。
    下層のフォルダは見ない。
    【説明詳細】
    jpg画像(*.jpg, *.jpeg)の場合、
        Exifタグから日付を取得し、ファイル名に使用する。
        取得できない場合はファイルの更新日を使用し、以下のフォーマットでリネームする。
        "img" + yyMMdd_HHmmss_ + 連番3桁 + ".jpg"
        例)
            img161122_134055_001.jpg
    動画(*.mov)の場合、
        ファイルの更新日を使用し、以下のフォーマットでリネームする。
        "mov" + yyMMdd_HHmmss_ + 連番3桁 + ".jpg"
        例)
            mov161122_134055_001.mov
    その他のファイルの場合、
        何もしない。

.PARAMETER wildcardFilePath
    対象ファイルパス（ワイルドカード指定可）

.EXAMPLE
    RenameMediaFiles .\*.mov
    カレントディレクトリの拡張子movのファイルを処理する
    
.EXAMPLE
    RenameMediaFiles C:\Work\IMGFILE*.*
    C:\WorkにあるIMGFILEで始まる全てのファイルを処理する
#>
function RenameMediaFiles([Parameter(Mandatory)][string]$wildcardFilePath)
{
    # ディレクトリは除外してファイルだけを処理する
    Get-ChildItem -Path $wildcardFilePath |
        Where-Object { -not $_.PsIsContainer } |
        ForEach-Object {
            $fileItem = $_
            [string]$ext = $fileItem.Extension.ToLower()
            switch ($ext)
            {
                ".jpg"  { RenameJpegFile $fileItem; break }
                ".jpeg" { RenameJpegFile $fileItem; break }
                ".mov"  { RenameMovFile $fileItem; break }
            }
        }
    Write-Host "処理完了" -ForegroundColor Green
}
```

```PowerShell:例3の実行方法
# (1) 例3の関数群を読み込む
# (2) カレントディレクトリを対象ファイルのある場所に移動。(3)でフルパス指定でも可
cd C:\Work\RenameTmp
# (3) リネーム実行
RenameMediaFiles .\MEDIAFILE*.*
```
実行結果の例
![ps09.png](https://qiita-image-store.s3.amazonaws.com/0/142879/e000077e-5835-4dc2-5e26-ec6a32615afa.png)



# 動作確認した環境
* PowerShell V4 (Windows 8.1)
* PowerShell V5 (Windows 10)