Extract exif data solution brainstorming

So I’m in the process of migrating image storage over the Cloudflare images. Initial setup isn’t particularly elegant, but I can’t contain client review files in a git repo style deployment.

Anyways…so basically I’m stripping all the large image assets out and sending them to get served.

I was getting real spoiled extracting image exif data with hugo processing though. And I plan to store my original image export in the page bundle and not commit them, life is just easier if I store it there.

Before I beat my head into a wall trying to figure this out…

Idea 1
Would it even be possible to process the image locally within hugo, and get the exif data, somehow save that json output file since the git deployment wont have the file to process on the server?

Idea 2
Ignore the image, I’m in the window, run some sort of exif tool script to output the formatted exif data for a json file or just to paste into the front matter?

Idea #2 sounds about right.

You could use Phil Harvey’s ExifTool to create a JSON file in your project’s data directory prior to each build. For example:

exiftool -recurse -json content/ > data/exif.json

data/exif.json

[{
  "SourceFile": "content/post/post-1/a.jpg",
  "FileName": "a.jpg",
  "Directory": "content/post/post-1",
  "FileSize": "18 kB",
  "ImageDescription": "Image A",
  "ImageWidth": 600,
  "ImageHeight": 400,
  ...
},
{
  "SourceFile": "content/post/post-1/b.jpg",
  "FileName": "b.jpg",
  "Directory": "content/post/post-1",
  "FileSize": "155 kB",
  "ImageDescription": "Image B",
  "ImageWidth": 600,
  "ImageHeight": 400,
  ...
}]
1 Like

Thanks. Exiftool was what I intended to refer to. I’ve used it for years, but for really surface level stuff.

I’ll take a crack at this tonight, see if I can get it working like how I see it in my head.

Does it have to be in the data directory? or can it live in a page bundle?

Data files can live in a page bundle, to be accessed as a page resource.

But in your scenario, it is much simpler to create a single data file using the one-liner from my previous comment.

To create multiple data files (one for each page bundle) you will need to write a script that runs exiftool recursively in each page bundle. That seems like a lot of work for a limited return.

Still chewing on this one.

I kind of imagined it more like I run hugo new photos/2022-06-24-post-title/ and the post bundle builds, I drop the image into that (I’m not checking the jpg into git, so I can’t process anything from the photo on build), from there I run terminal from that director, process some form of exif read on the photograph, extracts the json file and saves it to the root of the post-bundle.

The json is then processed on build to input the correct data in the page to accompany the photograph, which lives in cloudflare images and has the ID matching the filename, assumed I’d upload from the CLI with wrangler to assign the custom ID. I never have the leave the IDE at least.

The json lives with the post/content if I ever move things around in a dramatic fashion.

One data file works too. Just isn’t how I wanted to intuitively create a solution.

Workflow:

  1. Create leaf bundle

    hugo new content/photos/2022-06-24-post-title
    
  2. Copy JPEG images to leaf bundle

  3. Create data file

    cd content/photos/2022-06-24-post-title
    exiftool -json *.jpg > exif.json
    

Resulting structure:

content/
├── photos/
│   └── 2022-06-24-post-1/
│       ├── a.jpg
│       ├── b.jpg
│       ├── exif.json
│       └── index.md
└── _index.md
exif.json
[{
  "SourceFile": "a.jpg",
  "ExifToolVersion": 11.88,
  "FileName": "a.jpg",
  "Directory": ".",
  "FileSize": "17 kB",
  "FileModifyDate": "2022:06:24 10:48:13-07:00",
  "FileAccessDate": "2022:06:24 10:51:09-07:00",
  "FileInodeChangeDate": "2022:06:24 10:48:13-07:00",
  "FilePermissions": "rw-------",
  "FileType": "JPEG",
  "FileTypeExtension": "jpg",
  "MIMEType": "image/jpeg",
  "ExifByteOrder": "Big-endian (Motorola, MM)",
  "ImageDescription": "A kitten",
  "XResolution": 72,
  "YResolution": 72,
  "ResolutionUnit": "inches",
  "YCbCrPositioning": "Centered",
  "ImageWidth": 600,
  "ImageHeight": 400,
  "EncodingProcess": "Baseline DCT, Huffman coding",
  "BitsPerSample": 8,
  "ColorComponents": 3,
  "YCbCrSubSampling": "YCbCr4:2:0 (2 2)",
  "ImageSize": "600x400",
  "Megapixels": 0.240
},
{
  "SourceFile": "b.jpg",
  "ExifToolVersion": 11.88,
  "FileName": "b.jpg",
  "Directory": ".",
  "FileSize": "16 kB",
  "FileModifyDate": "2022:06:24 10:48:13-07:00",
  "FileAccessDate": "2022:06:24 10:51:09-07:00",
  "FileInodeChangeDate": "2022:06:24 10:48:13-07:00",
  "FilePermissions": "rw-------",
  "FileType": "JPEG",
  "FileTypeExtension": "jpg",
  "MIMEType": "image/jpeg",
  "ExifByteOrder": "Big-endian (Motorola, MM)",
  "ImageDescription": "Another kitten",
  "XResolution": 72,
  "YResolution": 72,
  "ResolutionUnit": "inches",
  "YCbCrPositioning": "Centered",
  "ImageWidth": 600,
  "ImageHeight": 400,
  "EncodingProcess": "Baseline DCT, Huffman coding",
  "BitsPerSample": 8,
  "ColorComponents": 3,
  "YCbCrSubSampling": "YCbCr4:2:0 (2 2)",
  "ImageSize": "600x400",
  "Megapixels": 0.240
}]

layouts/photos/single.html
{{ $exif := dict }}
{{ with .Resources.Get "exif.json" }}
  {{ $exif = . | transform.Unmarshal }}
{{ end }}

{{ range .Resources.Match "*.jpg" }}
  {{ $imageDescription := "" }}
  {{ $imageSize := "" }}
  {{ with where $exif "SourceFile" "eq" .Name }}
    {{ with index . 0 }}
      {{ $imageDescription = .ImageDescription }}
      {{ $imageSize = .ImageSize }}
    {{ end }}
  {{ end }}
  <figure>
    <img src="{{ .RelPermalink }}" width="{{ .Width }}" height="{{ .Height }}" alt="{{ $imageDescription }}">
      <figcaption>{{ $imageDescription }} ({{ $imageSize }})</figcaption>
  </figure>
{{ end }}

You would, obviously, need to modify the template code to use your Cloudflare images instead of the locally stored image(s).

2 Likes

Okay, that’s pretty close to how i had it in my brain I think. I just fire the image off with curl to cloudflare images, assign the ID as the filename, and then copy / paste that into the front matter.

Not ideal, it’s a few steps. But it’s like 5 seconds of effort.

As always, thanks for the feedback. It’s awesome to be able to bounce the concept off the community and get responses back like yours. Always more than I expect.

3 Likes

Would it make more sense to process the json as a short code within the post when I retrieve the image/filename ID from cloudflare images?

I spent far longer than I would like admit trying to get this to work. But I fear my programming experience just isn’t far enough along to not have to ask for help.

I grasp the everything up to the single.html template portion, where I need to make the major modifications.

        {{ with .Resources.GetMatch "exif.json" }}
        <section>
            {{ .Content }}
        </section>
        {{ end }}

That currently returns all of the json data. So at least I know that is working. I’ll keep tinkering and see if I can formulate a question that makes sense. Your example code has been a huge help thus far!

I think you’ve shorted my brain @jmooring! haha. For the life of me I can’t seem to reverse your code and bend it to my will.

I mostly don’t understand hot to modify off the jpg’s in your example, and what the with where is doing here.

{{ with where $exif "SourceFile" "eq" .Name }}
    {{ with index . 0 }}
      {{ $imageDescription = .ImageDescription }}
      {{ $imageSize = .ImageSize }}
    {{ end }}

Could I just bypass the jpg entirely and call in exif data if an exif file exists?

I don’t understand the question. Perhaps you can share what you have tried that isn’t working.

Sorry, I was trying to reverse what you were doing and understand it better. For simplicity sake, I skipped making it part of a shortcode and just placed your example into /post-types/singles.html

I started down this path trying to extract just the information I need out of the exif.json, and if a exif.json is present, an article section with there exif is created with the data. Whenever I attempted to lift out the
{{ range .Resources.Match "*.jpg" }} because I’m ignoring the jpg after the json is created.

What I can’t seem to extract is just how to call a data field back from the json stored in the page bundle.

I guess this is the simplest working version I’ve implimented

    {{ with .Resources.GetMatch "exif.json" }}
    {{ $exif := .Content | transform.Unmarshal }}
        {{ range $exif }}
        <section class="exif">
            <ul>
                <li>{{ .SourceFile }}</li>
                <li>{{ .DateTimeOriginal }}</li>
                <li>{{ .FocalLength }}</li>
                <li>{{ .ExposureTime }}</li>
                <li>{{ .FNumber }}</li>
                <li>{{ .ISO }}</li>
            </ul>
        </section>
        {{ end }}
    {{ end }}

Do you still have a question?

I guess not. I was confused how some parts of your code worked and what they were trying to do, but I couldn’t figure out how to word it.

Anyways, I think that does what I’m looking for. Thanks again! Always cool seeing how detailed of a response you tend to leave.

1 Like

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.