JNappi
JNappi

Reputation: 1585

How can I get programmatic access to the "Date taken" field of an image or video using powershell?

I'm trying convert a bunch of pictures and videos, but when I convert it to a new format I obviously lose the properties of the original file. I'd like to be able to read the "Date taken" property from the old file and update it on the new one using powershell.

Upvotes: 20

Views: 23991

Answers (5)

hdev
hdev

Reputation: 6527

This PowerShell function, Get-DateTaken, retrieves the date when a file was taken or as fallback created. It uses the Windows Shell COM object to access file properties that aren't directly accessible through .NET's System.IO classes.

This function is more versatile than using System.Drawing for a couple of reasons:

It can be used with any type of file, not just image files. System.Drawing is primarily used for working with images, so it may not be able to retrieve the "Date taken" property for other types of files.

function Get-DateTaken {
    [OutputType([datetime])]
    param (
        [Parameter(Mandatory = $true)]
        [System.IO.FileInfo]
        $file
    )

    # $DateFormat = 'dd.MM.yyyy HH:mm'
    $DateTakenWinApi = 12
    $DateCreatedWinApi = 4

    if ($null -eq $Shell) {
        $Shell = New-Object -ComObject shell.application
    }
 
    $dir = $Shell.Namespace($_.DirectoryName)
    $DateTakenString = $dir.GetDetailsOf($dir.ParseName($_.Name), $DateTakenWinApi)
    if ($DateTakenString -eq '') {
        $DateTakenString = $dir.GetDetailsOf($dir.ParseName($_.Name), $DateCreatedWinApi)
    }
    # sanitze string
    $DateTakenString = $DateTakenString -replace '[^0-9\.\:\ \/]', ''
    # parse to DateTime
    $DateTaken = Get-Date $DateTakenString # -Format $DateFormat
    $DateTaken
}

With synopsis: https://gist.github.com/dhcgn/90c6677f2d6c5c70c123b050b9f9a310

Upvotes: 0

Andrew Cleveland
Andrew Cleveland

Reputation: 11

This works for me, thanks to the above help and others.

try{
Get-ChildItem C:\YourFolder\Path | Where-Object {$_.extension -eq '.jpg'} |
ForEach-Object {
         
        $path = $_.FullName
        Add-Type -AssemblyName System.Drawing
        $bitmap = New-Object System.Drawing.Bitmap($path)
        $propertyItem = $bitmap.GetPropertyItem(36867) 
        $bytes = $propertyItem.Value 
        $string = [System.Text.Encoding]::ASCII.GetString($bytes) 
        $dateTime = [DateTime]::ParseExact($string,"yyyy:MM:dd HH:mm:ss`0",$Null)

        $bitmap.Dispose()

        $_.LastWriteTime = $dateTime
        $_.CreationTime = $dateTime
        }}
finally
{
}

Upvotes: 1

arni
arni

Reputation: 2397

To read and write the "date taken" property of an image, use the following code (building on the answer of @EBGreen):

try
{
    $path = "C:\PATH\TO\SomePic.jpg"
    $pathModified = "C:\PATH\TO\SomePic_MODIFIED.jpg"

    Add-Type -AssemblyName System.Drawing
    $bitmap = New-Object System.Drawing.Bitmap($path)
    $propertyItem = $bitmap.GetPropertyItem(36867) 
    $bytes = $propertyItem.Value 
    $string = [System.Text.Encoding]::ASCII.GetString($bytes) 
    $dateTime = [DateTime]::ParseExact($string,"yyyy:MM:dd HH:mm:ss`0",$Null)
   
    $dateTimeModified = $dateTime.AddDays(1) # Set new date here
    $stringModified = $dateTimeModified.ToString("yyyy:MM:dd HH:mm:ss`0",$Null)
    $bytesModified = [System.Text.Encoding]::ASCII.GetBytes($stringModified) 

    $propertyItem.Value = $bytesModified
    $bitmap.SetPropertyItem($propertyItem)
    $bitmap.Save($pathModified)
}
finally
{
    $bitmap.Dispose()
}

Upvotes: 0

EBGreen
EBGreen

Reputation: 37720

I can't test it right now (don't have any images with XIF data laying around, but I think this should work:

[reflection.assembly]::LoadWithPartialName("System.Drawing")
$pic = New-Object System.Drawing.Bitmap('C:\PATH\TO\SomePic.jpg')
$bitearr = $pic.GetPropertyItem(36867).Value 
$string = [System.Text.Encoding]::ASCII.GetString($bitearr) 
$DateTime = [datetime]::ParseExact($string,"yyyy:MM:dd HH:mm:ss`0",$Null)
$DateTime

Upvotes: 17

Emperor XLII
Emperor XLII

Reputation: 13432

In general, you can access any extended property for a file shown in explorer through the shell GetDetailsOf method. Here's a short example, adapted from another answer:

$file = Get-Item IMG_0386.jpg
$shellObject = New-Object -ComObject Shell.Application
$directoryObject = $shellObject.NameSpace( $file.Directory.FullName )
$fileObject = $directoryObject.ParseName( $file.Name )

$property = 'Date taken'
for(
  $index = 5;
  $directoryObject.GetDetailsOf( $directoryObject.Items, $index ) -ne $property;
  ++$index ) { }

$value = $directoryObject.GetDetailsOf( $fileObject, $index )


However, according to the comments on another question, there is no general-purpose mechanism for setting these properties. The System.Drawing.Bitmap class that EBGreen mentioned will work for images, but I'm afraid I also do not know of a .NET option for video files.

Upvotes: 7

Related Questions