Issue
I'm creating an image upload app where the user can edit image metadata. The current way I'm handling file upload is with multipart file.
fun handleFileUpload(@RequestParam files: Array<MultipartFile>): String {
This works, but I lose access to all file metadata for the images. I'm wondering what the ideal way to do this so I can access metadata? I know the Java.io.File class has metadata access.
I'd prefer to maintain drag and drop functionality on my Angular front end.
Solution
File meta data is not image meta data...
In java we can:
For Kotlin (backend) I found:
- Scrimmage, which led me to:
- ...
Let's try/compare:
package com.satckoverflow.imagemeta
import com.drew.imaging.ImageMetadataReader
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.multipart.MultipartFile
import org.w3c.dom.NamedNodeMap
import org.w3c.dom.Node
import javax.imageio.ImageIO
import javax.imageio.ImageReader
import kotlin.text.StringBuilder
fun main(args: Array<String>) {
runApplication<ImageMetaDataApplication>(*args)
}
@SpringBootApplication
@Controller("/")
class ImageMetaDataApplication {
@GetMapping
fun show(): String {
return "index"
}
@PostMapping
fun handleFileUpload(@RequestParam files: Array<MultipartFile>, model: Model): String {
val images = ArrayList<MyImage>()
for (file in files) {
// adopted from http://johnbokma.com/java/obtaining-image-metadata.html
val iis = ImageIO.createImageInputStream(file.inputStream)
val readers: Iterator<ImageReader> = ImageIO.getImageReaders(iis)
if (readers.hasNext()) {
// pick the first available ImageReader
val reader = readers.next()
// attach source to the reader
reader.setInput(iis, true)
// read metadata of first image
val metadata = reader.getImageMetadata(0)
val names = metadata.metadataFormatNames
val length = names.size
for (i in 0 until length) {
images.add(MyImage(file.originalFilename, names[i], displayMetadata(metadata.getAsTree(names[i]))))
}
}
}
model.addAttribute("images", images)
return "index"
}
@PostMapping(params = ["3rdparty"])
fun handleFileUploadScrimage(@RequestParam files:
Array<MultipartFile>, model: Model): String {
val images = ArrayList<MyImage>()
for (file in filesS) {
val meta = ImageMetadataReader.readMetadata(file.inputStream, file.size)
meta.directories.forEach { dir ->
dir.tags.forEach { tag ->
images.add(MyImage(file.originalFilename, dir.name, tag.toString()))
}
}
}
model.addAttribute("images", images)
return "index"
}
}
// adopted from http://johnbokma.com/java/obtaining-image-metadata.html
private fun displayMetadata(root: Node): String {
val sb = StringBuilder()
displayMetadata(root, 0, sb)
return sb.toString()
}
private fun indent(level: Int, sb: StringBuilder) {
for (i in 0 until level) sb.append(" ")
}
private fun displayMetadata(node: Node, level: Int, sb: StringBuilder): String {
// print open tag of element
if (level > 0) {
sb.append(System.lineSeparator())
}
indent(level, sb)
sb.append('<').append(node.nodeName)
val map: NamedNodeMap = node.attributes
// print attribute values
val length = map.length
for (i in 0 until length) {
val attr: Node = map.item(i)
sb.append(' ').append(attr.nodeName).append('=').append('"').append(attr.nodeValue).append('"')
}
var child = node.firstChild
if (child == null) {
// no children, so close element and return
sb.append("/>")
} else {
// children, so close current tag
sb.append('>')
while (child != null) {
// print children recursively
displayMetadata(child, level + 1, sb)
child = child.nextSibling
}
// print close tag of element
sb.append(System.lineSeparator())
indent(level, sb)
sb.append("</").append(node.nodeName).append('>')
}
return sb.toString()
}
// adopted-end
data class MyImage(val name: String?, val key: String?, val vali: String?)
with this src/main/resources/templates/index.html (sorry for thymeleaf, "no design", and only 2 inputs..):
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Image meta data upload Example</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<style>
table td, table td * {
vertical-align: top;
}
pre {
white-space: pre-wrap;
}
</style>
</head>
<body>
<h2>Standard java</h2>
<form action="#" method="post" th:action="@{/}" enctype="multipart/form-data">
<label for="files0">File 1:</label>
<input type="file" id="files0" name="files" accept="image/*"/>
<label for="files1">File 2:</label>
<input type="file" id="files1" name="files" accept="image/*"/>
<input type="submit"/>
</form>
<hr/>
<h2>additional lib</h2>
<form action="#" method="post" th:action="@{/}" enctype="multipart/form-data">
<label for="files2">File 1:</label>
<input type="file" id="files2" name="files" accept="image/*"/>
<label for="files3">File 2:</label>
<input type="file" id="files3" name="files" accept="image/*"/>
<input type="submit"/>
<input type="hidden" name="3rdparty" />
</form>
<hr/>
<table th:if="${images}">
<thead>
<tr>
<th>Name</th>
<th>Meta-Data-Key</th>
<th>Meta-Data-Value</th>
</tr>
</thead>
<tbody>
<tr th:each="img: ${images}">
<td th:text="${img.name}">some file</td>
<td th:text="${img.key}">some text</td>
<td>
<pre th:text="${img.vali}">some text</pre>
</td>
</tr>
</tbody>
</table>
</body>
</html>
We can get something, like (without additional libs, but with some xml-crawling code) :
And with:
<dependency>
<groupId>com.drewnoakes</groupId>
<artifactId>metadata-extractor</artifactId>
<version>2.16.0</version>
</dependency>
We get (with few lines) something more like that:
Thanks to blog for javax.imageio
"crawling".
Answered By - xerx593