2012年3月12日 星期一

使用 Windows PowerShell 將照片依照拍攝日期更名,並分置於不同日期資料夾歸檔

我在整理數位相片的習慣,一向是依拍照日期分置各自資料夾整理。
數位相機使用頻繁以後,要處理的檔案大增,雖然市面上有許多軟體可以方便做到分資料夾,但是不能完全自訂總是不夠滿意,幸好有PowerShell這個方便的工具,讓我省下許多時間。

以往都是將數位相片在記憶卡中就分好資料夾,並沒有特別變更檔名,但是近來相機數量逐漸增加,而且各大廠四位數序號命名早就拍超過並重新歸零數次,於是前陣子開始再次去閱讀PowerShell的資料,把更名跟分資料夾一次完成。


純粹分置資料夾的指令我愛用了好幾年,一開始是指定位置,我這個"PhotoArrange.ps1"是這樣寫的:
$dir = 'S:\DCIM\102CANON'
cd $dir
$files = dir -path $dir | ?{ !$_.PSIsContainer }
$files | %{ [void] (New-Item -ItemType Directory `
    $_.LastWriteTime.ToString("yyyyMMdd") -ErrorAction SilentlyContinue); $_ }`
    | %{ Move-Item $_.PSPath -dest $_.LastWriteTime.ToString("yyyyMMdd") }
因為都是在記憶卡就整理,所以單純使用最後修改日期,不去調用EXIF拍照資訊,省得麻煩,缺點是有時候要去改路徑。

後來拍到相機自動新建資料夾,而且多了其他台相機,所以就把目標資料夾路徑去掉,改在桌面捷徑中修改,比較方便。
這個"PhotoArrangeWithoutPath.ps1"是這樣:
$files = dir | ?{ !$_.PSIsContainer }
$files | %{ [void] (New-Item -ItemType Directory `
    $_.LastWriteTime.ToString("yyyyMMdd") -ErrorAction SilentlyContinue); $_ }`
    | %{ Move-Item $_.PSPath -dest $_.LastWriteTime.ToString("yyyyMMdd") }
然後在桌面的捷徑中,
設定目標為「%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe -noexit D:\路徑\PhotoArrangeWithoutPath.ps1」、
設定開始位置為「S:\DCIM\102CANON」(待整理檔案的路徑位置)。

以上指令所有檔案類型一視同仁都處理,若是讀不到路徑(記憶卡沒插入),會把桌面的所有東西依日期分資料夾擺喔!

題外話,在網路上看到有人把「PsIsContainer」設別名(Alias):
$IsDir = {$_.PsIsContainer}
$IsFile = {!$_.PsIsContainer}
dir | Where $IsDir
dir | Where $IsFile
以避免重複輸入落落長。
重點是聽說PowerShell 第3版有內建:
dir -directory
dir -ad
dir -file
dir -af
這樣真的有比較方便。


回到主題,為了要依照日期變更檔名,想說要做就弄好一點,JPEG檔就去抓EXIF資訊來改,其它檔案再以老方法取修改時間。
本來想用jHead這個以指令惡搞JPEG檔頭資訊的強大好物,搭配PowerShell來整理。
沒想網路上撈到有前人已經以PowerShell寫好了,功能跟我要的幾乎一樣!還加了執行說明畫面,真是棒!
但是小修改還是必須的,我把它的新資料夾路徑從年月改成年月日、加入保留舊檔名及是否使用序號的選項,並把注解及執行畫面都改成中文。
它這個指令調用JamesBrundage的PowerShellPack模組的PSImageTools以讀取EXIF,因為用不到相片EXIF外其他功能,所以另外找了James O'Neill修改自PSImageTools的增強版PowerShell Image模組。
試了一下跟我說有些東西找不到,看來是要先裝PowerShellPack,所以還是乖乖用回PowerShellPack。
這個模組說明要V2以上才能用,WIN7內建的就是囉,雖然執行檔路徑是在1.0,但是在PS畫面下輸入"get-host",可以確認已經是2.0了。

模組服用方式:
安裝前先在PowerShell畫面輸入「$Env:PsModulePath」,看看模組捷徑指向何方,待會要把模組檔放在裡面。
一般放在「%SystemRoot%\system32\WindowsPowerShell\v1.0\Modules\」,則該電腦所有使用者都能使用;
如果沒權限,就去打開「我的文件夾」,放在裡面的「WindowsPowerShell」的「Modules」資料夾裡。
要用的時候,輸入指令「Import-Module 模組名稱」來呼叫模組即可。
上述的PowerShellPack模組是自動安裝檔,會裝在安裝者的"我的文件夾"裡,別的使用者要調用必須自行安裝,或複製一份到system32去大家共用。

安裝好模組後,接下來我的這個"PhotoRenameAndFiling.ps1"指令是這樣寫的:

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#  原作:Rikard Ronnkvist / snowland.se || 修改/中文註解:Tony Huang / tonysnote.blogspot.com
#
#  以下將使用EXIF資訊中的拍照日期,或使用最後修改日期,將JPG及其它檔案更名,並分置於各自子資料夾內。
#  檔案將被更名並放置於:
#    \some path\YYYYMMDD\YYYYMMDDHHMMSS_00.JPG
#               ^^^^^^^^ - 可選擇是否歸檔,若要則將"createSubdir"設為"True"。
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#  可輸入[$basePath = convert-path ~]選定目前(捷徑設定)路徑,或自設路徑例如:[$basePath = "F:\Pictures\Import dir"]。
$basePath = [Environment]::CurrentDirectory
#  $basePath = "F:\Pictures\Import dir"

#  若僅更名不歸檔,可將"$createSubdir"設為"$False"。
$createSubdir = $True
#  $createSubdir = $False

#  測試模式,若要測試可將"$testMode"設為"$True"。
$testMode = $False
#  $testMode = $True

#  日期時間後保留舊檔名與否。
$keepOldName = $True
#  $keepOldName = $False

#  自動加序號與否。
#  $addString = $True
$addString = $False
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# 呼叫PowerShellPack模組[http://archive.msdn.microsoft.com/PSImage],為自動安裝檔。
# 若要讓電腦中所有使用者使用,將"我的文件夾\WindowsPowerShell\Modules\"內所有內容,複製到"%SystemRoot%\system32\WindowsPowerShell\v1.0\Modules\"即可。
Import-Module PowerShellPack
 
# 加上 \* 以搜尋路徑下所有檔案。
$searchPath = $basePath + "\*"
 
# 搜尋待處理檔案。
Write-Host "             搜尋: " -ForegroundColor DarkGray -NoNewline
Write-Host $basePath -ForegroundColor Yellow
 
$allFiles  = Get-ChildItem -Path $searchPath -Include *.JPG,*.MOV,*.THM,*.RAW,*.AVI -Exclude folder.jpg,*.ini
 
Write-Host "             找到: " -ForegroundColor DarkGray -NoNewline
Write-Host $allFiles.Count -ForegroundColor Yellow -NoNewline
Write-Host " 個檔案" -ForegroundColor DarkGray
 
$fNum = 0
# 依序處理所有檔案。
foreach ($file in $allFiles )
{
    $fNum++
    # 若是jpg檔,使用exif資訊,否則使用最後修改日期。
    if ($file.Extension -eq ".JPG") {
        $imgInfo = $file | Get-Image | Get-ImageProperty
        $fileDate = $imgInfo.dt
    } else {
        $fileDate = $file.LastWriteTime
    }
 
    if ($createSubdir -eq $True) {
        # 設定新資料夾路徑。
        $fileDir = $basePath + "\" + $fileDate.ToString("yyyyMMdd")
 
        # 檢查新資料夾路徑是否已存在,若無則新建。
        if (!(Test-Path($fileDir))) {
            # 新建子資料夾路徑。
            if ($testMode -ne $True) {
                $newDir = New-Item -Type directory -Path $fileDir
                Write-Host "             建立: " -ForegroundColor DarkGray -NoNewline
                Write-Host $fileDir -ForegroundColor Red
            }
        }
    } else {
        # 若檔頭選擇僅更名不歸檔,則將檔案留在目前(捷徑設定)路徑或檔頭自設之路徑,不依日期歸檔。
        $fileDir = $basePath
    }
 
    # 設定好新的檔名路徑,並於while迴圈開頭時檢查。
    $newPath = $file.Fullname

   # 若檔頭選擇保留舊檔名,則將其添加於日期時間之後。
    if ($keepOldName -eq $True) {
        $oldName = "-" + $file.Name.Remove($file.Name.Length - $file.Extension.Length)
    } else {
        $oldName = ""
    }   

   # 若檔頭選擇添加序號,則添加於主檔名最後。
    if ($addString -eq $True) {
        $i = 0
        while (Test-Path $newPath) {
            # 設定有序號之新檔名。
            $newPath = $fileDir + "\" + $fileDate.ToString("yyyyMMddHHmmss") + $oldName + "_" + $i.ToString("00") + $file.Extension
            $i++
        }
    } else {
            # 設定無序號之新檔名
            $newPath = $fileDir + "\" + $fileDate.ToString("yyyyMMddHHmmss") + $oldName + $file.Extension
    }

 
    # 顯示執行資訊。
    Write-Host $fNum.ToString().PadLeft(4) -ForegroundColor DarkYellow -NoNewline
    Write-Host " / " -ForegroundColor DarkGray -NoNewline
    Write-Host $allFiles.Count.ToString().PadRight(4) -ForegroundColor Yellow -NoNewline
    Write-Host "  移動: " -ForegroundColor DarkGray -NoNewline
    Write-Host $file.Name -ForegroundColor Cyan -NoNewline
    Write-Host " 到 " -ForegroundColor DarkGray -NoNewline
    Write-Host $newPath -ForegroundColor Green
 
    # 更名並移動檔案。
    if ($testMode -ne $True) {
        Move-Item $file.Fullname $newPath -ErrorAction SilentlyContinue
    }
}

使用時一樣在桌面設定好捷徑即可使用,可針對不同相機記憶卡設不同捷徑。

至此大功告成!!

沒有留言:

張貼留言