Giter VIP home page Giter VIP logo

Comments (17)

haraldk avatar haraldk commented on May 25, 2024 1

Hi Steinar,

Short answer: JPEGImage10Metadata is (currently) read-only, see the isReadOnly method. Invoking mergeTree/setFromTree on an instance, will fail.

Long answer: You should still be able to achieve what you want, see #668. πŸ˜€
Especially this comment where I try to outline a workaround. I think it worked for jAlbum.

Try that first, but if you can't make it work, let me know, and we'll see if I can help.

from twelvemonkeys.

steinarb avatar steinarb commented on May 25, 2024 1

I did this instead, and then I got further:

            try(var imageOutputStream = new MemoryCacheImageOutputStream(bytes)) {
                new TIFFWriter().write(entries, imageOutputStream);
            }

Now the EXIF metadata parses without throwing an EOFException.

Current problem: my replacement of the JFIF comment didn't survive the addition of modifying EXIF comments: https://gist.github.com/steinarb/da81e5d71a6d9e44058ef02598972f3a#file-oldalbumserviceprovider-java-L30

from twelvemonkeys.

steinarb avatar steinarb commented on May 25, 2024 1

from twelvemonkeys.

steinarb avatar steinarb commented on May 25, 2024

Thanks, Harald! The workaround of using the default metadata of the writer and replacing the content with the modified metadata tree of the writer worked. Working code example below.

Next up for me is to find the exif segment of the metadata, if present, or create one of there isn't one, and then insert 3 values:

  1. last modified date
  2. the image description
  3. the user comment

Can I do this in a simple way from the existing metadata object (where I can see the exif data in the debugger)?
Or do I have to read the exif segment from the input stream?
And when/if I have the exif segent can I easily add it to the default metadata object?
I assume I will have to insert it into the tree?

    File downloadImageUrlToTempFile(AlbumEntry albumEntry, Path tempDir) {
        var imageUrl = albumEntry.getImageUrl();
        if (imageUrl == null || imageUrl.isEmpty()) {
            throw new OldAlbumException(String.format("Unable to download album entry matching id=%d, imageUrl is missing", albumEntry.getId()));
        }

        var fileName = findFileNamePartOfUrl(imageUrl);
        var tempfile = tempDir.resolve(fileName).toFile();
        IIOImage image = null;
        ImageWriter writer = null;
        try {
            HttpURLConnection connection = getConnectionFactory().connect(imageUrl);
            connection.setRequestMethod("GET");
            try(var inputStream = ImageIO.createImageInputStream(connection.getInputStream())) {
                var readers = ImageIO.getImageReaders(inputStream);
                if (readers.hasNext()) {
                    var reader = readers.next();
                    writer = ImageIO.getImageWriter(reader);
                    reader.setInput(inputStream);
                    image = reader.readAll(0, null);
                }
            }
        } catch (IOException e) {
            throw new OldAlbumException(String.format("Unable to download album entry matching id=%d from url=\"%s\"", albumEntry.getId(), albumEntry.getImageUrl()), e);
        }

        var metadata = image.getMetadata();
        var metadataAsTree = metadata.getAsTree("javax_imageio_1.0");
        findJfifCommentNode(metadataAsTree)
            .ifPresent(node -> node.setAttribute("value", albumEntry.getDescription()));

        try (var outputStream = ImageIO.createImageOutputStream(new FileOutputStream(tempfile))){
            writer.setOutput(outputStream);
            var param = writer.getDefaultWriteParam();
            var modifiedMetadata = writer.getDefaultImageMetadata(ImageTypeSpecifiers.createFromRenderedImage(image.getRenderedImage()), param);
            modifiedMetadata.setFromTree("javax_imageio_1.0", metadataAsTree);
            image.setMetadata(modifiedMetadata);
            writer.write(image);
            Files.setLastModifiedTime(tempfile.toPath(), FileTime.from(albumEntry.getLastModified().toInstant()));
            return tempfile;
        } catch (IOException e) {
            throw new OldAlbumException(String.format("Unable to save local copy of album entry matching id=%d from url=\"%s\"", albumEntry.getId(), albumEntry.getImageUrl()), e);
        }
    }

    Optional<IIOMetadataNode> findJfifCommentNode(Node metadataAsTree) {
        return StreamSupport.stream(iterable(metadataAsTree.getChildNodes()).spliterator(), false)
            .filter(n -> "Text".equals(n.getNodeName()))
            .findFirst()
            .flatMap(n -> StreamSupport.stream(iterable(n.getChildNodes()).spliterator(), false).findFirst());
    }

    public static Iterable<IIOMetadataNode> iterable(final NodeList nodeList) {
        return () -> new Iterator<IIOMetadataNode>() {

                private int index = 0;

                @Override
                public boolean hasNext() {
                    return index < nodeList.getLength();
                }

                @Override
                public IIOMetadataNode next() {
                    if (!hasNext())
                        throw new NoSuchElementException();
                    return (IIOMetadataNode) nodeList.item(index++);
                }
            };
    }

from twelvemonkeys.

steinarb avatar steinarb commented on May 25, 2024

Adding exif to the tree is possibly covered here? #586 (comment)

(Seems a little cumbersome...?) Is it easier if the exif segment already exists I wonder?

(Edit: yes it easier if the exif segment exists, it says so in the text of the comment above the code example which (I think describes) how to create an exif segment from scratch)

from twelvemonkeys.

steinarb avatar steinarb commented on May 25, 2024

Tried the approach outlined in #586 (comment) and https://stackoverflow.com/q/36029295 but I got the following error message when setting the metadata:

Caused by: javax.imageio.metadata.IIOInvalidTreeException: Invalid node: markerSequence

Here is what I've tried to do: https://gist.github.com/steinarb/0ab59ad7c17b7ac15b757dd67368dad9

Here is the complete stack trace: https://gist.github.com/steinarb/a2ae1019aa90a4ade2ca49387f6135b9

I'm sure there is a simple error copying from the examples, but I'm unable to sport the error.

from twelvemonkeys.

haraldk avatar haraldk commented on May 25, 2024

Sorry, no time right now, but: javax_imageio_1.0 is the format neutral metadata format. You need the β€œJPEG native” format to access/write Exif, ie javax_imageio_jpeg_image_1.0.

from twelvemonkeys.

haraldk avatar haraldk commented on May 25, 2024

Did changing to the javax_imageio_jpeg_image_1.0 metadata format help? πŸ˜€

Your code does a lot, so it would probably help to organize things a little different to make things easier to understand... But I'm fairly sure this was the reason for the exception (as there is no markerSequece in the "plug-in neutral" format).

The "plug-in neutral" format is good for getting a few essential values in a standard way, but it's not so good if you need to mess with format specific things like Exif in JPEG. I believe the JPEG native format always has a markerSequence node, but I guess it doesn't hurt to be on the safe side...

javax_imageio_1.0 DTD
javax_imageio_jpeg_image_1.0 DTD

from twelvemonkeys.

steinarb avatar steinarb commented on May 25, 2024

from twelvemonkeys.

steinarb avatar steinarb commented on May 25, 2024

Ok, now I'm back to getting java.io.EOFException.

The exception comes when I'm trying to read the metadata of the transformed image in the test and it comes when trying to read a stream containing just "Exif" followed by to 0-byte values.

I.e. it looks like the line doesn't do anything:

            new TIFFWriter().write(entries, new MemoryCacheImageOutputStream(bytes));

The size of the bytes ByteArrayOutputStream doesn't change in the above line.

This is the current version of the complete code: https://gist.github.com/steinarb/251e02cda3de13c9cc2845a2c9020f4a#file-oldalbumserviceprovider-java-L35

from twelvemonkeys.

haraldk avatar haraldk commented on May 25, 2024

The latest version (with try-with-resources) closes (and implicitly flushes) the stream, to commit from the memory cache to the underlying ByteArrayOuputStream. The previous code would probably also have worked if you explicitly invoked flush() on the MemoryCacheImageOutputStream.

Not sure about why the comment didn't survive. As mentioned, there's too much "going on" for me to fully understand what's happening. Try writing this as a test case, and I'll be able to help you better. πŸ˜€

Are you sure the findJfifCommentNode method works as intended? Are you sure there was an existing comment in the file to replace in the first place? Are you sure the ifPresent block is even executed?

PS: This code won't work as intended:

        var markerSequence = (IIOMetadataNode) metadataAsTree.getElementsByTagName("markerSequence").item(0);
        if (markerSequence == null) {
            markerSequence = new IIOMetadataNode("markerSequence");
            metadataAsTree.appendChild(markerSequence);
        }

If there is no "markerSequence" node, the node list will be empty, and there won't be an item 0... You'll either get a node or an exception, markerSequence will never be null.

from twelvemonkeys.

steinarb avatar steinarb commented on May 25, 2024

from twelvemonkeys.

haraldk avatar haraldk commented on May 25, 2024

I'm trying to find the right node in the new tree. As said I can't see
the tree nodes well in the debugger and the debugger goes a bit crazy
when I try to navigate them.

One thing this odd XML-ish node structure is good for, is that it's.. well.. XML. πŸ˜€

You can dump the whole tree like this:

 new XMLSerializer(System.out, "UTF-8").serialize(tree, false);

(using com.twelvemonkeys.xml. XMLSerializer, other serializers will probably do fine too..)

The only thing you won't see there it the userObjects, because the javax.imageio API invented userObject instead of reusing userData, not really sure why...

I found that the comment was put into a node named "com" by stepping
through the build of the native tree in the debugger.

But I found a node named "com" directly under markerSequence and tried
replacing it, but that probably wasn't the right one.

According to the DTD I referenced above, the com (JFIF comment) marker is a child of markerSequence. It can't appear anywhere else.

from twelvemonkeys.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.