topbanner_forum
  *

avatar image

Welcome, Guest. Please login or register.
Did you miss your activation email?

Login with username, password and session length
  • Friday December 13, 2024, 7:14 am
  • Proudly celebrating 15+ years online.
  • Donate now to become a lifetime supporting member of the site and get a non-expiring license key for all of our programs.
  • donate

Author Topic: IDEA: Automagic Digital Photo Manager  (Read 9125 times)

oblivion

  • Supporting Member
  • Joined in 2010
  • **
  • Posts: 495
    • View Profile
    • Read more about this member.
    • Donate to Member
IDEA: Automagic Digital Photo Manager
« on: October 21, 2018, 03:28 PM »
My father-in-law wants to be able to clear the photos off the SD card from his camera onto his PC so he can (a) free up space on the card, and (b) review and delete any photos he doesn't want to keep.

He has next to no idea of file management. The computer is for eBay and a few games.

So what I thought was some sort of semi-automated process. Identify a base target folder (possibly stored for later use in a config file), make a subfolder of the current year (if it doesn't already exist) and a subfolder of that for the current month.

Identify the source drive; assume \DCIM as the base folder for the photos.

Then copy all the JPGs from \dcim\ and any subfolders it might have into the newly-created yyyy/mmmm target folder, but ignore source target structure. (His camera makes subfolders for month or day but trying to create and then navigate the destination if it's too granular seems overkill, and dating the destination for now won't prevent later organisation based on the file data or EXIF stuff, I figure.)

Then fire up Explorer, or Photos, or Windows Photo Viewer, pointed at the destination.

Then reviewing the photos should at least mean accidental deletions wind up in the recycle bin...

This sounds straightforward but I keep getting tied in knots trying to work out how to solve it myself -- I guess I'm getting old :(
-- bests, Tim

...this space unintentionally left blank.

skwire

  • Global Moderator
  • Joined in 2005
  • *****
  • Posts: 5,287
    • View Profile
    • Donate to Member
Re: IDEA: Automagic Digital Photo Manager
« Reply #1 on: October 21, 2018, 03:43 PM »
To review:

  • Source is an SD card full of photos in the standard DCIM folder.  The folder structure of this folder is of no concern, i.e., we're not looking to recreate it in the destination.
  • Choose a destination folder on the computer.  In this folder, create a new folder with the year and a subfolder of the month.
  • Move all photos in the DCIM folder, and its subfolders, into this destination folder.
  • Start Windows Explorer at the current month subfolder of the destination.

Does the SD card show up as a drive letter?

oblivion

  • Supporting Member
  • Joined in 2010
  • **
  • Posts: 495
    • View Profile
    • Read more about this member.
    • Donate to Member
Re: IDEA: Automagic Digital Photo Manager
« Reply #2 on: October 22, 2018, 02:09 AM »
The review: yes to all.

Does the SD card show up as a drive letter?
Yes, again.

I can probably ensure that the drive letter always comes up the same, but I guess you could also look for available drives with \dcim folders in the root and present them (or, hopefully always!) it as a confirmation for the source.
-- bests, Tim

...this space unintentionally left blank.

4wd

  • Supporting Member
  • Joined in 2006
  • **
  • Posts: 5,644
    • View Profile
    • Donate to Member
Re: IDEA: Automagic Digital Photo Manager
« Reply #3 on: October 23, 2018, 05:36 PM »
Something simple in PowerShell while skwire whips up a masterpiece :P : DCIMover.ps1

Updated version:
  • Lists all removable drives that have a DCIM folder,
  • Saves the destination folder when you click Start,
  • Defaults to Copy,
  • Creates destination folders as per Year\Month, eg. 2017\03_Mar,
  • Will not overwrite any existing file,
  • Works on JPEG, DNG, AVI, MOV, MP4 files, (uses Date Created for video formats),
  • Can easily add more image/video formats, (just edit the .ini file),
  • Will open your default file manager at the last folder created, (works for DOpus, File Explorer), for both images and videos (if it's the same folder then normally the already open file manager window is brought to the front),
  • Multi-threaded so the GUI doesn't become unresponsive,
  • Progressbar updates as it goes and a window shows the current operation and any errors,
  • Can cancel the operation by just closing the GUI, (might implement a Stop button later),
  • Sets the creation date of the destination file to the Date Taken of the image, (or Date Created if it's video),
  • You can set the initial folder to select a destination folder in the .ini file,
  • Interface controls are disabled while it's running, (so you don't go swapping between Copy/Move),
  • If any errors occurred the log file will be opened at the end of the operation.

There's a small video, (1:16), in the archive that shows it working, (original files shown on the left, folder tree being created with files on the right).

It requires Powershell v3+, (TBH I'm not sure what minimum version is required for some cmdlets - I can only test against v5).

Installation:

Extract the archive and copy the contents somewhere.
If you need to move the shortcut somewhere else, open the properties of the shortcut and edit the Target field to include the path to the script.

Edit the DCIMover.ini file for some basic preferences.

DCIMover.ini
Code: Text [Select]
  1. [General]
  2. dest=K:\a_folder\test place
  3. initfolder=c:\users\fred\pictures\photos
  4. images=.dng,.jpg
  5. videos=.3gp,.avi,.mov,.mp4

  • dest - The last destination folder used, saved whenever the Start button is pressed.
  • initfolder - The "top level" folder used for the folder browser when selecting a destination, if it doesn't exist the folder browser will default to My Computer.
  • images - The extensions of image files, comma separated.  These are files that contain Date Taken metadata so can include any other file type that has that data, (eg. maybe some video formats)
  • videos - The extensions of video files, comma separated.  The Date Created metadata will be used for these file types.

Running:

  • Run it from the shortcut, a Powershell console will open for a second then close, the script GUI will then open.
  • Select your source drive from the drop-down list, (any removable drive with a folder named DCIM in the root will be in the list).
  • Select your destination folder, the files will be copied here in the appropriate year\month sub-folders.
  • Select Copy or Move, (I suggest Copy until you're are sure it's working correctly).
  • Hit Start.

DCIMover.pngIDEA: Automagic Digital Photo Manager

Code: PowerShell [Select]
  1. $Global:SyncHash = [hashtable]::Synchronized(@{})
  2. $newRunspace =[runspacefactory]::CreateRunspace()
  3. $newRunspace.ApartmentState = "STA"
  4. $newRunspace.ThreadOptions = "ReuseThread"
  5. $newRunspace.Open()
  6. $newRunspace.SessionStateProxy.SetVariable("SyncHash",$SyncHash)
  7.  
  8. # Load WPF assembly if necessary
  9. [void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
  10.  
  11. $psCmd = [PowerShell]::Create().AddScript({
  12.   [xml]$xaml = @"
  13. <Window x:Class="DCIMover.MainWindow"
  14.        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  15.        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  16.        Title="DCIMover" Height="350" Width="522" ResizeMode="CanMinimize">
  17.    <Grid>
  18.        <TextBox x:Name="TextBox1" HorizontalAlignment="Left" Height="23" Margin="102,72,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="325" ToolTip="Destination folder for photos/videos"/>
  19.        <Label x:Name="Label1" Content="Destination" HorizontalAlignment="Left" Margin="25,66,0,0" VerticalAlignment="Top" Width="72"/>
  20.        <Button x:Name="Button1" Content="..." HorizontalAlignment="Left" Margin="460,72,0,0" VerticalAlignment="Top" Width="32" ToolTip="Select destination folder"/>
  21.        <Label x:Name="Label2" Content="Source" HorizontalAlignment="Left" Margin="25,21,0,0" VerticalAlignment="Top"/>
  22.        <ComboBox x:Name="ComboBox1" HorizontalAlignment="Left" Margin="102,25,0,0" VerticalAlignment="Top" Width="62" ToolTip="Select source card from list"/>
  23.        <ProgressBar x:Name="ProgressBar1" HorizontalAlignment="Left" Height="26" Margin="25,118,0,0" VerticalAlignment="Top" Width="467" IsTabStop="False"/>
  24.        <TextBox x:Name="TextBox2" HorizontalAlignment="Left" Height="136" Margin="25,164,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="467" ToolTip="Status messages" AllowDrop="False" Focusable="False" IsTabStop="False" VerticalScrollBarVisibility="Auto" IsUndoEnabled="False" IsReadOnly="True" HorizontalScrollBarVisibility="Auto" ScrollViewer.CanContentScroll="True"/>
  25.        <Button x:Name="Button2" Content="Start" HorizontalAlignment="Left" Margin="417,25,0,0" VerticalAlignment="Top" Width="75"/>
  26.        <RadioButton x:Name="RadioButton1" Content="Copy" HorizontalAlignment="Left" Margin="331,19,0,0" VerticalAlignment="Top" IsChecked="True" ToolTip="Copy files from card"/>
  27.        <RadioButton x:Name="RadioButton2" Content="Move" HorizontalAlignment="Left" Margin="331,39,0,0" VerticalAlignment="Top" ToolTip="Move files from card"/>
  28.  
  29.    </Grid>
  30. </Window>
  31. "@
  32.  
  33.   # Remove XML attributes that break a couple things.
  34.   #   Without this, you must manually remove the attributes
  35.   #   after pasting from Visual Studio. If more attributes
  36.   #   need to be removed automatically, add them below.
  37.   $AttributesToRemove = @(
  38.     'x:Class',
  39.     'mc:Ignorable'
  40.   )
  41.  
  42.   Foreach ($Attrib in $AttributesToRemove) {
  43.     if ($xaml.Window.GetAttribute($Attrib)) {
  44.       $xaml.Window.RemoveAttribute($Attrib)
  45.     }
  46.   }
  47.    
  48.   $reader = (New-Object System.Xml.XmlNodeReader $xaml)
  49.    
  50.   $SyncHash.Window = [Windows.Markup.XamlReader]::Load( $reader )
  51.  
  52.   [xml]$XAML = $xaml
  53.   $xaml.SelectNodes("//*[@*[contains(translate(name(.),'n','N'),'Name')]]") | %{
  54.   #Find all of the form types and add them as members to the synchash
  55.     $SyncHash.Add($_.Name, $SyncHash.Window.FindName($_.Name) )
  56.   }
  57.  
  58.   $Script:JobCleanup = [hashtable]::Synchronized(@{})
  59.   $Script:Jobs = [system.collections.arraylist]::Synchronized((New-Object System.Collections.ArrayList))
  60.  
  61.   #region Background runspace to clean up jobs
  62.   $jobCleanup.Flag = $True
  63.   $newRunspace = [runspacefactory]::CreateRunspace()
  64.   $newRunspace.ApartmentState = "STA"
  65.   $newRunspace.ThreadOptions = "ReuseThread"          
  66.   $newRunspace.Open()        
  67.   $newRunspace.SessionStateProxy.SetVariable("jobCleanup", $jobCleanup)    
  68.   $newRunspace.SessionStateProxy.SetVariable("jobs", $jobs)
  69.   $jobCleanup.PowerShell = [PowerShell]::Create().AddScript({
  70.   #Routine to handle completed runspaces
  71.     Do {    
  72.       Foreach($runspace in $jobs) {            
  73.         If ($runspace.Runspace.isCompleted) {
  74.           [void]$runspace.powershell.EndInvoke($runspace.Runspace)
  75.           $runspace.powershell.dispose()
  76.           $runspace.Runspace = $null
  77.           $runspace.powershell = $null              
  78.         }
  79.       }
  80.   #Clean out unused runspace jobs
  81.       $temphash = $jobs.clone()
  82.       $temphash | Where {$_.runspace -eq $Null} | ForEach {$jobs.remove($_)}        
  83.       Start-Sleep -Seconds 1    
  84.     } while ($jobCleanup.Flag)
  85.   })
  86.   $jobCleanup.PowerShell.Runspace = $newRunspace
  87.   $jobCleanup.Thread = $jobCleanup.PowerShell.BeginInvoke()  
  88.   #endregion Background runspace to clean up jobs
  89.  
  90.  
  91.   $SyncHash.Button1.Add_Click({
  92.     $objShell = new-object -com shell.application
  93.     # Special Folders: https://docs.microsoft.com/en-us/windows/desktop/api/shldisp/ne-shldisp-shellspecialfolderconstants
  94.     # eg. Pictures = 0x27
  95.     $objFolder = $objShell.BrowseForFolder(0, "Select destination folder", 0x00000074, $SyncHash.InitFolder)
  96.     if ($objfolder.self.path -ne $null) {
  97.       $SyncHash.TextBox1.Text = $objfolder.self.path
  98.     }
  99.   })
  100.  
  101.   $SyncHash.Button2.Add_Click({
  102. # Start-Job -Name Sleeping -ScriptBlock {start-sleep 5}
  103. # while ((Get-Job Sleeping).State -eq 'Running'){
  104. # region Boe's Additions
  105.     $newRunspace =[runspacefactory]::CreateRunspace()
  106.     $newRunspace.ApartmentState = "STA"
  107.     $newRunspace.ThreadOptions = "ReuseThread"          
  108.     $newRunspace.Open()
  109.     $newRunspace.SessionStateProxy.SetVariable("SyncHash", $SyncHash)
  110.     $PowerShell = [PowerShell]::Create().AddScript({
  111.       Function Update-Window {
  112.         Param (
  113.           $Control,
  114.           $Property,
  115.           $Value,
  116.           [switch]$AppendContent
  117.         )
  118. # This is kind of a hack, there may be a better way to do this
  119.         If ($Property -eq "Close") {
  120.           $SyncHash.Window.Dispatcher.invoke([action]{$SyncHash.Window.Close()}, "Normal")
  121.           Return
  122.         }
  123. # This updates the control based on the parameters passed to the function
  124.         $SyncHash.$Control.Dispatcher.Invoke([action]{
  125. # This bit is only really meaningful for the TextBox control, which might be useful for logging progress steps
  126.           If ($PSBoundParameters['AppendContent']) {
  127.             $SyncHash.$Control.AppendText($Value)
  128.           } Else {
  129.             $SyncHash.$Control.$Property = $Value
  130.           }
  131.         }, "Normal")
  132.       }
  133.  
  134.       Function Get-MetaData {
  135.         Param (
  136.           [PARAMETER(Mandatory=$true)]
  137.           [string]$path = '',
  138. #          [PARAMETER( Mandatory = $true, HelpMessage = 'extension eg. .mp3, .txt or .mov')]
  139.           [boolean]$images,
  140. #          [string]$type = '', # 'mp3'
  141.           [switch]$recurse
  142.         )
  143.  
  144.         if ($images) {
  145.           $types = $SyncHash.imageTypes
  146.         } else {
  147.           $types = $SyncHash.videoTypes
  148.         }
  149.  
  150.         $path += 'DCIM'
  151.         if ($recurse) {
  152.           $LPath = Get-ChildItem -Path $path -Directory -Recurse
  153.         } else {
  154.           $LPath = $path
  155.         }
  156.  
  157. #        $DirectoryCount = 1
  158.         $RetrievedMetadata = $true
  159.         $OutputList = New-Object 'System.Collections.generic.List[psobject]'
  160.  
  161.         Foreach ($pa in $LPath) {
  162.           $shell = New-Object -ComObject shell.application
  163.  
  164.           if ($recurse) {
  165.             $objshell = $shell.NameSpace($pa.FullName)
  166.           } else {
  167.             $objshell = $shell.NameSpace($pa)
  168.           }
  169.           # Build data list
  170.           $count = 0
  171.  
  172.           # Filter on filetype
  173.           for ($i = 0; $i -lt $types.Count; $i++) {
  174.             $filter = $null
  175.             Update-Window -Control ProgressBar1 -Property Value -Value 0
  176.             Start-Sleep -Milliseconds 500
  177.             $filter = $objshell.Items() | where {$_.Name -match $types[$i]}
  178.             Write-Message  "Collecting Metadata for '$($types[$i])' files ..."
  179. <#
  180. $filter.Count > 0 if more than one matching file
  181. $filter.Count = 0 if no matching files
  182. $filter.Count = $null if one matching file
  183. #>
  184.             Foreach ($file in $filter) {
  185.               if ($RetrievedMetadata) {
  186. # Build metanumbers
  187.                 $Metanumbers = New-Object -TypeName 'System.Collections.Generic.List[int]'
  188.                 for ($a = 0; $a -le 400; $a++) {
  189.                   if ($objshell.GetDetailsOf($file, $a)) {
  190.                     $Metanumbers.Add([int]$a)
  191.                   }    
  192.                 }
  193.                 $RetrievedMetadata = $false
  194.               }
  195.  
  196.               $count++
  197.               $CurrentDirectory = Get-ChildItem -Path $file.path
  198.  
  199.               if ($filter.Count -gt 0) {
  200.                 try {
  201.                   Update-Window -Control ProgressBar1 -Property Value -Value ([math]::Round((($count / $filter.count) * 100)))
  202.                 } catch {}
  203.               } else {
  204.                 try {
  205.                   Update-Window -Control ProgressBar1 -Property Value -Value 100
  206.                 } catch {}
  207.               }
  208.                
  209. # Build Hashtable for each file
  210.               $hash = $null
  211.               $Hash = @{}
  212.               foreach ($nr in $Metanumbers) {
  213.                 $PropertyName = $($objshell.GetDetailsOf($objshell.Items, $nr))
  214.                 $PropertyValue = $($objshell.GetDetailsOf($File, $nr))
  215.                 $Hash[$PropertyName] = $PropertyValue
  216.               }
  217.            
  218.               $Hash.Remove("")
  219.               $FileMetaData = New-Object -TypeName PSobject -Property $hash
  220.               $OutputList.Add($FileMetaData)
  221.             }
  222.           }
  223.         }
  224.         Process-OutputList $OutputList $images
  225.       }
  226.  
  227.       Function Process-OutputList {
  228.         Param (
  229.           [object]$files,
  230.           [string]$images
  231.         )
  232.  
  233.         $dest = $SyncHash.Window.Dispatcher.invoke([System.Func[String]] {$SyncHash.TextBox1.Text})
  234.  
  235.         if ($files.Count -eq $null) {
  236.           Write-Message  "Copy/Move one file ..."
  237.           switch ($images) {
  238.             $true { $takenDate = [datetime]((Select-Object -InputObject $files[$j] -ExpandProperty 'Date Taken') -replace '[^\d:/ -_.,]', '') }
  239.             $false { $takenDate = [datetime](Select-Object -InputObject $files[$j] -ExpandProperty 'Date Created') }
  240.           }
  241.           if ($takenDate -ne $null) {
  242.             $outPath = ($dest + '\' + $takenDate.Year + '\' + ($takenDate.Month).ToString('00') + '_' + ((Get-Culture).DateTimeFormat.GetAbbreviatedMonthName($takenDate.Month)) + '\' + $files.Name) -replace '\\', '\'
  243.             Move-File $files.'Path' $outPath $takenDate
  244.             Update-Window -Control ProgressBar1 -Property Value -Value 100
  245.           } else {
  246.             Write-Message "[Warn]: $($files.Name): Date Taken not found, Copy/Move not performed" $true
  247.           }
  248.         } else {
  249.           if ($files.Count -gt 0) {
  250.             Write-Message  "Copy/Move $($files.Count) files ..."
  251.             for ($j = 0; $j -lt $files.Count; $j++) {
  252.               $outPath = $null
  253.               $takenDate = $null
  254.                 switch ($images) {
  255.                   $true {
  256.                       if (((Select-Object -InputObject $files[$j] -ExpandProperty 'Date Taken')).Length -gt 1 ) {
  257.                         $takenDate = [datetime]((Select-Object -InputObject $files[$j] -ExpandProperty 'Date Taken') -replace '[^\d:/ -_.,]', '')
  258.                       } else {
  259.                         Write-Message "[Warn]: $($files[$j].'Name'): Date Taken not found, Copy/Move not performed" $true
  260.                       }
  261.                     }
  262.                   $false {
  263.                       $takenDate = [datetime](Select-Object -InputObject $files[$j] -ExpandProperty 'Date Created')
  264.                     }
  265.                 }
  266.               if ($takenDate -ne $null) {
  267.                 $outPath = ($dest + '\' + $takenDate.Year + '\' + ($takenDate.Month).ToString('00') + '_' + ((Get-Culture).DateTimeFormat.GetAbbreviatedMonthName($takenDate.Month)) + '\' + $files[$j].'Name') -replace '\\', '\'
  268.                 Move-File $files[$j].'Path' $outPath $takenDate
  269.               }
  270.               Update-Window -Control ProgressBar1 -Property Value -Value ([math]::Round((($j + 1) / $files.Count) * 100))
  271.             }
  272.           } else {
  273.             Write-Message  "No matching files found ..."
  274.           }
  275.         }
  276.         Invoke-Item (Split-Path -Path $outPath)
  277.       }
  278.  
  279.       Function Move-File {
  280.         param (
  281.           [string]$source,
  282.           [string]$dest,
  283.           [datetime]$date
  284.         )
  285.         if (!(Test-Path -Path (Split-Path -Path $dest))) {
  286.           New-Item (Split-Path -Path $dest) -ItemType Directory
  287.           if (!$?) {
  288.             Write-Message "[Error] Failed to create folder: $(Split-Path -Path $dest)" $true
  289.           }
  290.         }
  291.  
  292.         if (!(Test-Path $dest)) {
  293.           if ($SyncHash.Window.Dispatcher.invoke([System.Func[string]] {$SyncHash.RadioButton1.IsChecked}) -eq 'True') {
  294.             Copy-Item $source $dest
  295.             if (!$?) {
  296.               Write-Message "[Error] $(Split-Path $source -Leaf): Copy failed" $true
  297.             }
  298.           } else {
  299.             Move-Item $source $dest
  300.             if (!$?) {
  301.               Write-Message "[Error] $(Split-Path $source -Leaf): Move failed" $true
  302.             }
  303.           }
  304.           Set-ItemProperty -Path $dest -Name CreationTime -Value $date
  305.         } else {
  306.           Write-Message "[Warn] $(Split-Path $source -Leaf): Exists in destination" $true
  307.         }
  308.       }
  309.  
  310.       Function Write-Message {
  311.         Param (
  312.           [string]$message,
  313.           [boolean]$err = $false
  314.         )
  315.         Update-Window -Control TextBox2 -Property Text -Value "$($message)`r`n" -Append
  316.         if ($err) {
  317.           $SyncHash.hadError = $true
  318.           $message | Out-File -FilePath $SyncHash.LogFile -Append
  319.         }
  320.       }
  321.  
  322. # Button2 Main
  323.       $srceFol = $SyncHash.Window.Dispatcher.invoke([System.Func[String]] {$SyncHash.ComboBox1.SelectedItem})
  324.       $destFol = $SyncHash.Window.Dispatcher.invoke([System.Func[String]] {$SyncHash.TextBox1.Text})
  325.       $SyncHash.hadError = $false
  326.  
  327.       if (($destFol -ne $null) -and ($destFol -ne '')) {
  328.         if (($srceFol -ne $null) -and ($srceFol -ne '')) {
  329.           $ini = "[General]`r`ndest=$($destFol)`r`ninitfolder=$($SyncHash.InitFolder)`r`nimages=$($SyncHash.h.Get_Item('images'))`r`nvideos=$($SyncHash.h.Get_Item('videos'))"
  330.           Out-File -FilePath $SyncHash.iniFile -InputObject $ini
  331.           $SyncHash.LogFile = ("$($env:TEMP)\$(Get-Date -Format 'yyyyMMdd_HHmmss').txt").Replace('\\', '\')
  332.           Update-Window -Control Button2 -Property IsEnabled -Value $false
  333.           Update-Window -Control RadioButton1 -Property IsEnabled -Value $false
  334.           Update-Window -Control RadioButton2 -Property IsEnabled -Value $false
  335.           Update-Window -Control TextBox1 -Property IsEnabled -Value $false
  336.           Update-Window -Control ComboBox1 -Property IsEnabled -Value $false
  337.           Update-Window -Control Button1 -Property IsEnabled -Value $false
  338.           $SyncHash.hadError = $false
  339.           Update-Window -Control TextBox2 -Property Text -Value ''
  340.           Write-Message "---- START ----`r`nLogfile: $($SyncHash.LogFile)`r`nProcessing image files ..."
  341.           Get-MetaData -Path $srceFol $true -Recurse
  342.           Write-Message "Finished image files`r`nProcessing video files ..."
  343.           Get-MetaData -Path $srceFol $false -Recurse
  344.           Write-Message  "Finished video files`r`n---- END ----"
  345.           Update-Window -Control RadioButton1 -Property IsEnabled -Value $true
  346.           Update-Window -Control RadioButton2 -Property IsEnabled -Value $true
  347.           Update-Window -Control TextBox1 -Property IsEnabled -Value $true
  348.           Update-Window -Control ComboBox1 -Property IsEnabled -Value $true
  349.           Update-Window -Control Button1 -Property IsEnabled -Value $true
  350.           Update-Window -Control Button2 -Property IsEnabled -Value $true
  351.           if ($SyncHash.hadError) {
  352.             "--- SAVE THIS FILE ---`r`n" + (Get-Content $SyncHash.LogFile -Raw) | Set-Content $SyncHash.LogFile
  353.             Write-Message  "*** There were errors, refer to log file ***"
  354.             Invoke-Item $SyncHash.LogFile
  355.           }
  356.         }
  357.       }
  358. # End Button2 Main
  359.  
  360.     })
  361.     $PowerShell.Runspace = $newRunspace
  362.     [void]$Jobs.Add((
  363.     [pscustomobject]@{
  364.       PowerShell = $PowerShell
  365.       Runspace = $PowerShell.BeginInvoke()
  366.     }
  367.     ))
  368.   })
  369.  
  370.     #region Window Close
  371.     $SyncHash.Window.Add_Closed({
  372.       Write-Verbose 'Halt runspace cleanup job processing'
  373.       $jobCleanup.Flag = $False
  374.  
  375.       #Stop all runspaces
  376.       $jobCleanup.PowerShell.Dispose()      
  377.     })
  378.     #endregion Window Close
  379.     #endregion Boe's Additions
  380.  
  381.  
  382. # GUI Main + Functions
  383.   Function Add-Drives {
  384.     $usb = ([System.IO.DriveInfo]::GetDrives() | Where DriveType -match 'Removable')
  385.     for ($i = 0; $i -lt $usb.Count; $i++) {
  386.       $dcimFol = ($usb[$i].Name) + 'DCIM'
  387.       if (Test-Path -Path $dcimFol) {
  388.         [void] $SyncHash.ComboBox1.Items.Add($usb[$i])
  389.       }
  390.     }
  391.   }
  392.  
  393.   Function Read-Ini {
  394.     if (Test-Path -Path $SyncHash.iniFile) {
  395.       Get-Content $SyncHash.iniFile | foreach-object -begin {$SyncHash.h=@{}} -process { $k = [regex]::split($_,'='); `
  396.         if (($k[0].CompareTo("") -ne 0) -and ($k[0].StartsWith("[") -ne $True)) { $SyncHash.h.Add($k[0], $k[1]) } }
  397.       $SyncHash.TextBox1.Text = $SyncHash.h.Get_Item('dest')
  398.       $SyncHash.InitFolder = $SyncHash.h.Get_Item('initfolder')
  399.       $SyncHash.imageTypes = ($SyncHash.h.Get_Item('images')).Split(',')
  400.       $SyncHash.videoTypes = ($SyncHash.h.Get_Item('videos')).Split(',')
  401.     }
  402.   }
  403.  
  404.   if ($PSVersionTable.PSVersion.Major -lt 3) {
  405.     [System.Windows.MessageBox]::Show("This script requires Powershell v3+`r`n`r`nClick OK to exit")
  406.     Exit
  407.   }
  408.   $SyncHash.iniFile = '.\DCIMover.ini'
  409.   Read-Ini
  410.   Add-Drives
  411. # End GUI Main + Functions
  412.  
  413.  
  414.   $SyncHash.Window.ShowDialog() | Out-Null
  415.   $SyncHash.Error = $Error
  416. })
  417. $psCmd.Runspace = $newRunspace
  418. $data = $psCmd.BeginInvoke()

Other things I might look at doing:
  • If Date Taken doesn't exist, default to Date Created, (usually happens with photos that are manipulated in the phone/camera, eg. panoramas).
  • For video files look for Media Created first before using Date Created, (one problem is Media Created uses UTC as defined by the camera setting, which can be different from Date Created).
  • Find some way of outputting Powershell errors, (the red ones), to a log file.
« Last Edit: November 10, 2018, 06:45 AM by 4wd »

skwire

  • Global Moderator
  • Joined in 2005
  • *****
  • Posts: 5,287
    • View Profile
    • Donate to Member
Re: IDEA: Automagic Digital Photo Manager
« Reply #4 on: October 24, 2018, 01:57 PM »
Something simple in PowerShell while skwire whips up a masterpiece

Hahaha.   :D  I've been traveling for work a lot lately, so I won't be able to get to this for at least a few days.  Thank you for coming up with your rendition of the original ask!

oblivion

  • Supporting Member
  • Joined in 2010
  • **
  • Posts: 495
    • View Profile
    • Read more about this member.
    • Donate to Member
Re: IDEA: Automagic Digital Photo Manager
« Reply #5 on: October 27, 2018, 12:53 AM »
Something simple in PowerShell while skwire whips up a masterpiece :P : DCIMover.ps1
That's awesome, thanks!

I won't be able to get access to their PC again for a few days but I gave them a USBsafelyRemove license I no longer need to handle the autoplay side of things and between the two, it should be perfect -- thanks again!
-- bests, Tim

...this space unintentionally left blank.

oblivion

  • Supporting Member
  • Joined in 2010
  • **
  • Posts: 495
    • View Profile
    • Read more about this member.
    • Donate to Member
Re: IDEA: Automagic Digital Photo Manager
« Reply #6 on: October 27, 2018, 04:23 PM »
#region gui events {
$Button1.Add_Click({
  $objForm = New-Object System.Windows.Forms.FolderBrowserDialog
  $objForm.Description = "Destination folder"
  $objForm.SelectedPath = [System.Environment+SpecialFolder]'MyComputer'
  $objForm.ShowNewFolderButton = $false
  $result = $objForm.ShowDialog()
  if ($result -eq "OK") {
    $TextBox1.Text = $objForm.SelectedPath
  } else {
    $TextBox1.Text = ""
  }
})

Just one quick question: if I wanted to modify this bit to a specific root destination folder (eg c:\users\fred\pictures\photos) would I just change this line

$objForm.SelectedPath = [System.Environment+SpecialFolder]'MyComputer'

or is it more complicated than that? (I really want to make it as simple as I can for him!)
-- bests, Tim

...this space unintentionally left blank.

4wd

  • Supporting Member
  • Joined in 2006
  • **
  • Posts: 5,644
    • View Profile
    • Donate to Member
Re: IDEA: Automagic Digital Photo Manager
« Reply #7 on: October 27, 2018, 11:02 PM »
Just one quick question: if I wanted to modify this bit to a specific root destination folder (eg c:\users\fred\pictures\photos) would I just change this line

$objForm.SelectedPath = [System.Environment+SpecialFolder]'MyComputer'

or is it more complicated than that? (I really want to make it as simple as I can for him!)

If you want an absolute location change it to:
$objForm.SelectedPath = 'c:\users\fred\pictures\photos'

The [System.Environment+SpecialFolder] is due to My Computer being classed as a special folder, (like 'Documents', 'Pictures', etc).

I've just updated it using a different file properties function and added the ability to handle the following filetypes:
.jpg, .dng (Canon Raw), .mov, .mp4, .avi

For JPEG and DNG it'll use the Date Taken value, for the video files it'll use Date Created.

Easy enough to add other filetypes if you want something specific to a camera.

Also changed the month folder names to xx_ABC, eg. 05_May, so it lists the months in calendar order rather than alphabetical.

Added a progress bar, still can't interrupt it as it works but at least it shows it's doing something ... maybe.

Now to work out how runspaces work.

Updated
« Last Edit: October 27, 2018, 11:21 PM by 4wd »

oblivion

  • Supporting Member
  • Joined in 2010
  • **
  • Posts: 495
    • View Profile
    • Read more about this member.
    • Donate to Member
Re: IDEA: Automagic Digital Photo Manager
« Reply #8 on: October 28, 2018, 06:14 PM »
I've just updated it
Well, I took it over tonight; put it and the shortcuts in c:\utilities\dcimover, modified the relevant line with the absolute path, set up the relevant bits to autorun the shortcut on insertion, and I just got a Powershell window with nothing in it.

Some more experimentation later and I managed to start the GUI -- by running the script directly, I think -- but it threw some scary-looking errors when I hit the Move button.

It did something -- the test pictures I'd put on the card disappeared -- but I couldn't work out what.

And, of course, as I'd spent several hours trying to solve a completely unrelated problem on the computer that I was completely unprepared for, by the time I got to this point I didn't have time to do all the testing I'd have liked. :(

Now I'm away from it, it occurs to me to wonder if I'd just left a terminating backslash off the path, or included one when I shouldn't have, or something else obvious, but I probably won't be able to look for another week...
-- bests, Tim

...this space unintentionally left blank.

4wd

  • Supporting Member
  • Joined in 2006
  • **
  • Posts: 5,644
    • View Profile
    • Donate to Member
Re: IDEA: Automagic Digital Photo Manager
« Reply #9 on: October 28, 2018, 07:34 PM »
Well, I took it over tonight; put it and the shortcuts in c:\utilities\dcimover, modified the relevant line with the absolute path, set up the relevant bits to autorun the shortcut on insertion, and I just got a Powershell window with nothing in it.

Forgot to mention, the shortcut needs to be in the same place as the script otherwise you need to add the full path to it in the shortcut Target field.

What OS are we looking at?

Some more experimentation later and I managed to start the GUI -- by running the script directly, I think -- but it threw some scary-looking errors when I hit the Move button.

Did you run it with ExecutionPolicy set to Bypass?

Because it was downloaded it will be marked by Windows as coming from another computer which can block it from running or lower it's access to a folder.

Run it with the following command from a PowerShell console:

Code: Text [Select]
  1. %SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe -sta -noprofile -executionpolicy bypass -File "DCIMover-GUI.ps1"

You can then copy/paste the error messages in the console.

Also, what version of PowerShell, do you know?
Next time you're there type $PSVersionTable in the PowerShell console which will give the version number, I don't think there's anything version specific in the script.

It did something -- the test pictures I'd put on the card disappeared -- but I couldn't work out what.

Well, it moved them somewhere otherwise they'd still be there ;)

The error messages will help a lot.

Now I'm away from it, it occurs to me to wonder if I'd just left a terminating backslash off the path, or included one when I shouldn't have, or something else obvious, but I probably won't be able to look for another week...

No terminating backslash required on the path.

You can't try it on your computer?

Thanks for giving it a go.

4wd

  • Supporting Member
  • Joined in 2006
  • **
  • Posts: 5,644
    • View Profile
    • Donate to Member
Re: IDEA: Automagic Digital Photo Manager
« Reply #10 on: November 05, 2018, 05:59 AM »
« Last Edit: November 08, 2018, 10:22 PM by 4wd »

magician62

  • Supporting Member
  • Joined in 2011
  • **
  • Posts: 180
    • View Profile
    • Donate to Member
Re: IDEA: Automagic Digital Photo Manager
« Reply #11 on: November 05, 2018, 07:30 AM »
A little observation, as I need to look further at this util. I work always in GMT for photograph times, but I suspect not all cameras do. :)
Why an I Magician62? Because Magician1 thru 61 were gone. :)

4wd

  • Supporting Member
  • Joined in 2006
  • **
  • Posts: 5,644
    • View Profile
    • Donate to Member
Re: IDEA: Automagic Digital Photo Manager
« Reply #12 on: November 05, 2018, 07:55 AM »
Date/time on photos/videos is set by the camera, the operator sets the date/time on the camera.

The script just grabs the date/time in the file metadata: Date Taken for JPEG and DNG; Date Created for AVI, MP4, and MOV.

No date/time manipulation is done.

oblivion

  • Supporting Member
  • Joined in 2010
  • **
  • Posts: 495
    • View Profile
    • Read more about this member.
    • Donate to Member
Re: IDEA: Automagic Digital Photo Manager
« Reply #13 on: November 06, 2018, 04:27 PM »
I've attached a new version here, differences to the one above:
Thanks!

I haven't had a chance to try again, yet. The father-in-law's PC is acting weird and I spent several hours at the weekend trying to work out the problem rather than taking another shot at this. (I am beginning to wonder if the problems I had with the earlier version of this and the odd general problems are perhaps related... every so often the system goes unresponsive, for no obvious reason, and the only evidence of anything is some incomprehensible DCOM-related errors in the system log. I may have to reset Windows. :( )
-- bests, Tim

...this space unintentionally left blank.

4wd

  • Supporting Member
  • Joined in 2006
  • **
  • Posts: 5,644
    • View Profile
    • Donate to Member
Re: IDEA: Automagic Digital Photo Manager
« Reply #14 on: November 07, 2018, 02:47 AM »
I'm updating the script a fair bit to display progress/errors, (and an error log), and make it easier to add support for other photo/video extensions.

EDIT: Whoops, I misinterpreted the Copy-Item/Move-Item cmdlets, they do overwrite by default - which is kind of dumb.

I'll put in a Test-Path in beforehand to check for file existence first.
« Last Edit: November 07, 2018, 04:55 AM by 4wd »

4wd

  • Supporting Member
  • Joined in 2006
  • **
  • Posts: 5,644
    • View Profile
    • Donate to Member
Re: IDEA: Automagic Digital Photo Manager
« Reply #15 on: November 08, 2018, 10:30 PM »
New version

Other things I might look at doing:
  • If Date Taken doesn't exist, default to Date Created, (usually happens with photos that are manipulated in the phone/camera, eg. panoramas).
  • For video files look for Media Created first before using Date Created, (one problem is Media Created uses UTC as defined by the camera setting, which can be different from Date Created).
  • Find some way of outputting Powershell errors, (the red ones), to a log file.