4
4
import java .awt .image .BufferedImage ;
5
5
import java .io .BufferedOutputStream ;
6
6
import java .io .ByteArrayOutputStream ;
7
- import java .io .File ;
8
7
import java .io .IOException ;
8
+ import java .io .InputStream ;
9
9
import java .io .OutputStream ;
10
10
import java .nio .file .Files ;
11
+ import java .nio .file .Path ;
11
12
import java .nio .file .Paths ;
13
+ import java .nio .file .attribute .FileTime ;
12
14
import java .time .ZoneOffset ;
13
15
import java .time .ZonedDateTime ;
14
16
import java .time .format .DateTimeFormatter ;
15
17
import java .util .concurrent .ArrayBlockingQueue ;
16
18
19
+ import javax .annotation .Nonnull ;
20
+ import javax .annotation .Nullable ;
17
21
import javax .imageio .ImageIO ;
18
22
19
23
import org .apache .commons .imaging .ImageReadException ;
20
24
import org .apache .commons .imaging .ImageWriteException ;
21
25
import org .apache .commons .imaging .common .RationalNumber ;
22
26
import org .apache .commons .imaging .formats .jpeg .exif .ExifRewriter ;
27
+ import org .apache .commons .imaging .formats .jpeg .xmp .JpegXmpRewriter ;
23
28
import org .apache .commons .imaging .formats .tiff .constants .ExifTagConstants ;
24
29
import org .apache .commons .imaging .formats .tiff .constants .GpsTagConstants ;
25
30
import org .apache .commons .imaging .formats .tiff .write .TiffOutputDirectory ;
@@ -73,7 +78,7 @@ public void run() {
73
78
this .monitor .setCustomText ("Downloaded 0/" + this .amount );
74
79
BufferedImage img ;
75
80
INode mimg ;
76
- String finalPath ;
81
+ Path file ;
77
82
for (int i = 0 ; i < this .amount ; i ++) {
78
83
while (this .queue .peek () == null ) {
79
84
if (this .amount == this .written ) {
@@ -92,9 +97,7 @@ public void run() {
92
97
img = this .queue .take ();
93
98
mimg = this .queueImages .take ();
94
99
if (MapillaryImageUtils .getKey (mimg ) != 0 && mimg .getUniqueId () > 0 ) {
95
- finalPath = Paths
96
- .get (this .path , MapillaryImageUtils .getSequenceKey (mimg ), Long .toString (mimg .getUniqueId ()))
97
- .toString ();
100
+ file = Paths .get (this .path , MapillaryImageUtils .getSequenceKey (mimg ), mimg .getUniqueId () + ".jpg" );
98
101
} else {
99
102
// Increases the progress bar.
100
103
this .monitor .worked (PleaseWaitProgressMonitor .PROGRESS_BAR_MAX / this .amount );
@@ -134,24 +137,28 @@ public void run() {
134
137
.format (DateTimeFormatter .ofPattern ("yyyy:MM:dd HH:mm:ss" ));
135
138
exifDirectory .add (ExifTagConstants .EXIF_TAG_DATE_TIME_ORIGINAL , dateTime );
136
139
140
+ final String xml = getXmpXml (img , mimg );
141
+
137
142
outputSet .setGPSInDegrees (mimg .lon (), mimg .lat ());
138
- File file = new File (finalPath + ".jpg" );
139
- File parentFile = file .getParentFile ();
140
- if (!parentFile .exists () && !parentFile .isDirectory ()) {
141
- if (!parentFile .mkdirs ()) {
142
- throw new IOException ("Directory could not be created: " + parentFile .getPath ());
143
- }
144
- if (!parentFile .setLastModified (MapillaryImageUtils .getDate (mimg ).toEpochMilli ())) {
145
- throw new IOException ("Could not set last modified date: " + parentFile .getPath ());
146
- }
143
+ Path parentFile = file .getParent ();
144
+ if (!Files .exists (parentFile ) && !Files .isDirectory (parentFile )) {
145
+ Files .createDirectories (parentFile );
146
+ Files .setLastModifiedTime (parentFile ,
147
+ FileTime .fromMillis (MapillaryImageUtils .getDate (mimg ).toEpochMilli ()));
147
148
}
148
- try (OutputStream os = new BufferedOutputStream (Files .newOutputStream (file . toPath () ))) {
149
+ try (OutputStream os = new BufferedOutputStream (Files .newOutputStream (file ))) {
149
150
new ExifRewriter ().updateExifMetadataLossless (imageBytes , os , outputSet );
150
151
}
151
- if (!file .setLastModified (MapillaryImageUtils .getDate (mimg ).toEpochMilli ())) {
152
- Logging .info ("Unable to set last modified time for {0} to {1}" , file ,
153
- MapillaryImageUtils .getDate (mimg ));
152
+ if (xml != null ) {
153
+ Path tFile = Files .createTempFile ("mapillary-" , "-" + file .getFileName ());
154
+ try (InputStream is = Files .newInputStream (file );
155
+ OutputStream os = new BufferedOutputStream (Files .newOutputStream (tFile ))) {
156
+ new JpegXmpRewriter ().updateXmpXml (is , os , xml );
157
+ }
158
+ Files .deleteIfExists (file );
159
+ Files .move (tFile , file );
154
160
}
161
+ Files .setLastModifiedTime (file , FileTime .fromMillis (MapillaryImageUtils .getDate (mimg ).toEpochMilli ()));
155
162
this .written ++;
156
163
} catch (InterruptedException e ) {
157
164
if (this .written != this .amount ) {
@@ -170,6 +177,33 @@ public void run() {
170
177
}
171
178
}
172
179
180
+ /**
181
+ * Get the XMP information for this image
182
+ *
183
+ * @param bufferedImage The image data
184
+ * @param imageNode The originating image node with the appropriate information
185
+ * @return The XMP data to write to the image
186
+ */
187
+ @ Nullable
188
+ private static String getXmpXml (@ Nonnull BufferedImage bufferedImage , @ Nonnull INode imageNode ) {
189
+ if (MapillaryImageUtils .IS_PANORAMIC .test (imageNode )) {
190
+ // Assume equirectangular for now
191
+ return "<x:xmpmeta xmlns:x='adobe:ns:meta/'>"
192
+ + "<rdf:RDF xmlns:rdf=\" http://www.w3.org/1999/02/22-rdf-syntax-ns#\" >"
193
+ + "<rdf:Description rdf:about=\" \" xmlns:GPano=\" http://ns.google.com/photos/1.0/panorama/\" >"
194
+ + "<GPano:ProjectionType>equirectangular</GPano:ProjectionType>" + "<GPano:CroppedAreaImageWidthPixels>"
195
+ + bufferedImage .getWidth () + "</GPano:CroppedAreaImageWidthPixels>" + "<GPano:FullPanoWidthPixels>"
196
+ + bufferedImage .getWidth () + "</GPano:FullPanoWidthPixels>" + "<GPano:CroppedAreaImageHeightPixels>"
197
+ + bufferedImage .getHeight () + "</GPano:CroppedAreaImageHeightPixels>" + "<GPano:FullPanoHeightPixels>"
198
+ + bufferedImage .getHeight () + "</GPano:FullPanoHeightPixels>"
199
+ // We don't actually do any cropping, so these are 0.
200
+ + "<GPano:CroppedAreaLeftPixels>0</GPano:CroppedAreaLeftPixels>"
201
+ + "<GPano:CroppedAreaTopPixels>0</GPano:CroppedAreaTopPixels>"
202
+ + "</rdf:Description></rdf:RDF></x:xmpmeta>" ;
203
+ }
204
+ return null ;
205
+ }
206
+
173
207
/**
174
208
* Called when the size is decreased
175
209
*/
0 commit comments