📸

PowerShell で raw データの撮影日時を読み取る力業

3 min read

撮り溜めた Canon の raw データを整理するにあたり、各写真の撮影日時を取得してみようと思います。

raw データの中身を見る

有志による CR2ファイルの仕様解析 によると、ファイル先頭部分に各種のメタ情報が記述されているようです。

PowerShell では Format-Hex コマンドレットでファイルを16進数ダンプ表示できます。とりあえず先頭の512バイトを見てみると……

所々に見覚えのある文字列が出現します。

手元のファイルをひと通り見たところ、どれも先頭から324バイト行ったところから yyyy:MM:dd hh:mm:ss 形式でタイムスタンプが保存されているようです。ということで、この部分を機械的に取り出してみましょう。

手元の機種以外では未検証です。
機種によって上記の数値は異なる可能性があるのでご注意ください。

バイトを計算して各エントリーをループする方式もあるようなのですが( 参考 )、 PowerShell で実装するには力が及ばず……

詳しい方のアドバイスお待ちしております!

コード

function Get-CR2Timestamp {
    param (
        [parameter(ValueFromPipeline = $true)]$inputObj
    )
    begin {}
    process {
        $fileObj = Get-Item $inputObj
        if ($fileObj.Extension -ne ".CR2") {
            return [PSCustomObject]@{
                Name = $fileObj.Name
                Timestamp = ""
            }
        }
        $bytes = Get-Content $fileObj.FullName -AsByteStream -TotalCount (324 + 19) | Select-Object -Last 19
        $decoded = [System.Text.Encoding]::ASCII.GetString($bytes)
        $timestamp = ($decoded -match "^\d{4}:\d{2}:\d{2} \d{2}:\d{2}:\d{2}$")?
            "{0}00" -f ($decoded -replace ":" -replace " ", "_" -replace "^(\d{4})", '$1_') :
            ""
        return [PSCustomObject]@{
            Name = $fileObj.Name
            Timestamp = $timestamp
        }
    }
    end {}
}

実行イメージ↓

> Get-CR2Timestamp .\IMG_6000.CR2

Name         Timestamp
----         ---------
IMG_6000.CR2 2021_0203_23245000

パイプ経由でもファイルを渡すこともできます↓。

> ls "*cr2" | Get-CR2Timestamp

Name         Timestamp
----         ---------
IMG_6000.CR2 2021_0203_23245000

リネーム関数

リネーム処理もコマンドレット化してみました。
下記コマンドレットに CR2 ファイルをパイプで渡すと一括でファイル名の先頭にタイムスタンプを挿入します。

function Rename-CR2Timestamp {
    $input | Where-Object Extension -eq ".CR2" | ForEach-Object {
        $itemName = $_.Name
        $timestamp = ($_ | Get-CR2Timestamp).Timestamp
        if ($timestamp) {
            $newName = "{0}_{1}" -f $timestamp, $itemName
            try {
                "RENAMED: '{0}' => '{1}'" -f $itemName, $newName | Write-Host -ForegroundColor Cyan
                $_ | Rename-Item -NewName $newName -ErrorAction Stop
            }
            catch {
                "ERROR!: failed to rename '{0}'!" -f $itemName | Write-Error
            }
        }
    }
}

各ファイルの最終更新日時(LastWriteTime)をみるという方法もありますが、微妙に違う値に書き換わっていることがあったのでこの方法を採用しました。