I want to edit jpg files' properties like: comments, title, date taken, camera maker, etc.
I have found libraries to read these data. But I need a free library with examples to edit them.
I'm aware of apache's imaging (sanselan). But I was not able to edit data with it. If you have previously used it yourself, I'd accept that as an answer only if you provide an example code other than the one in their website. Because even when I use their example I was not able to edit any property other than GPS data. After i run the code, file-properties-details still have the same values.
Thanks !
Note: I also tried JHeader (https://sourceforge.net/projects/jheader/) but using it as a process with -cl option still did not changed properties list.
Apache commons Imaging works for me.
I have extended the sample provided here
So obviously my client code looks like this
public static void main(String[] args) throws ImageWriteException, ImageReadException, IOException {
new WriteExifMetadataExample().changeExifMetadata(new File("somefilename.jpg"), new File("result_file.jpg"));
}
and the extended method in WriteExifMetadataExample
public void changeExifMetadata(final File jpegImageFile, final File dst)
throws IOException, ImageReadException, ImageWriteException {
OutputStream os = null;
boolean canThrow = false;
try {
TiffOutputSet outputSet = null;
// note that metadata might be null if no metadata is found.
final ImageMetadata metadata = Imaging.getMetadata(jpegImageFile);
final JpegImageMetadata jpegMetadata = (JpegImageMetadata) metadata;
if (null != jpegMetadata) {
// note that exif might be null if no Exif metadata is found.
final TiffImageMetadata exif = jpegMetadata.getExif();
if (null != exif) {
// TiffImageMetadata class is immutable (read-only).
// TiffOutputSet class represents the Exif data to write.
//
// Usually, we want to update existing Exif metadata by
// changing
// the values of a few fields, or adding a field.
// In these cases, it is easiest to use getOutputSet() to
// start with a "copy" of the fields read from the image.
outputSet = exif.getOutputSet();
}
}
// if file does not contain any exif metadata, we create an empty
// set of exif metadata. Otherwise, we keep all of the other
// existing tags.
if (null == outputSet) {
outputSet = new TiffOutputSet();
}
{
// Example of how to add a field/tag to the output set.
//
// Note that you should first remove the field/tag if it already
// exists in this directory, or you may end up with duplicate
// tags. See above.
//
// Certain fields/tags are expected in certain Exif directories;
// Others can occur in more than one directory (and often have a
// different meaning in different directories).
//
// TagInfo constants often contain a description of what
// directories are associated with a given tag.
//
final TiffOutputDirectory exifDirectory = outputSet
.getOrCreateExifDirectory();
// make sure to remove old value if present (this method will
// not fail if the tag does not exist).
exifDirectory
.removeField(ExifTagConstants.EXIF_TAG_APERTURE_VALUE);
exifDirectory.add(ExifTagConstants.EXIF_TAG_APERTURE_VALUE,
new RationalNumber(3, 10));
}
{
// Example of how to add/update GPS info to output set.
// New York City
final double longitude = -74.0; // 74 degrees W (in Degrees East)
final double latitude = 40 + 43 / 60.0; // 40 degrees N (in Degrees
// North)
outputSet.setGPSInDegrees(longitude, latitude);
}
final TiffOutputDirectory exifDirectory = outputSet
.getOrCreateRootDirectory();
exifDirectory
.removeField(ExifTagConstants.EXIF_TAG_SOFTWARE);
exifDirectory.add(ExifTagConstants.EXIF_TAG_SOFTWARE,
"SomeKind");
os = new FileOutputStream(dst);
os = new BufferedOutputStream(os);
new ExifRewriter().updateExifMetadataLossless(jpegImageFile, os,
outputSet);
canThrow = true;
} finally {
IoUtils.closeQuietly(canThrow, os);
}
}
Please pay attention only to line where I add additional tag
final TiffOutputDirectory exifDirectory = outputSet
.getOrCreateRootDirectory();
exifDirectory
.removeField(ExifTagConstants.EXIF_TAG_SOFTWARE);
exifDirectory.add(ExifTagConstants.EXIF_TAG_SOFTWARE,
"SomeKind");
as a result EXIF tag was properly added
To change the comments tag you can do the following
final TiffOutputDirectory exifDirectory = outputSet.getOrCreateRootDirectory();
exifDirectory.removeField(MicrosoftTagConstants.EXIF_TAG_XPCOMMENT);
exifDirectory.add(MicrosoftTagConstants.EXIF_TAG_XPCOMMENT, "SomeKind");
the full list of available constants is in the package:
org.apache.commons.imaging.formats.tiff.constants
Would an example like this work for you?
I'd assume using packages like org.apache.commons.imaging.util.IoUtils and import org.apache.commons.imaging.Imaging would be of great help to you here.
To change the comments tag you can do the following
final TiffOutputDirectory exifDirectory = outputSet.getOrCreateRootDirectory();
exifDirectory.removeField(MicrosoftTagConstants.EXIF_TAG_XPCOMMENT);
exifDirectory.add(MicrosoftTagConstants.EXIF_TAG_XPCOMMENT, "SomeKind");
the full list of available constants is in the package:
org.apache.commons.imaging.formats.tiff.constants
You need to use Microsoft tag constraints to edit tags
Related
I am using docx4j api for creating docx file. I am successfully copied one docx content to another.
For copy header content, i get header text.
But my requirement is also copy header image. how can i do this?
I am using below code to copy header-
WordprocessingMLPackage source = WordprocessingMLPackage.load(new File(
"D://PoC//Agenda Formats//test.docx"));
RelationshipsPart rp = source.getMainDocumentPart()
.getRelationshipsPart();
Relationship rel = rp.getRelationshipByType(Namespaces.HEADER);
HeaderPart headerPart = (HeaderPart)rp.getPart(rel);
HeaderPart newHeaderPart = new HeaderPart();
newHeaderPart.setContents(XmlUtils.deepCopy(headerPart.getContents()));
return wordprocessingMLPackage.getMainDocumentPart().addTargetPart(
newHeaderPart, AddPartBehaviour.RENAME_IF_NAME_EXISTS);
but this code not copy image. any help is appreciated.
Try something like (untested):
void attachHeader(HeaderPart sourcePart, WordprocessingMLPackage targetPkg) throws Docx4JException {
HeaderPart newHeaderPart = new HeaderPart();
newHeaderPart.setContents(XmlUtils.deepCopy(sourcePart.getContents()));
if (sourcePart.getRelationshipsPart()!=null) {
// clone the rels part
RelationshipsPart rp = sourcePart.getRelationshipsPart();
newHeaderPart.getRelationshipsPart(true).setContents(XmlUtils.deepCopy(rp.getContents()));
// copy/add each part
for (Relationship r : newHeaderPart.getRelationshipsPart().getContents().getRelationship()) {
// get the source part
Part part = sourcePart.getRelationshipsPart().getPart(r.getId());
// ensure it is loaded
if (part instanceof BinaryPart) {
((BinaryPart)part).getBuffer();
}
// You might need to clone this part depending on your use case, but here I'll just attach it to targetPkg
targetPkg.getParts().getParts().put(part.getPartName(), part);
// This simple approach won't work if the target package already contains a part with the same name
// To fix that, you'd need to rename the part (also in the rel)
part.setPackage(targetPkg);
part.setOwningRelationshipPart(newHeaderPart.getRelationshipsPart());
}
}
targetPkg.getMainDocumentPart().addTargetPart(newHeaderPart,
AddPartBehaviour.RENAME_IF_NAME_EXISTS);
}
I am new to Hindsight & Hadoop map reduce concept. I am trying to merge multiple XML files to a single XML file using map reduce program. My intention is to merge each XML file into a destination XML file by prepending and appending file name as start and end tag.
For eg. the below XML's should be merged into a single XML shown below
Input XML Files
<xml><a></a></xml>
<xml><b></b></xml>
<xml><c></c></xml>
Output XML File
<xml>
<File1Name><xml><a></a></xml><File2Name>
<File2Name><xml><b></b></xml><File3Name>
<File3Name><xml><c></c></xml><File3Name>
<xml>
Question 1: Is it possible to map a XML file to each mapper and create a key value pair, key as a file name and value as an each XML file prepending and appending file name as start and end tags and reducer to merge all XML's to a single context and output to XML shown above.
Question 2: How can i get file name as key in mapper code?
Answer 1:
I don't suggest sending just a single XML to a mapper unless the files are over 1gb a piece. You can send a list of xml locations to your mapper and then in your mapper code open each location and extract the data into your output.
Answer 2:
If using azure blob storage, you could list all the blobs in a container and assign them to the input split.
How to create your list of InputSplits:
ArrayList<InputSplit> ret = new ArrayList<InputSplit>();
/*Do this for each path we receive. Creates a directory of splits in this order s = input path (S1,1),(s2,1)…(sN,1),(s1,2),(sN,2),(sN,3) etc..
*/
for (int i = numMinNameHashSplits; i <= Math.min(numMaxNameHashSplits,numNameHashSplits–1); i++) {
for (Path inputPath : inputPaths) {
ret.add(new ParseDirectoryInputSplit(inputPath.toString(), i));
System.out.println(i + ” “+inputPath.toString());
}
}
return ret;
}
}
Once the List<InputSplits> is assembled, each InputSplit is handed to a Record Reader class where each Key, Value, pair is read then passed to the map task. The initialization of the recordreader class uses the InputSplit, a string representing the location of a “folder” of invoices in blob storage, to return a list of all blobs within the folder, the blobs variable below. The below Java code demonstrates the creation of the record reader for each hashslot and the resulting list of blobs in that location.
Public class ParseDirectoryFileNameRecordReader
extends RecordReader<IntWritable, Text> {
private int nameHashSlot;
private int numNameHashSlots;
private Path myDir;
private Path currentPath;
private Iterator<ListBlobItem> blobs;
private int currentLocation;
public void initialize(InputSplit split, TaskAttemptContext context)
throws IOException, InterruptedException {
myDir = ((ParseDirectoryInputSplit)split).getDirectoryPath();
//getNameHashSlot tells us which slot this record reader is responsible for
nameHashSlot = ((ParseDirectoryInputSplit)split).getNameHashSlot();
//gets the total number of hashslots
numNameHashSlots = getNumNameHashSplits(context.getConfiguration());
//gets the input credientals to the storage account assigned to this record reader.
String inputCreds = getInputCreds(context.getConfiguration());
//break the directory path to get account name
String[] authComponents = myDir.toUri().getAuthority().split(“#”);
String accountName = authComponents[1].split(“\\.”)[0];
String containerName = authComponents[0];
String accountKey = Utils.returnInputkey(inputCreds, accountName);
System.out.println(“This mapper is assigned the following account:”+accountName);
StorageCredentials creds = new StorageCredentialsAccountAndKey(accountName,accountKey);
CloudStorageAccount account = new CloudStorageAccount(creds);
CloudBlobClient client = account.createCloudBlobClient();
CloudBlobContainer container = client.getContainerReference(containerName);
blobs = container.listBlobs(myDir.toUri().getPath().substring(1) + “/”, true,EnumSet.noneOf(BlobListingDetails.class), null,null).iterator();
currentLocation = –1;
return;
}
Once initialized, the record reader is used to pass the next key to the map task. This is controlled by the nextKeyValue method, and it is called every time map task starts. The blow Java code demonstrates this.
//This checks if the next key value is assigned to this task or is assigned to another mapper. If it assigned to this task the location is passed to the mapper, otherwise return false
#Override
public boolean nextKeyValue() throws IOException, InterruptedException {
while (blobs.hasNext()) {
ListBlobItem currentBlob = blobs.next();
//Returns a number between 1 and number of hashslots. If it matches the number assigned to this Mapper and its length is greater than 0, return the path to the map function
if (doesBlobMatchNameHash(currentBlob) && getBlobLength(currentBlob) > 0) {
String[] pathComponents = currentBlob.getUri().getPath().split(“/”);
String pathWithoutContainer =
currentBlob.getUri().getPath().substring(pathComponents[1].length() + 1);
currentPath = new Path(myDir.toUri().getScheme(), myDir.toUri().getAuthority(),pathWithoutContainer);
currentLocation++;
return true;
}
}
return false;
}
The logic in the map function is than simply as follows, with inputStream containing the entire XML string
Path inputFile = new Path(value.toString());
FileSystem fs = inputFile.getFileSystem(context.getConfiguration());
//Input stream contains all data from the blob in the location provided by Text
FSDataInputStream inputStream = fs.open(inputFile);
Resources:
http://www.andrewsmoll.com/3-hacks-for-hadoop-and-hdinsight-clusters/ "Hack 3"
http://blogs.msdn.com/b/mostlytrue/archive/2014/04/10/merging-small-files-on-hdinsight.aspx
I am very new to GeoTools. I would like to create a hex grid and save it to a SHP file. But something goes wrong along the way (the saved SHP file is empty). In the debug mode I found that the gird is correctly created and contains a bunch of polygons that make sense. Writing those to a shape file proves to be difficult. I followed the tutorial on GeoTools' website, but that does not quite do it yet. I suspect TYPE to be incorrectly defined, but could not find out how to define it correctly.
Any help of how to store the grid into a SHP file is highly appreciated.
ReferencedEnvelope gridBounds = new ReferencedEnvelope(xMin, xMax, yMin, yMax, DefaultGeographicCRS.WGS84);
// length of each hexagon edge
double sideLen = 0.5;
// max distance between vertices
double vertexSpacing = sideLen / 20;
SimpleFeatureSource grid = Grids.createHexagonalGrid(gridBounds, sideLen, vertexSpacing);
/*
* We use the DataUtilities class to create a FeatureType that will describe the data in our
* shapefile.
*
* See also the createFeatureType method below for another, more flexible approach.
*/
final SimpleFeatureType TYPE = createFeatureType();
/*
* Get an output file name and create the new shapefile
*/
File newFile = new File("D:/test/shape.shp");
ShapefileDataStoreFactory dataStoreFactory = new ShapefileDataStoreFactory();
Map<String, Serializable> params = new HashMap<String, Serializable>();
params.put("url", newFile.toURI().toURL());
params.put("create spatial index", Boolean.TRUE);
ShapefileDataStore newDataStore = (ShapefileDataStore) dataStoreFactory.createNewDataStore(params);
newDataStore.createSchema(TYPE);
/*
* You can comment out this line if you are using the createFeatureType method (at end of
* class file) rather than DataUtilities.createType
*/
newDataStore.forceSchemaCRS(DefaultGeographicCRS.WGS84);
/*
* Write the features to the shapefile
*/
Transaction transaction = new DefaultTransaction("create");
String typeName = newDataStore.getTypeNames()[0];
SimpleFeatureSource featureSource = newDataStore.getFeatureSource(typeName);
if (featureSource instanceof SimpleFeatureStore) {
SimpleFeatureStore featureStore = (SimpleFeatureStore) featureSource;
featureStore.setTransaction(transaction);
try {
featureStore.addFeatures(grid.getFeatures());
transaction.commit();
} catch (Exception problem) {
problem.printStackTrace();
transaction.rollback();
} finally {
transaction.close();
}
} else {
System.out.println(typeName + " does not support read/write access");
}
private static SimpleFeatureType createFeatureType() {
SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();
builder.setName("Location");
builder.setCRS(DefaultGeographicCRS.WGS84); // <- Coordinate reference system
// add attributes in order
builder.add("Polygon", Polygon.class);
builder.length(15).add("Name", String.class); // <- 15 chars width for name field
// build the type
final SimpleFeatureType LOCATION = builder.buildFeatureType();
return LOCATION;
}
I would like to get the metadata from an image file in my local system using Java code
In the attached image you can see the desired data which i would like to pull from java code.
I wrote the below code and do not seem pull the data mentioned in the "Details" tab. The below code's output is and this is not what I look for.
Started ..
Format name: javax_imageio_jpeg_image_1.0
Format name: javax_imageio_1.0
Please give me your ideas. Thanks
try {
ImageInputStream inStream = ImageIO.createImageInputStream(new File("D:\\codeTest\\arun.jpg"));
Iterator<ImageReader> imgItr = ImageIO.getImageReaders(inStream);
while (imgItr.hasNext()) {
ImageReader reader = imgItr.next();
reader.setInput(inStream, true);
IIOMetadata metadata = reader.getImageMetadata(0);
String[] names = metadata.getMetadataFormatNames();
int length = names.length;
for (int i = 0; i < length; i++) {
System.out.println( "Format name: " + names[ i ] );
}
}
} catch (IOException e) {
e.printStackTrace();
}
There's no easy way to do it with the Java Core API. You'd have to parse the image's metadata tree, and interpret the proper EXIF tags. Instead, you can pick up the required code from an existing library with EXIF-parsing capabilities, and use it in yours. For example, I have used the Image class of javaxt, which provides a very useful method to extract GPS metadata from an image. It is as simple as:
javaxt.io.Image image = new javaxt.io.Image("D:\\codeTest\\arun.jpg");
double[] gps = image.getGPSCoordinate();
Plus, javaxt.io.Image has no external dependencies, so you can just use that particular class if you don't want to add a dependency on the entire library.
I suggest you read the EXIF header of the image and then parse the tags for finding the GPS information. In Java there is a great library (called metadata-extractor) for extracting and parsing the EXIF header. Please see the getting started for this library here.
Once you do the first 2 steps in the tutorial, look for the tags starting with [GPS] ([GPS] GPS Longitude, [GPS] GPS Latitude, ...).
Based on #dan-d answer, here is my code (kotlin)
private fun readGps(file: String): Optional<GeoLocation> {
// Read all metadata from the image
// Read all metadata from the image
val metadata: Metadata = ImageMetadataReader.readMetadata(File(file))
// See whether it has GPS data
val gpsDirectories = metadata.getDirectoriesOfType(
GpsDirectory::class.java)
for (gpsDirectory in gpsDirectories) {
// Try to read out the location, making sure it's non-zero
val geoLocation = gpsDirectory.geoLocation
if (geoLocation != null && !geoLocation.isZero) {
return Optional.of(geoLocation)
}
}
return Optional.empty()
}
I'm starting to design an application, that will, in part, run through a directory of files and compare their extensions to their file headers.
Does anyone have any advice as to the best way to approach this? I know I could simply have a lookup table that will contain the file's header signature. e.g., JPEG: \xFF\xD8\xFF\xE0
I was hoping there might be a simper way.
Thanks in advance for your help.
I'm afraid it'll have to be more complicated than that. Not every file type has a header at all, and some (such as RAR) have their characteristic data structures at the end rather than at the beginning.
You may want to take a look at the Unix file command, which does the same job:
http://linux.die.net/man/1/file
http://linux.die.net/man/5/magic
If you don't need to do dirty work on these values (and you don't have linux) you could simply use an external program, like TrID, that is able to do this thing for you.
Maybe you can just work on its output without caring to doing it by yourself.. in anycase if you have just around 20 kinds of files that you will have to manage having a simple lookup table (eg. HashMap<String,byte[]>) is not that bad. Of cours this will work only if desidered file format has a magic number, otherwise you are on your own (or with an external program).
Because of the problem with the missing significant header for some file types (thanks #Michael) I would create a map of extension to a kind of type checker with a simple API like
public interface TypeCheck throws IOException {
public boolean isValid(InputStream data);
}
Now you can code something like
File toBeTested = ...;
Map<String,TypeCheck> typeCheckByExtension = ...;
TypeCheck check = typeCheckByExtension.get(getExtension(toBeTested.getName()));
if (check != null) {
InputStream in = new FileInputStream(toBeTested);
if (check.isValid(in)) {
// process valid file
} else {
// process invalid file
}
in.close();
} else {
// process unknown file
}
The Header check for JPEG for example may look like
public class JpegTypeCheck implements TypeCheck {
private static final byte[] HEADER = new byte[] {0xFF, 0xD8, 0xFF, 0xE0};
public boolean isValid(InputStream data) throws IOException {
byte[] header = new byte[4];
return data.read(header) == 4 && Arrays.equals(header, HEADER);
}
}
For other types with no significant header you can implement completly other type checks.
You can extract the mime type for each file and compare this to a map of mimetype/extension (Map<String, List<String>>, the first String is the mime type, the second is a list of valid extensions).
Resources :
Get the Mime Type from a File
JMimeMagic
On the same topic :
Java - HowTo extract MimeType from a byte[]
Getting A File's Mime Type In Java
You can know the file type of file reading the header using apache tika. Following code need apache tika jar.
InputStream is = MainApp.class.getResourceAsStream("/NetFx20SP1_x64.txt");
BufferedInputStream bis = new BufferedInputStream(is);
AutoDetectParser parser = new AutoDetectParser();
Detector detector = parser.getDetector();
Metadata md = new Metadata();
md.add(Metadata.RESOURCE_NAME_KEY,MainApp.class.getResource("/NetFx20SP1_x64.txt").getPath());
MediaType mediaType = detector.detect(bis, md);
System.out.println("MIMe Type of File : " + mediaType.toString());