Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flattened tags not appearing in returned object #184

Closed
bnjmnrsh opened this issue Jun 3, 2024 · 6 comments · Fixed by pablopfg/evolution-api#2 · May be fixed by pablopfg/evolution-api#16
Closed

Flattened tags not appearing in returned object #184

bnjmnrsh opened this issue Jun 3, 2024 · 6 comments · Fixed by pablopfg/evolution-api#2 · May be fixed by pablopfg/evolution-api#16

Comments

@bnjmnrsh
Copy link

bnjmnrsh commented Jun 3, 2024

Describe the bug

According to the ExifTool docs regarding Structured Information:

When reading, structures are flattened by default, and ExifTool returns one "flattened" tag for each field in the structure... (emphasis the author)

To my understanding, this would imply that 'Flattened' tags such as CreatorAddress should appear in the object returned by exiftool-vendored.

i.e., Adobe Bridge's IPTC Core section of field setting 'Creator: Address' saves the data to the Structured Dataset CreatorContactInfo.CiAdrExtadr. However, the documented behaviour above would imply that the Flattened Tag CreatorAddress would be returned by ExifTool and, by extension, exiftool-vendored (but its not)

{
   ...
   // current behaviour
   "CreatorContactInfo": {
      "CiAdrExtadr": "some address",
      ...
    },
   // But this flattened tag is *not* included by exiftool-vendored
   "CreatorAddress": "some address",
   ...
}

In reading further reading the EXIF Documentation, the output returned from exiftool-vendored appears to strip out flattened tags, as if the -struct flag were passed.

I tried to override this behaviour by not using the provided exiftools instance but created my own:

import { ExifTool } from 'exiftool-vendored'

export const exifExtract = new ExifTool({
  exiftoolArgs: ['-stay_open', 'True', '-@', '-', '-api', 'struct=2'] 
})

-api is an advanced option that enables the use of other options such as struct, which I can only find documented through its Perl implementation.

Digging deeper into ReadTasks.ts, I can see that -json and indeed -struct flags are passed, and are not overwritten by subsequent optional arguments.

To Reproduce

exiftool.read(absFilePath)

Expected behaviour

The notes in the Structured Information docs state that before 8.44, ExifTools "accepted only flattened tags as input" eg default is now to return them in output as well.

Ideally, exiftool-vendored would follow ExifTool defaults; however, this may not be possible for backwards compat reasons. However, hard-coding -struct into the .read() method appears to prevent the modifying of returned output by passing in our own arguments via a new instance of ExifTool (Or perhaps more likely, Im misunderstanding what exiftoolArgs param is for).

Unless Im missing some combination of settings to get the output im wanting (in which case please forgive this issue entirely), I see several possible solutions:

  1. ReadTask: could pass '-api', 'struct=2' instead of -struct. This shouldn't cause any downstream regressions, as the flattened tags are added to the output in addition to structured data.

  2. Create an additional option like exiftoolArgs to specify flattened tags [exiftools default], structured tags [current default], or both.

Option two is preferable but more invasive.

Environment:

  • Version of this library (26.1.0)
  • OS and version: (macOS Monterey)
  • Node.js version (v21.7.3)
@bnjmnrsh bnjmnrsh changed the title Flattend tags no appearing in returned object Flattened tags not appearing in returned object Jun 3, 2024
@bnjmnrsh
Copy link
Author

bnjmnrsh commented Jun 3, 2024

This issue is derived from the following SO discussion:
https://stackoverflow.com/questions/78564547/exiftool-vendored-structured-tags
With thanks to StarGeek for helping to clarify CLI flags.

@mceachen
Copy link
Member

mceachen commented Jun 3, 2024

Thanks for taking the time to explain the situation!

I'm currently in favor of solution #2 as well, but I think there may be nuances here that I'm currently oblivious to.

If you could provide me an example source file, the exiftool commands you expect this library to use for reading and writing, and the expected final file, that might ensure we're on the same page here.

@mceachen
Copy link
Member

mceachen commented Jun 3, 2024

I'm going to release add424c just to ease your testing. If it handles your use case, excellent--but please do send me examples so I can add unit tests exercising this new code.

@bnjmnrsh
Copy link
Author

bnjmnrsh commented Jun 7, 2024

Thanks so much for this addition. I've added my test jpg as an example; I believe that's what you're asking for.

For context and future readers, here are some reduced examples of the output before add424c.

// Behaviour of exif.write() with `-struct` prior to add424c
// Structured data is returned

{
    "Creator": [
      "CREATOR"
    ],
    "AuthorsPosition": "CREATOR JOB TITLE",
    "CreatorContactInfo": {
      "CiAdrCity": "CREATOR CITY",
      "CiAdrCtry": "CREATOR COUNTRY",
      "CiAdrExtadr": "CREATOR ADDRESS",
      "CiAdrPcode": "CREATOR POST CODE",
      "CiAdrRegion": "CREATOR STATE/PROVINCE",
      "CiEmailWork": "CREATOR EMAIL(S)",
      "CiTelWork": "CREATOR PHONE(S)",
      "CiUrlWork": "CREATOR WEBSITE(S)"
    },
    "Headline": "HEADLINE",
    "Description": "DESCRIPTION",
    "AltTextAccessibility": "ALT TEXT",
    "ExtDescrAccessibility": "EXTENDED DESCRIPTION",
    "Subject": [
      "SOME KEYWORDS"
    ],
    ...
}


// ExifTool default behaviour (flattened tags returned instead of structured data) no flags passed. Tags like 'Creator' and 'Subject' are now flattened strings instead of arrays. 

{
    "Creator": "CREATOR",
    "AuthorsPosition": "CREATOR JOB TITLE",
    "CreatorAddress": "CREATOR ADDRESS",
    "CreatorCity": "CREATOR CITY",
    "CreatorRegion": "CREATOR STATE/PROVINCE",
    "CreatorPostalCode": "CREATOR POST CODE",
    "CreatorCountry": "CREATOR COUNTRY",
    "CreatorWorkTelephone": "CREATOR PHONE(S)",
    "CreatorWorkEmail": "CREATOR EMAIL(S)",
    "CreatorWorkURL": "CREATOR WEBSITE(S)",
    "Headline": "HEADLINE",
    "Description": "DESCRIPTION",
    "AltTextAccessibility": "ALT TEXT",
    "ExtDescrAccessibility": "EXTENDED DISCRIPTION",
    "Subject": "SOME KEYWORDS",
    ...
}


// equivalent to  '-api struct=0'
{
    "Creator": "CREATOR",
    "AuthorsPosition": "CREATOR JOB TITLE",
    "CreatorAddress": "CREATOR ADDRESS",
    "CreatorCity": "CREATOR CITY",
    "CreatorRegion": "CREATOR STATE/PROVINCE",
    "CreatorPostalCode": "CREATOR POST CODE",
    "CreatorCountry": "CREATOR COUNTRY",
    "CreatorWorkTelephone": "CREATOR PHONE(S)",
    "CreatorWorkEmail": "CREATOR EMAIL(S)",
    "CreatorWorkURL": "CREATOR WEBSITE(S)",
    "Headline": "HEADLINE",
    "Description": "DESCRIPTION",
    "AltTextAccessibility": "ALT TEXT",
    "ExtDescrAccessibility": "EXTENDED DISCRIPTION",
    "Subject": "SOME KEYWORDS",
    ...
}

// equivalent to  '-api struct=1'
{
    "Creator": [
      "CREATOR"
    ],
    "AuthorsPosition": "CREATOR JOB TITLE",
    "CreatorContactInfo": {
      "CiAdrCity": "CREATOR CITY",
      "CiAdrCtry": "CREATOR COUNTRY",
      "CiAdrExtadr": "CREATOR ADDRESS",
      "CiAdrPcode": "CREATOR POST CODE",
      "CiAdrRegion": "CREATOR STATE/PROVINCE",
      "CiEmailWork": "CREATOR EMAIL(S)",
      "CiTelWork": "CREATOR PHONE(S)",
      "CiUrlWork": "CREATOR WEBSITE(S)"
    },
    "Headline": "HEADLINE",
    "Description": "DESCRIPTION",
    "AltTextAccessibility": "ALT TEXT",
    "ExtDescrAccessibility": "EXTENDED DISCRIPTION",
    "Subject": [
      "SOME KEYWORDS"
    ],
    ...
}

// equivalent to  '-api struct=2'
{
  
    "Creator": [
      "CREATOR"
    ],
    "AuthorsPosition": "CREATOR JOB TITLE",
    "CreatorContactInfo": {
      "CiAdrCity": "CREATOR CITY",
      "CiAdrCtry": "CREATOR COUNTRY",
      "CiAdrExtadr": "CREATOR ADDRESS",
      "CiAdrPcode": "CREATOR POST CODE",
      "CiAdrRegion": "CREATOR STATE/PROVINCE",
      "CiEmailWork": "CREATOR EMAIL(S)",
      "CiTelWork": "CREATOR PHONE(S)",
      "CiUrlWork": "CREATOR WEBSITE(S)"
    },
    "CreatorAddress": "CREATOR ADDRESS",
    "CreatorCity": "CREATOR CITY",
    "CreatorRegion": "CREATOR STATE/PROVINCE",
    "CreatorPostalCode": "CREATOR POST CODE",
    "CreatorCountry": "CREATOR COUNTRY",
    "CreatorWorkTelephone": "CREATOR PHONE(S)",
    "CreatorWorkEmail": "CREATOR EMAIL(S)",
    "CreatorWorkURL": "CREATOR WEBSITE(S)",
    "Headline": "HEADLINE",
    "Description": "DESCRIPTION",
    "AltTextAccessibility": "ALT TEXT",
    "ExtDescrAccessibility": "EXTENDED DESCRIPTION",
    "Subject": [
      "SOME KEYWORDS"
    ],
    ...
}

FIELD_TEST

@bnjmnrsh
Copy link
Author

bnjmnrsh commented Jun 7, 2024

In working with add424c, I believe, given the documented options for the new struct property, that undefined is not behaving as expected, at least in the .read() operations Ive tried.

From the ExifTool docs:

undef = Same as 0 for reading and 2 for copying

undef in ExifTool is the default behaviour; however, in my tests, passing the undefined global property, or quoted as a string, produces the same results as 1 in that no flattened tags are returned.

Here is my setup

export const exifExtract = new ExifTool({
  struct: undefined // undefined | 0 | 1 | 2
})

Skimming over ReadTask.ts It looks like a culprit is on line 104

console.log(opts.struct) // 1 not undefined!
if (opts.struct != null) {
      args.push("-api", "struct=" + opts.struct)
    }

The undefined value has been overwritten at this stage. Regardless, I believe there would be an issue with the strictness of the null check against undefined. However even if the string 'undefined' or 'undef' are passed the output still doesn't include flattened tags, so something else downstream is at issue.

Generally, I'd say that following the ExifTools pattern using the string 'undef' as a check would sidestep a lot of strict check complexity and bring the API into closer alignment to ExifTools.

It might also be worth mentioning in the docs/ts def that exiftool-vendored defaults to 1 / -struct output for backward compat reasons, while ExifTool produces flattened tags by default. I, for one, spent a long time bumping into that difference before realising it was a false assumption on my part.

In the long term, i.e., the next major version number, it may be worth considering making undef the default behaviour to better align this project with ExifTool's default behaviour.

bnjmnrsh added a commit to bnjmnrsh/exif-extract that referenced this issue Jun 7, 2024
…output.

Reason for Commit: Prior to 26.2.0, exiftool-vendored passed the `-struct` flag which forced structured output without falttened tags. Raised in issue #184, the `struct` porperty has been added when creating new instances. However, its 'undefined' value which should align to ExifTools default output is currently broken. I believe that this would be the optimal behavior, however 0 or 2 would suffice. We will want to be able to pass this to the user in a later refactor.

Summary:
Flatend tags are now able to be included in output - Yay!

Notes:
photostructure/exiftool-vendored.js#184

photostructure/exiftool-vendored.js@add424c
@mceachen
Copy link
Member

Ah, crap! I didn't think about the undefined getting overwritten by the default options. I took your resolution suggestion: ece7bcd

I'm not going to change the default value for struct to be "undef" -- that would break literally all existing library users.

I'll check out the test image you attached.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants