or “Fix CreationDate for GOOGLE Takeout Photos”

(or “a favor for a friend that turned ballistic”)

Today i saw a message on my phone, a good friend of mine had lost the photos that were locally stored on his phone.
Luckily, he had a backup on google photos, he decided to grab a backup of his google cloud storage via Google Takeout and create another local backup.

That’s where things got interesting,
it turned out that Google Takeout (or maybe even Google Photos itself?) actually strips all photos of their EXIF data and creates a “imagename.json” json file instead, storing the json next to the actual image.
And most interestingly the Takeout generated Json information was warped in regards to the original Exif data, containing information like “photoTakenTime” and “creationDate” which are not the same value.

This poses an interesting problem, you might want to be able to search through, sort and otherwise work with the image metadata of the takeout, for example, i’m somebody who likes to statistically analyze some aspects of my google behavior locally with my own self-made tools.

So my friend asked me if i could provide a script that fixes the creationDate of the pictures on the windows filesystem by using the json files,

After a little prototyping, i noticed that the problem actually touches a lot of issues that you can get into when using powershell, so i wanted to share my findings and result with you.

First up, i used the Powershell ISE, the ISE is actually a helpful scripting tool for Powershell itself, but is missing some features and intuitivity that i would expect.
For example, errors seem ambiguous, if you declare a variable and powershell fails to set a value, it will not acknowledge the variable as such and instead use it as a string value without hinting at all at what might have gone wrong.

Now, let me show you what the JSON looks like:

{
"title": "01.jpg",
"description": "",
"imageViews": "0",
"creationTime": {
"timestamp": "1541396665",
"formatted": "05.11.2018, 05:44:25 UTC"
},
"photoTakenTime": {
"timestamp": "1487671512",
"formatted": "21.02.2017, 10:05:12 UTC"
},
(… it continues here but we are not interested in that part)

“photoTakenTime.formatted” contains the value we need.

Starting on the script, my assumption is that image files can have different file types and that the source folder will only contain images and jsons, maybe the script (ps1) itself.
Also Takeout “handily” (?) names the Jsons like so:
myimage.jpg, myimage.jpg.json
Easy peasy! Let’s assign all files in the current working-directory (from which i assume the script will be executed)

$files = Get-ChildItem $pwd -Recurse -Exclude .json,.ps1
if you want to be sure it worked, add another line
Write-Host $files

The interesting part here is that
$pwd retrieves the current workingdirectory and
-Exclude defines what extensions should be ignored, i only want the images, so i exclude json, these values are not strings!

Next up, i’m gong through all the files and creating variables for easy access to the file and the corresponding json file

foreach ($f in $files) {

$fileFullName = $f.FullName $fileJsonName = $f.FullName + ".json"

The following line is commented out, i noticed that this line will retrieve a Json File “Object” in powershell, the parameter “-Depth 3” defines the depth with with the json will be analyzed, a depth of 1 would leave nested elements out.
It turned out that this approach is more complicated, so the line was removed, but the code is still interesting if you want to work with a Json-ified object in powershell and it took me some time to paste together the sequence.

#$fileJsonContent = Get-Content -Raw -Path $fileJsonName | Out-String | ConvertTo-Json -Depth 3 | ConvertFrom-Json

The next line retrieves all of the Json Content, -Raw enables us to load all the nested elements, this generates a Powershell Objects containing the actual properties of the Json
So the object “fileJson” has the property “photoTakenTime” which you could access via “fileJson.photoTakenTime”

$fileJson = Get-Content -Raw -Path $fileJsonName | ConvertFrom-Json

Now the nice part, it’s possible to tell Powershell to retrieve sub-elements of another Powershell object, here the Object has a property photoTakenTime, but it is empty, the expand flag enables us to open up its content and do another select inside of it to get the actual value.
$fileJsonPhotoTakenTime = $fileJson | Select -expand photoTakenTime | Select formatted

The output of the previous command is
"formatted":"21.02.2017, 10:05:12 UTC"
This is, as stated, a Powershell Object which you can access with its properties, in this case, formatted.
We extract the value "21.02.2017, 10:05:12 UTC" by accessing $fileJsonPhotoTakenTime.formatted

Write-Host 'Photo Taken:' $fileJsonPhotoTakenTime.formatted

The timestamp contains “UTC” at the end, Get-Date cannot recognize this value, so we have to remove it.
This results in following string:
"21.02.2017, 10:05:12

$readabletimestamp = $fileJsonPhotoTakenTime.formatted.Replace("UTC","")

Now to set the actual file property, we access the image files “CreationTime” property and set the date from the readable string.

$(Get-Item $fileFullName).CreationTime=$(Get-Date $readabletimestamp)

Write-Host $fileFullName set to CREATION DATE $readabletimestamp

}

Perfect, this will create following beautiful script!

$files = Get-ChildItem $pwd -Recurse -Exclude .json,.ps1
Write-Host $files
foreach ($f in $files) {
$fileFullName = $f.FullName
$fileJsonName = $f.FullName + ".json"
Write-Host $fileFullName
$fileJson = Get-Content -Raw -Path $fileJsonName | ConvertFrom-Json
$fileJsonPhotoTakenTime = $fileJson | Select -expand photoTakenTime | Select formatted
Write-Host 'Photo Taken:' $fileJsonPhotoTakenTime.formatted
$readabletimestamp = $fileJsonPhotoTakenTime.formatted.Replace("UTC","")
$(Get-Item $fileFullName).CreationTime=$(Get-Date $readabletimestamp)
Write-Host $fileFullName set to CREATION DATE $readabletimestamp
}

Either run it by using the ISE and navigating to your folder, change it or put it into the destination folder and enable powershell-scripts running on your system before you run it.

NOTE:
If you opened your file explorer to see the change, or you opened an image property window, close the window and refresh the explorer before opening it.
If you did not refresh the explorer, the file will not “know” that it changed, that’s due to the strange implementation in windows, not my fault 😉

Thanks for your time and as always, i’m happy for constructive criticism and support!