453 lines
16 KiB
PowerShell
453 lines
16 KiB
PowerShell
############### GLOBAL ##############
|
|
$LogPath = "C:/Temp/OrgPhonePhotos.log"
|
|
|
|
############### MAIN ###############
|
|
# main function
|
|
#
|
|
$Main =
|
|
{
|
|
[CmdletBinding()]
|
|
Param(
|
|
[Parameter(Mandatory=$true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[string] $SourceFolder,
|
|
|
|
[Parameter(Mandatory=$true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[string] $DestFolder,
|
|
|
|
[Parameter(Mandatory=$true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[string] $FolderForDuplicates,
|
|
|
|
[Parameter(Mandatory=$true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[string] $LogFile
|
|
|
|
)
|
|
$header = "$("-"*80)`n`n$(Get-Date -UFormat ""%A %Y-%m-%d %T"")`n"
|
|
Write-Output $header | Tee-Object -FilePath $LogFile -Append
|
|
$LogPath = (Get-Item $LogFile).FullName
|
|
validatedest $SourceFolder $DestFolder $FolderForDuplicates
|
|
cleanfolder $SourceFolder
|
|
remove-emptyfolders $SourceFolder
|
|
$medialist=[System.Collections.Generic.List[object]]::new()
|
|
$Files_jpg = Get-ChildItem -Path $SourceFolder -File -Filter "*.jpg" -Recurse
|
|
$Files_raw = Get-ChildItem -Path $SourceFolder -File -Filter "*.cr?" -Recurse
|
|
$Files_mp4 = Get-ChildItem -Path $SourceFolder -File -Filter "*.mp4" -Recurse
|
|
ForEach ($File in $Files_jpg) {
|
|
$medialist.Add($File)
|
|
}
|
|
ForEach ($File in $Files_raw) {
|
|
$medialist.Add($File)
|
|
}
|
|
ForEach ($File in $Files_mp4) {
|
|
$medialist.Add($File)
|
|
}
|
|
foreach ($mediafile in $medialist) {
|
|
$xfoutput = @(C:\Tools\exiftool\exiftool.exe $mediafile.FullName)
|
|
$sourcefilefullname = $mediafile.FullName
|
|
# Write-Output $sourcefilefullname | Tee-Object -FilePath $LogPath -Append
|
|
$exiflist = @{}
|
|
foreach ($tagvalpair in $xfoutput) {
|
|
$arr=@($tagvalpair -split ":", 2)
|
|
$key=$arr[0] -replace ' ' -replace '/','_'
|
|
$value=$arr[1].Trim()
|
|
$exiflist[$key] = $value
|
|
}
|
|
# Write-Output "`n------ xfoutput ---------" $mediafile.Name $xfoutput
|
|
# Write-Output "`n------ exiflist ---------" $mediafile.Name $exiflist
|
|
$CreateDate = cv2datetime $exiflist['CreateDate']
|
|
$Date_TimeOriginal = cv2datetime $exiflist['Date_TimeOriginal']
|
|
$FileCreationDate_Time = cv2datetime $exiflist['FileCreationDate_Time']
|
|
$FileModificationDate_Time = cv2datetime $exiflist['FileModificationDate_Time']
|
|
$FileAccessDate_Time = cv2datetime $exiflist['FileAccessDate_Time']
|
|
$filenamedate = validatedate $mediafile.Name
|
|
$datestr = $null
|
|
$datetaken = getearliest $CreateDate $Date_TimeOriginal
|
|
if (-not($null -eq $datetaken)) {
|
|
$datestr = $datetaken.ToString('yyyyMMdd')
|
|
}
|
|
elseif (-not($null -eq $filenamedate)) {
|
|
Write-Output """$sourcefilefullname"" : File has no usable ExifData. Using date in filename." | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Yellow
|
|
$datestr = $filenamedate.ToString('yyyyMMdd')
|
|
}
|
|
else {
|
|
$earliest = getearliest $FileCreationDate_Time $FileModificationDate_Time $FileAccessDate_Time
|
|
$file_time = $earliest.ToString('yyyy-MM-dd HH:mm:ss')
|
|
Write-Output "Skipped ""$sourcefilefullname"" : File has no usable ExifData! Earliest file time is $file_time" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor DarkRed
|
|
}
|
|
$sourcefiledir = "$($mediafile.Directory)"
|
|
#Write-Output $time_arr
|
|
if (-not($null -eq $datestr)) {
|
|
#Write-Output "$($mediafile.Name) $datestr"
|
|
movetosubfolder "$($mediafile.Name)" "$datestr" "$sourcefiledir" "$DestFolder" "$FolderForDuplicates"
|
|
remove-emptyfolder "$sourcefiledir"
|
|
}
|
|
}
|
|
}
|
|
Function validatedate {
|
|
[CmdletBinding()]
|
|
Param(
|
|
[Parameter(Mandatory=$false)]
|
|
[string] $datetimestr
|
|
)
|
|
if (($null -eq $datetimestr) -or ($datetimestr -eq "")) {
|
|
return $null
|
|
}
|
|
if ($datetimestr.Length -le 13) {
|
|
return $null
|
|
}
|
|
$datestart = 0
|
|
$stryear = $datetimestr.substring($datestart,4)
|
|
if (($stryear -eq "IMG-") -or ($stryear -eq "VID-")){
|
|
$datestart = 4
|
|
$stryear = $datetimestr.substring($datestart,4)
|
|
if ($datetimestr.Length -le 17) {
|
|
return $null
|
|
}
|
|
}
|
|
$strmonth = $datetimestr.substring($datestart + 4,2)
|
|
$strday = $datetimestr.substring($datestart + 6,2)
|
|
$year = 0
|
|
$month = 0
|
|
$day = 0
|
|
if (-not [int]::TryParse($stryear, [ref] $year)) {
|
|
return $null
|
|
}
|
|
if (-not [int]::TryParse($strmonth, [ref] $month)) {
|
|
return $null
|
|
}
|
|
if (-not [int]::TryParse($strday, [ref] $day)) {
|
|
return $null
|
|
}
|
|
if ($year -lt 1 -or $month -lt 1 -or $day -lt 1) {
|
|
return $null
|
|
}
|
|
try {
|
|
$datetime =[DateTime]"$stryear-$strmonth-$strday"
|
|
}
|
|
catch {
|
|
return $null
|
|
}
|
|
$now = Get-Date
|
|
if ($datetime -gt $now) {
|
|
return $null
|
|
}
|
|
return $datetime
|
|
}
|
|
Function getearliest {
|
|
[CmdletBinding()]
|
|
Param(
|
|
[Parameter(Mandatory=$false)]
|
|
[string] $datetimestr1,
|
|
|
|
[Parameter(Mandatory=$false)]
|
|
[string] $datetimestr2,
|
|
|
|
[Parameter(Mandatory=$false)]
|
|
[string] $datetimestr3
|
|
)
|
|
$result = [long]::MaxValue
|
|
if (-Not(($null -eq $datetimestr1) -or ($datetimestr1 -eq ""))) {
|
|
$datetime1 = [DateTime]$datetimestr1
|
|
$time = $datetime1.Ticks
|
|
if ($time -lt $result) {
|
|
$result = $time
|
|
}
|
|
}
|
|
if (-Not(($null -eq $datetimestr2) -or ($datetimestr2 -eq ""))) {
|
|
$datetime2 = [DateTime]$datetimestr2
|
|
$time = $datetime2.Ticks
|
|
if ($time -lt $result) {
|
|
$result = $time
|
|
}
|
|
}
|
|
if (-Not(($null -eq $datetimestr3) -or ($datetimestr3 -eq ""))) {
|
|
$datetime3 = [DateTime]$datetimestr3
|
|
$time = $datetime3.Ticks
|
|
if ($time -lt $result) {
|
|
$result = $time
|
|
}
|
|
}
|
|
if ($result -eq [long]::MaxValue) {
|
|
return $null
|
|
}
|
|
return [DateTime]$result
|
|
}
|
|
Function cv2datetime {
|
|
[CmdletBinding()]
|
|
Param(
|
|
[Parameter(Mandatory=$false)]
|
|
[string] $datetimestr
|
|
)
|
|
if (($null -eq $datetimestr) -or ($datetimestr -eq "")) {
|
|
return $null
|
|
}
|
|
$time_arr = @($datetimestr -split " ", 2)
|
|
$datestr = $time_arr[0] -replace ":"
|
|
$datestr = $datestr.Trim()
|
|
if (($null -eq $datestr) -or ($datestr -eq "")) {
|
|
$datestr = ""
|
|
}
|
|
else {
|
|
$datestr = $time_arr[0] -replace ":","-"
|
|
}
|
|
$timestr = $time_arr[1] -replace ":"
|
|
$timestr = $timestr.Trim()
|
|
if (($null -eq $timestr) -or ($timestr -eq "")) {
|
|
$timestr = ""
|
|
}
|
|
else {
|
|
$timestr = $time_arr[1]
|
|
}
|
|
$result = "$datestr $timestr".Trim()
|
|
if ($result -eq "") {
|
|
return $null
|
|
}
|
|
try {
|
|
return [DateTime]$result
|
|
}
|
|
catch {
|
|
return $null
|
|
}
|
|
}
|
|
Function cleanfolder {
|
|
[CmdletBinding()]
|
|
Param(
|
|
[Parameter(Mandatory=$true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[string] $folder
|
|
)
|
|
$filelist = Get-ChildItem -Path $SourceFolder -File -Filter "ZbThumbnail.info" -Recurse
|
|
foreach ($file in $filelist) {
|
|
deletefile "$($file.FullName)"
|
|
}
|
|
$filelist = Get-ChildItem -Path $SourceFolder -File -Filter "Thumbs.db" -Recurse
|
|
foreach ($file in $filelist) {
|
|
deletefile "$($file.FullName)"
|
|
}
|
|
}
|
|
Function deletefile {
|
|
[CmdletBinding()]
|
|
Param(
|
|
[Parameter(Mandatory=$true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[string] $filepath
|
|
)
|
|
if(Test-Path -Path $filepath -PathType Leaf) {
|
|
Remove-Item $filepath
|
|
Write-Output "$filepath deleted!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Magenta
|
|
}
|
|
}
|
|
Function remove-emptyfolders {
|
|
[CmdletBinding()]
|
|
Param(
|
|
[Parameter(Mandatory=$true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[string] $folder
|
|
)
|
|
$folderlist = Get-ChildItem -Path $folder -Directory -Force -Recurse | Sort-Object FullName -Descending
|
|
foreach ($item in $folderlist) {
|
|
$folderdetail = $item.GetFileSystemInfos() | Measure-Object
|
|
If ($folderdetail.Count -eq 0) {
|
|
deletefolder "$($item.FullName)"
|
|
}
|
|
}
|
|
}
|
|
Function remove-emptyfolder {
|
|
[CmdletBinding()]
|
|
Param(
|
|
[Parameter(Mandatory=$true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[string] $folder
|
|
)
|
|
$count = (Get-ChildItem -Path $folder -Force | Measure-Object).Count
|
|
If ($count -eq 0) {
|
|
deletefolder "$folder"
|
|
}
|
|
}
|
|
Function deletefolder {
|
|
[CmdletBinding()]
|
|
Param(
|
|
[Parameter(Mandatory=$true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[string] $folderpath
|
|
)
|
|
if(Test-Path -Path $folderpath -PathType Container) {
|
|
Remove-Item $folderpath
|
|
Write-Output "$folderpath deleted!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor DarkMagenta
|
|
}
|
|
}
|
|
Function validatedest {
|
|
[CmdletBinding()]
|
|
Param(
|
|
[Parameter(Mandatory=$true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[string] $sourcefolder,
|
|
|
|
[Parameter(Mandatory=$true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[string] $destfolder,
|
|
|
|
[Parameter(Mandatory=$true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[string] $duplicatesfolder
|
|
|
|
)
|
|
if(-Not(Test-Path $sourcefolder)) {
|
|
Write-Output "Fatal: Source folder ""$sourcefolder"" does not exist!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
if(-Not(Test-Path $destfolder)) {
|
|
Write-Output "Fatal: Destination folder ""$destfolder"" does not exist!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
if(-Not(Test-Path $duplicatesfolder)) {
|
|
Write-Output "Fatal: Duplicates folder ""$duplicatesfolder"" does not exist!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
do {
|
|
$temp = $sourcefolder
|
|
$sourcefolder = $temp.TrimEnd('\').TrimEnd('/')
|
|
} while ($temp -ne $sourcefolder)
|
|
do {
|
|
$temp = $destfolder
|
|
$destfolder = $temp.TrimEnd('\').TrimEnd('/')
|
|
} while ($temp -ne $destfolder)
|
|
do {
|
|
$temp = $duplicatesfolder
|
|
$duplicatesfolder = $temp.TrimEnd('\').TrimEnd('/')
|
|
} while ($temp -ne $duplicatesfolder)
|
|
$sourcedir = Get-Item $sourcefolder
|
|
$destdir = Get-Item $destfolder
|
|
$duplicatesdir = Get-Item $duplicatesfolder
|
|
if ($sourcedir.FullName -eq $destdir.FullName) {
|
|
Write-Output "Fatal: Destination folder must not be the same as the source folder!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
if ($sourcedir.FullName -eq $duplicatesdir.FullName) {
|
|
Write-Output "Fatal: Duplicates folder must not be the same as the source folder!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
if ($duplicatesdir.FullName -eq $destdir.FullName) {
|
|
Write-Output "Fatal: Destination folder must not be the same as the duplicates folder!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
$folderlist = @(Get-ChildItem -Path $sourcedir -Directory -Recurse)
|
|
ForEach ($folder in $folderlist) {
|
|
if ($folder.FullName -eq $destdir.FullName) {
|
|
Write-Output "Fatal: Destination folder must not be a subfolder of the source folder!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
if ($folder.FullName -eq $duplicatesdir.FullName) {
|
|
Write-Output "Fatal: Duplicates folder must not be a subfolder of the source folder!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
}
|
|
$folderlist = @(Get-ChildItem -Path $destdir -Directory -Recurse)
|
|
ForEach ($folder in $folderlist) {
|
|
if ($folder.FullName -eq $sourcedir.FullName) {
|
|
Write-Output "Fatal: Source folder must not be a subfolder of the destination folder!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
if ($folder.FullName -eq $duplicatesdir.FullName) {
|
|
Write-Output "Fatal: Duplicates folder must not be a subfolder of the destination folder!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
}
|
|
$folderlist = @(Get-ChildItem -Path $duplicatesdir -Directory -Recurse)
|
|
ForEach ($folder in $folderlist) {
|
|
if ($folder.FullName -eq $sourcedir.FullName) {
|
|
Write-Output "Fatal: Source folder must not be a subfolder of the duplicates folder!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
if ($folder.FullName -eq $destdir.FullName) {
|
|
Write-Output "Fatal: Destination folder must not be a subfolder of the duplicates folder!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
}
|
|
}
|
|
Function movetosubfolder
|
|
{
|
|
[CmdletBinding()]
|
|
Param(
|
|
[Parameter(Mandatory=$true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[string] $mediafile,
|
|
|
|
[Parameter(Mandatory=$true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[string] $datestr,
|
|
|
|
[Parameter(Mandatory=$true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[string] $sourcefolder,
|
|
|
|
[Parameter(Mandatory=$true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[string] $destfolder,
|
|
|
|
[Parameter(Mandatory=$true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[string] $duplicates
|
|
)
|
|
$sourceitem="$sourcefolder/$mediafile"
|
|
# 4 = length of extension + minimum length of filename = 8+4+1
|
|
if ($mediafile.Length -le 4) {
|
|
Write-Output "Skipped ""$sourceitem"" : File name too short!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor DarkYellow
|
|
}
|
|
else {
|
|
$stryear=$datestr.substring(0,4)
|
|
$strmonth=$datestr.substring(4,2)
|
|
$strday=$datestr.substring(6,2)
|
|
$year=[int]$stryear
|
|
$month=[int]$strmonth
|
|
$day=[int]$strday
|
|
if ($year -lt 1 -or $month -lt 1 -or $day -lt 1) {
|
|
Write-Output "Skipped ""$sourceitem"" : File ExifData has invalid date!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor DarkYellow
|
|
}
|
|
else {
|
|
$mediadatestr = "$stryear-$strmonth-$strday"
|
|
$mediadate = [DateTime]"$mediadatestr"
|
|
$mediasubfolder = "$stryear`_$strmonth`_$strday"
|
|
$destdir="$destfolder\$mediasubfolder"
|
|
$dupldir="$duplicates\$mediasubfolder"
|
|
$todaystr=Get-Date -UFormat "%Y-%m-%d"
|
|
$todaydate = [DateTime]"$todaystr"
|
|
if ($mediadate -gt $todaydate) {
|
|
Write-Output "Skipped ""$sourceitem"" : File ExifData has future date!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor DarkYellow
|
|
}
|
|
else {
|
|
$destitem="$destdir/$mediafile"
|
|
$duplitem="$dupldir/$mediafile"
|
|
if (-Not(Test-Path -Path "$destdir")) {
|
|
Write-Output "$destdir" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Green
|
|
New-Item -ItemType "Directory" -Path "$destdir"
|
|
}
|
|
if(Test-Path $destitem) {
|
|
if(Test-Path $duplitem) {
|
|
Write-Output "Skipped ""$sourceitem"" : File exists in duplicates subfolder ""$mediasubfolder""!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Yellow
|
|
}
|
|
else {
|
|
if (-Not(Test-Path -Path "$dupldir")) {
|
|
Write-Output "$dupldir" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Green
|
|
New-Item -ItemType "Directory" -Path "$dupldir"
|
|
}
|
|
Move-Item -Path "$sourceitem" -Destination "$dupldir/"
|
|
Write-Output """$sourceitem"" moved to duplicates subfolder ""$mediasubfolder""!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor DarkYellow
|
|
}
|
|
}
|
|
else {
|
|
Move-Item -Path "$sourceitem" -Destination "$destdir/"
|
|
Write-Output """$sourceitem"" moved to destination subfolder ""$mediasubfolder""!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Green
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
###################################
|
|
# we call main proc from here
|
|
& $Main @Args |