[Из песочницы] Порядок в фотоархиве средствами powershell

?v=1

Привет, Хабр!

На днях возникла типовая задача — помочь знакомой превратить гору фотографий в упорядоченную иерархию. Всё бы ничего, но гор фотографий не одна, а две — на Mac и на ноуте под Win10. В поисках решения, наткнулся на несколько сценариев для linux, а вот чего-то такого кроссплатформенного найти не удалось. Будем писать сами — прошу под кат.
Итак, нам нужно автоматизировать что-то, в том числе в Windows. Говорим автоматизация, подразумеваем Powershell — и к моему огромному счастью, Microsoft не так давно сделала его кроссплатформенным. Им и будем пользоваться.

Поскольку у фотографий есть обязательный общий признак, дата создания, иерархию будем строить на его основе в следующем порядке: корень архива → год → месяц → день. По ходу действия:

  1. Создаём корневой каталог,
  2. Получаем список файлов,
  3. Для каждого файла определяем дату создания из EXIF и
    • Дополняем структуру каталогов, если необходимо,
    • Перетаскиваем файл.


Звучит несложно, поехали.

Поскольку, пользователь неквалифицирован, логично максимально избавить его от работы в терминале. Пусть скрипт действует в своей директории и вложенных, пользователю останется только положить его в корневую папку и потом перетащить в окно терминала powershell:

[CmdletBinding()]
$curDir=$MyInvocation.MyCommand.Path | Split-Path -Parent


Проверяем наличие exiftool.

if (!(ExifTool)) {
    Write-Host "Install exiftool first!" -ForegroundColor Red -BackgroundColor Black
}


Обозначаем интересующие нас расширения и получаем список файлов, с которыми будем работать. Выбираем только те, которые имеют необходимое расширение и ещё не отсортированы.

Создаём корень нашей будущей структуры:

$ExtList = "arw,jpg,jpeg,NEF"
$FileList = Get-ChildItem $curDir -Recurse | `
    Where-Object {(($ExtList.ToUpper() -match $_.Name.Split('.')[-1]) `
        -or ($ExtList.ToLower() -match $_.Name.Split('.')[-1])) `
        -and ($_.FullName -notmatch "Ordered")
    }

# Define & сreate archive root folder
$ArchiveRoot = Join-Path -Path $curDir -ChildPath "Ordered"
if (!(Test-Path $ArchiveRoot)) {
    New-Item -ItemType Directory -Path $ArchiveRoot
}


Так как не все файлы джпеги могут иметь нужную информацию в EXIF (например, после пересохранения из соцсетей), создаём для них отдельный каталог. Их судьбу пользователь будет решать отдельно:

$ChaosRoot = Join-Path -Path $curDir -ChildPath "Chaos"
if (!(Test-Path $ChaosRoot)) {
    New-Item -ItemType Directory -Path $ChaosRoot
}


Для каждого файла из тех, которые разбираем, делаем следующее.

Определяем дату создания с помощью exiftool и разбиваем её на три составляющих: год, месяц, день. Так как exiftool поддерживает экспорт в csv, а powershell умеет с csv работать:

foreach ($File in $FileList) {
    $FileDateString = $null
    $FileDate = $null
    try {
        $FileDateString = ConvertFrom-Csv (ExifTool.exe $File.FullName -CSV) | `
            Select-Object -ExpandProperty CreateDate -ErrorAction Stop    
    }
    catch {
        Move-Item -Path $File.FullName -Destination $ChaosRoot -Force
        continue
    }
    
    $FileDate = $FileDateString.Split(" ")[0].Split(":")


Если нужный атрибут получить удалось, тогда разбиваем его на отдельные значения года, дня и месяца и идём далее, а если нет — нет, неопознанный файл сразу летит в хаос.

Итак, дата получена. Создаём каталог, ну и перетаскиваем файл:

$YearPath = Join-Path $ArchiveRoot $FileDate[0]
$MonthPath = Join-Path $YearPath $FileDate[1]
$FileDest = Join-Path $MonthPath $FileDate[2]
if (!(Test-Path $FileDest)) {
   New-Item -ItemType Directory -Path $FileDest
}
Move-Item -Path $File.FullName -Destination $FileDest -Force
}


Ну и не забываем прибрать за собой мусор, удалив уже ненужные пустые каталоги:

$AllDirsAtFinal = Get-ChildItem -Path $curDir -Recurse -Force | Where-Object {$_.PSIsContainer -eq $true} 
foreach ($Dir in $AllDirsAtFinal) {
    $DirLenght = Get-ChildItem -Path $Dir.FullName -Recurse -Force -ErrorAction SilentlyContinue | `
        Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue
    if ($null -eq $DirLenght.Sum) {
        Remove-Item $Dir.FullName -Recurse -Force -ErrorAction SilentlyContinue
    }
}


Вот, в общем-то, и всё.

Для работы понадобятся:

PowerShell для Mac
ExifTool by Phil Harvey

© Habrahabr.ru