DEV Community

Alexandru Bucur
Alexandru Bucur

Posted on • Originally published at alexandrubucur.com

How to read metadata easily in powershell with TagLibSharp

Doing my quarterly phone image cleanup and backup on onedrive I noticed that the script I was using wasn't quite consistent on the folder year and month generation.

If you think of it it does make sense, since it's basically doing a smart workaround on using LastWriteTime for its date source, but that isn't always good enough.

After some hours googling around (not yet on a 'binge' :D) I got two options that looked great.

  1. Reading the Date Taken from the file using ComObject
  2. Reading it using TagLibSharp

Reading the Date Taken from the file using ComObject

  $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
    ) {}

  $dateString = $directoryObject.GetDetailsOf($fileObject,$index)
Enter fullscreen mode Exit fullscreen mode

The problem for me was that I couldn't parse that date string to an actual DateTime object no matter what I tried ([datetime]::Parse / [datetime]::ParseExact with various formatting, even if g should have been a match, various cultures etc (including just sending $null)).

If anyone has any ideas on how to fix this I'm really curious.

Reading it using [TagLibSharp]

The tricky part for me here was understanding how to load a c# dll in powershell.
If you install the package using nuget the easiest way to figure out where the package is installed is to run Get-Package TagLibSharp | Format-List -Property Source.

Your output should be similar to:

❯ Get-Package TagLibSharp | Format-List -Property Source

Source : C:\Program Files\PackageManagement\NuGet\Packages\TagLibSharp.2.2.0\TagLibSharp.2.2.0.nupkg
Enter fullscreen mode Exit fullscreen mode

Once we know that we can use the following code to load the dll in our powershell script:

    $TagLib = "C:\Program Files\PackageManagement\NuGet\Packages\TagLibSharp.2.2.0\lib\netstandard2.0\TagLibSharp.dll"
    Add-Type -Path $TagLib

    [System.Reflection.Assembly]::LoadFile($TagLib)
Enter fullscreen mode Exit fullscreen mode

The reading part is really easy, and for images the property you want to look for is ImageTag:

$tag = [TagLib.File]::Create($file)
$tag.ImageTag
Enter fullscreen mode Exit fullscreen mode

There are a few nice things to gain once using TagLibSharp.

  1. ImageTag has a DateTime object directly available.
  2. You have full access to the image metadata so you could for eg create folders automatically based on the latitude and longitude with the closest city name. In case this sounds interesting please let me know and I'll try doing an updated version that does just that.
  3. We can implement a fallback to the simple Tag for videos for eg.

Full version can be found below and in the gist.

    $TagLib = "C:\Program Files\PackageManagement\NuGet\Packages\TagLibSharp.2.2.0\lib\netstandard2.0\TagLibSharp.dll"
    Add-Type -Path $TagLib

    [System.Reflection.Assembly]::LoadFile($TagLib)

    # Get the files which should be moved, without folders
    $files = Get-ChildItem 'X:\OneDrive\Pictures' -Recurse | Where-Object { !$_.PSIsContainer }

    # List Files which will be moved
    $files

    # Target Filder where files should be moved to. The script will automatically create a folder for the year and month.
    $targetPath = 'X:\OneDrive\Photos'

    foreach ($file in $files) {
      $tag = [TagLib.File]::Create($file)

      # Get year and Month of the file
      if ($tag.ImageTag.DateTime) {
        $year = $tag.ImageTag.DateTime.Year.ToString()
        # add 0 prefix to month
        $month = $tag.ImageTag.DateTime.Month.ToString("00")
      }
      elseif ($tag.Tag.DateTime) {
        $year = $tag.Tag.DateTime.Year.ToString()
        # add 0 prefix to month
        $month = $tag.Tag.DateTime.Month.ToString("00")
      }
      else {
        # I used LastWriteTime since this are synced files and the creation day will be the date when it was synced
        $year = $file.LastWriteTime.Year.ToString()
        # add 0 prefix to month
        $month = $file.LastWriteTime.Month.ToString("00")
      }

      # Out FileName, year and month
      $file.Name
      $year
      $month

      # Set Directory Path , change as wanted
      $Directory = $targetPath + "\" + $year + "\" + $year + "-" + $month
      # Create directory if it doesn't exists
      if (!(Test-Path $Directory)) {
        New-Item $directory -type directory
      }

      # Move File to new location, remove -Force if you might have files with the same name, i've used it since i knew that duplicates are ok to overwrite
      $file | Move-Item -Destination $Directory -Force
    }
Enter fullscreen mode Exit fullscreen mode

Top comments (0)