Comments (17)
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.
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.
from twelvemonkeys.
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:
- last modified date
- the image description
- 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.
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.
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.
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.
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.
from twelvemonkeys.
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.
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.
from twelvemonkeys.
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 userObject
s, 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)
- ImageIO.read return null HOT 1
- Add OpenSSF Scorecard Workflow HOT 1
- JPEG encoded TIFF: Metadata components != number of destination bands HOT 7
- java.lang.ArrayIndexOutOfBoundsException: 2 with a JPEG HOT 3
- Read huge jpg file ,get error: javax.imageio.IIOException: Can not read image of the size 54948 by 45620 HOT 2
- It would be nice if the twelvemonkeys jars were OSGi bundles HOT 26
- ClassCastException ImageReadParam BigTIFF reading HOT 2
- jakarta.servlet support HOT 2
- Allow tiff writing to use an explicit photometric interpretation option HOT 7
- Enable a SAST Tool
- CVE-2023-5129 - Vulnerability in libwebp huffman table implementation HOT 2
- Question: compatibility with org.apache.commons:commons-imaging ? HOT 2
- Features stopped working after upgrading to JDK 17 HOT 8
- It would be nice if Twelvemonkeys had support for EXIF tag 0xa436 Title HOT 10
- Problematic inconsistence in JPEG color space detection: TwelveMonkey vs standard Java API HOT 9
- Wrong colors with CMYK JPEG HOT 1
- Webp: cannot decode grayscale images HOT 5
- NPE while read a PSD file HOT 3
- JPEG: Preserve metadata when writing CMYK HOT 5
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google β€οΈ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from twelvemonkeys.