1 |
| package photospace.meta; |
2 |
| |
3 |
| import java.text.*; |
4 |
| import java.util.*; |
5 |
| import org.apache.commons.logging.*; |
6 |
| import com.drew.lang.*; |
7 |
| import com.drew.metadata.*; |
8 |
| import com.drew.metadata.exif.*; |
9 |
| import com.hp.hpl.jena.rdf.model.*; |
10 |
| import com.hp.hpl.jena.vocabulary.*; |
11 |
| import photospace.meta.rdf.*; |
12 |
| import photospace.space.*; |
13 |
| |
14 |
| public class Translator |
15 |
| implements java.io.Serializable |
16 |
| { |
17 |
| private static final Log log = LogFactory.getLog(Translator.class); |
18 |
| |
19 |
| private String baseUrl = "http://localhost"; |
20 |
| |
21 |
0
| public Model toRss(CollectionMeta collection, String link)
|
22 |
| { |
23 |
0
| String viewBase = baseUrl + "/web/view?view=media&path=";
|
24 |
| |
25 |
0
| Model rdf = getDefaultModel();
|
26 |
0
| Resource channel = rdf.createResource(link, RSS.channel);
|
27 |
0
| channel.addProperty(RSS.title, collection.getLabel());
|
28 |
0
| channel.addProperty(RSS.link, channel.getURI());
|
29 |
0
| if (getDescription(collection) != null) channel.addProperty(RSS.description, getDescription(collection));
|
30 |
0
| if (collection.getCreator() != null) channel.addProperty(DC.creator, collection.getCreator());
|
31 |
0
| if (collection.getCreated() != null) channel.addProperty(DC.date, DATE_FORMAT.format(collection.getCreated()));
|
32 |
0
| Seq items = rdf.createSeq();
|
33 |
0
| channel.addProperty(RSS.items, items);
|
34 |
0
| Meta[] files = collection.getFiles();
|
35 |
0
| for (int i = 0; i < files.length; i++)
|
36 |
| { |
37 |
0
| Meta meta = files[i];
|
38 |
0
| Resource item = rdf.createResource(getUri(meta), RSS.item);
|
39 |
0
| items.add(item);
|
40 |
0
| item.addProperty(RSS.title, meta.getLabel());
|
41 |
0
| item.addProperty(RSS.link, viewBase + meta.getPath());
|
42 |
0
| if (getDescription(meta) != null) item.addProperty(RSS.description, getDescription(meta));
|
43 |
0
| if (meta.getCreator() != null) item.addProperty(DC.creator, meta.getCreator());
|
44 |
0
| if (meta.getCreated() != null) item.addProperty(DC.date, DATE_FORMAT.format(meta.getCreated()));
|
45 |
0
| if (meta.getPosition().getLatitude() != null) item.addProperty(GeoVocab.LAT, meta.getPosition().getLatitude());
|
46 |
0
| if (meta.getPosition().getLongitude() != null) item.addProperty(GeoVocab.LONG, meta.getPosition().getLongitude());
|
47 |
0
| if (meta.getPosition().getAltitude() != null) item.addProperty(GeoVocab.ALT, meta.getPosition().getAltitude());
|
48 |
| } |
49 |
0
| return rdf;
|
50 |
| } |
51 |
| |
52 |
0
| private String getDescription(Meta meta)
|
53 |
| { |
54 |
0
| if (! (meta instanceof PhotoMeta)) return meta.getDescription();
|
55 |
0
| String desc = "<img border='0' src='" + baseUrl + "/image" + meta.getPath() + "?w=400&h=400'><br/>";
|
56 |
0
| if (meta.getDescription() != null) desc += meta.getDescription();
|
57 |
0
| return desc;
|
58 |
| } |
59 |
| |
60 |
1
| public Model toRdf(CollectionMeta collection, int depth)
|
61 |
| { |
62 |
0
| if (depth < 1) return toRdf(collection);
|
63 |
| |
64 |
1
| Model rdf = getDefaultModel();
|
65 |
1
| addSubject(collection, rdf, 0, depth);
|
66 |
1
| return rdf;
|
67 |
| } |
68 |
| |
69 |
1
| private void addSubject(CollectionMeta collection, Model rdf, int current, int depth)
|
70 |
| { |
71 |
1
| addSubject(collection, rdf);
|
72 |
| |
73 |
0
| if (current == depth) return;
|
74 |
| |
75 |
1
| Meta[] files = collection.getFiles();
|
76 |
1
| for (int i = 0; i < files.length; i++)
|
77 |
| { |
78 |
1
| Meta meta = files[i];
|
79 |
0
| if (meta instanceof CollectionMeta) addSubject((CollectionMeta) meta, rdf, ++current, depth);
|
80 |
1
| else addSubject(meta, rdf);
|
81 |
| } |
82 |
| } |
83 |
| |
84 |
9
| public Model toRdf(Meta meta)
|
85 |
| { |
86 |
9
| Model rdf = getDefaultModel();
|
87 |
9
| addSubject(meta, rdf);
|
88 |
9
| return rdf;
|
89 |
| } |
90 |
| |
91 |
11
| private void addSubject(Meta meta, Model rdf)
|
92 |
| { |
93 |
11
| Resource subject;
|
94 |
4
| if (getUri(meta) == null) subject = rdf.createResource();
|
95 |
7
| else subject = rdf.createResource(getUri(meta));
|
96 |
| |
97 |
7
| if (meta.getPath() != null) subject.addProperty(SpaceVocab.PATH, meta.getPath());
|
98 |
| |
99 |
| |
100 |
11
| if (meta instanceof PhotoMeta)
|
101 |
| { |
102 |
9
| subject.addProperty(DC.type, Meta.PHOTO);
|
103 |
9
| subject.addProperty(DC.format, "image/jpeg");
|
104 |
| } |
105 |
0
| else if (meta instanceof SearchResult) subject.addProperty(DC.type, Meta.SEARCH);
|
106 |
2
| else if (meta instanceof FolderMeta) subject.addProperty(DC.type, Meta.FOLDER);
|
107 |
| |
108 |
0
| else throw new IllegalStateException("Not sure what to do with " + meta);
|
109 |
| |
110 |
2
| if (meta.getTitle() != null) subject.addProperty(DC.title, meta.getTitle());
|
111 |
6
| if (meta.getDescription() != null) subject.addProperty(DC.description, meta.getDescription());
|
112 |
0
| if (meta.getCreator() != null) subject.addProperty(DC.creator, meta.getCreator());
|
113 |
6
| if (meta.getCreated() != null) subject.addProperty(DC.date, df[0].format(meta.getCreated()));
|
114 |
| |
115 |
| |
116 |
4
| if (meta.getUpdated() != null) subject.addProperty(SpaceVocab.UPDATED, df[0].format(meta.getUpdated()));
|
117 |
0
| if (meta.getRating() != null) subject.addProperty(SpaceVocab.RATING, meta.getRating());
|
118 |
0
| if (meta.getRatingCount() != null) subject.addProperty(SpaceVocab.RATING_COUNT, meta.getRatingCount());
|
119 |
0
| if (meta.getViewCount() != null) subject.addProperty(SpaceVocab.VIEW_COUNT, meta.getViewCount());
|
120 |
| |
121 |
11
| if (meta.getHasPosition())
|
122 |
| { |
123 |
6
| Position pos = meta.getPosition();
|
124 |
| |
125 |
6
| Resource point = rdf.createResource(GeoVocab.POINT);
|
126 |
6
| subject.addProperty(DC.coverage, point);
|
127 |
6
| if (pos.getLatitude() != null) point.addProperty(GeoVocab.LAT, pos.getLatitude().doubleValue());
|
128 |
6
| if (pos.getLongitude() != null) point.addProperty(GeoVocab.LONG, pos.getLongitude().doubleValue());
|
129 |
0
| if (pos.getAltitude() != null) point.addProperty(GeoVocab.ALT, pos.getAltitude().doubleValue());
|
130 |
| } |
131 |
| |
132 |
11
| if (meta.getLabels().length != 0)
|
133 |
| { |
134 |
4
| Resource labels = rdf.createBag();
|
135 |
4
| subject.addProperty(DC.subject, labels);
|
136 |
4
| for (int i = 0; i < meta.getLabels().length; i++)
|
137 |
| { |
138 |
12
| labels.addProperty(RDF.value, meta.getLabels()[i]);
|
139 |
| } |
140 |
| } |
141 |
| |
142 |
11
| if (meta instanceof MediaMeta)
|
143 |
| { |
144 |
9
| MediaMeta asset = (MediaMeta) meta;
|
145 |
| |
146 |
7
| if (asset.getDevice() != null) subject.addProperty(TechVocab.CAMERA, asset.getDevice());
|
147 |
| } |
148 |
| |
149 |
11
| if (meta instanceof PhotoMeta)
|
150 |
| { |
151 |
9
| PhotoMeta photo = (PhotoMeta) meta;
|
152 |
| |
153 |
6
| if (photo.getShutterSpeed() != null) subject.addProperty(TechVocab.SHUTTER_SPEED, photo.getShutterSpeed());
|
154 |
6
| if (photo.getAperture() != null) subject.addProperty(TechVocab.APERTURE, photo.getAperture());
|
155 |
6
| if (photo.getFocalLength() != null) subject.addProperty(TechVocab.FOCAL_LENGTH, photo.getFocalLength());
|
156 |
6
| if (photo.getExposureBias() != null) subject.addProperty(TechVocab.EXPOSURE_BIAS, photo.getExposureBias());
|
157 |
6
| if (photo.getMeteringMode() != null) subject.addProperty(TechVocab.METERING_MODE, photo.getMeteringMode());
|
158 |
6
| if (photo.getSensingMethod() != null) subject.addProperty(TechVocab.SENSING_METHOD, photo.getSensingMethod());
|
159 |
6
| if (photo.getFlash() != null) subject.addProperty(TechVocab.FLASH, photo.getFlash());
|
160 |
| } |
161 |
| } |
162 |
| |
163 |
18
| private String getUri(Meta meta)
|
164 |
| { |
165 |
4
| if (meta.getPath() == null) return null;
|
166 |
14
| return baseUrl + meta.getPath();
|
167 |
| } |
168 |
| |
169 |
10
| private Model getDefaultModel()
|
170 |
| { |
171 |
10
| Model rdf = ModelFactory.createDefaultModel();
|
172 |
10
| rdf.setNsPrefix("geo", GeoVocab.getURI());
|
173 |
10
| rdf.setNsPrefix("tech", TechVocab.getURI());
|
174 |
10
| rdf.setNsPrefix("content", ContentVocab.getURI());
|
175 |
10
| rdf.setNsPrefix("space", SpaceVocab.getURI());
|
176 |
10
| return rdf;
|
177 |
| } |
178 |
| |
179 |
8
| public Meta fromRdf(Model rdf) throws ParseException
|
180 |
| { |
181 |
| |
182 |
8
| Resource subject = getAsset(rdf);
|
183 |
| |
184 |
0
| if (!subject.hasProperty(DC.type)) throw new IllegalStateException("RDF does not define meta type.");
|
185 |
| |
186 |
8
| Meta meta;
|
187 |
| |
188 |
8
| String rdfType = subject.getProperty(DC.type).getString();
|
189 |
7
| if (Meta.PHOTO.equals(rdfType)) meta = new PhotoMeta();
|
190 |
1
| else if (Meta.FOLDER.equals(rdfType)) meta = new FolderMeta();
|
191 |
| |
192 |
0
| else throw new IllegalStateException("Not sure what to do with RDF type: " + rdfType);
|
193 |
| |
194 |
6
| if (subject.hasProperty(SpaceVocab.PATH)) meta.setPath(subject.getProperty(SpaceVocab.PATH).getString());
|
195 |
| |
196 |
0
| if (subject.hasProperty(DC.title)) meta.setTitle(subject.getProperty(DC.title).getString());
|
197 |
6
| if (subject.hasProperty(DC.description)) meta.setDescription(subject.getProperty(DC.description).getString());
|
198 |
0
| if (subject.hasProperty(DC.creator)) meta.setCreator(subject.getProperty(DC.creator).getString());
|
199 |
6
| if (subject.hasProperty(DC.date)) meta.setCreated(parseDate(subject.getProperty(DC.date).getString()));
|
200 |
| |
201 |
5
| if (subject.hasProperty(SpaceVocab.UPDATED)) meta.setUpdated(parseDate(subject.getProperty(SpaceVocab.UPDATED).getString()));
|
202 |
0
| if (subject.hasProperty(SpaceVocab.RATING)) meta.setRating(new Double(subject.getProperty(SpaceVocab.RATING).getDouble()));
|
203 |
0
| if (subject.hasProperty(SpaceVocab.RATING_COUNT)) meta.setRatingCount(new Integer(subject.getProperty(SpaceVocab.RATING_COUNT).getInt()));
|
204 |
0
| if (subject.hasProperty(SpaceVocab.VIEW_COUNT)) meta.setViewCount(new Integer(subject.getProperty(SpaceVocab.VIEW_COUNT).getInt()));
|
205 |
| |
206 |
8
| if (subject.hasProperty(DC.coverage))
|
207 |
| { |
208 |
6
| Resource point = subject.getProperty(DC.coverage).getResource();
|
209 |
6
| if (point.hasProperty(GeoVocab.LAT)) meta.getPosition().setLatitude(new Double(point.getProperty(GeoVocab.LAT).getDouble()));
|
210 |
6
| if (point.hasProperty(GeoVocab.LONG)) meta.getPosition().setLongitude(new Double(point.getProperty(GeoVocab.LONG).getDouble()));
|
211 |
0
| if (point.hasProperty(GeoVocab.ALT)) meta.getPosition().setAltitude(new Double(point.getProperty(GeoVocab.ALT).getDouble()));
|
212 |
| } |
213 |
| |
214 |
8
| if (subject.hasProperty(DC.subject))
|
215 |
| { |
216 |
5
| Set labels = new HashSet();
|
217 |
5
| for (StmtIterator i = subject.getProperty(DC.subject).getResource().listProperties(RDF.value); i.hasNext(); )
|
218 |
| { |
219 |
10
| labels.add(i.nextStatement().getString());
|
220 |
| } |
221 |
5
| meta.setLabels((String[]) labels.toArray(new String[] {}));
|
222 |
| } |
223 |
| |
224 |
8
| if (meta instanceof MediaMeta)
|
225 |
| { |
226 |
7
| MediaMeta media = (MediaMeta) meta;
|
227 |
| |
228 |
6
| if (subject.hasProperty(TechVocab.CAMERA)) media.setDevice(subject.getProperty(TechVocab.CAMERA).getString());
|
229 |
| } |
230 |
| |
231 |
8
| if (meta instanceof PhotoMeta)
|
232 |
| { |
233 |
7
| PhotoMeta photo = (PhotoMeta) meta;
|
234 |
| |
235 |
6
| if (subject.hasProperty(TechVocab.SHUTTER_SPEED)) photo.setShutterSpeed(subject.getProperty(TechVocab.SHUTTER_SPEED).getString());
|
236 |
6
| if (subject.hasProperty(TechVocab.APERTURE)) photo.setAperture(subject.getProperty(TechVocab.APERTURE).getString());
|
237 |
6
| if (subject.hasProperty(TechVocab.FOCAL_LENGTH)) photo.setFocalLength(subject.getProperty(TechVocab.FOCAL_LENGTH).getString());
|
238 |
6
| if (subject.hasProperty(TechVocab.EXPOSURE_BIAS)) photo.setExposureBias(subject.getProperty(TechVocab.EXPOSURE_BIAS).getString());
|
239 |
6
| if (subject.hasProperty(TechVocab.METERING_MODE)) photo.setMeteringMode(subject.getProperty(TechVocab.METERING_MODE).getString());
|
240 |
6
| if (subject.hasProperty(TechVocab.SENSING_METHOD)) photo.setSensingMethod(subject.getProperty(TechVocab.SENSING_METHOD).getString());
|
241 |
6
| if (subject.hasProperty(TechVocab.FLASH)) photo.setFlash(subject.getProperty(TechVocab.FLASH).getString());
|
242 |
| } |
243 |
| |
244 |
8
| return meta;
|
245 |
| } |
246 |
| |
247 |
| |
248 |
| |
249 |
| |
250 |
| |
251 |
8
| private Resource getAsset(Model rdf)
|
252 |
| { |
253 |
8
| return rdf.listSubjectsWithProperty(DC.type).nextResource();
|
254 |
| } |
255 |
| |
256 |
7
| public PhotoMeta fromExif(Metadata exif)
|
257 |
| throws MetadataException |
258 |
| { |
259 |
7
| PhotoMeta photo = new PhotoMeta();
|
260 |
7
| Directory dir = exif.getDirectory(ExifDirectory.class);
|
261 |
0
| if (dir.containsTag(ExifDirectory.TAG_ARTIST)) photo.setCreator(dir.getString(ExifDirectory.TAG_ARTIST));
|
262 |
7
| if (dir.containsTag(ExifDirectory.TAG_DATETIME_ORIGINAL)) photo.setCreated(dir.getDate(ExifDirectory.TAG_DATETIME_ORIGINAL));
|
263 |
7
| if (dir.containsTag(ExifDirectory.TAG_USER_COMMENT)) photo.setDescription(dir.getString(ExifDirectory.TAG_USER_COMMENT));
|
264 |
| |
265 |
7
| if (dir.containsTag(ExifDirectory.TAG_MODEL)) photo.setDevice(dir.getDescription(ExifDirectory.TAG_MODEL));
|
266 |
7
| if (dir.containsTag(ExifDirectory.TAG_EXPOSURE_TIME)) photo.setShutterSpeed(dir.getRational(ExifDirectory.TAG_EXPOSURE_TIME).toSimpleString(false) + " sec");
|
267 |
7
| if (dir.containsTag(ExifDirectory.TAG_APERTURE)) photo.setAperture(dir.getDescription(ExifDirectory.TAG_APERTURE));
|
268 |
7
| if (dir.containsTag(ExifDirectory.TAG_FOCAL_LENGTH)) photo.setFocalLength(dir.getDescription(ExifDirectory.TAG_FOCAL_LENGTH));
|
269 |
7
| if (dir.containsTag(ExifDirectory.TAG_EXPOSURE_BIAS)) photo.setExposureBias(dir.getDescription(ExifDirectory.TAG_EXPOSURE_BIAS));
|
270 |
7
| if (dir.containsTag(ExifDirectory.TAG_METERING_MODE)) photo.setMeteringMode(dir.getDescription(ExifDirectory.TAG_METERING_MODE));
|
271 |
7
| if (dir.containsTag(ExifDirectory.TAG_SENSING_METHOD)) photo.setSensingMethod(dir.getDescription(ExifDirectory.TAG_SENSING_METHOD));
|
272 |
7
| if (dir.containsTag(ExifDirectory.TAG_FLASH)) photo.setFlash(dir.getDescription(ExifDirectory.TAG_FLASH));
|
273 |
| |
274 |
7
| Directory gps = exif.getDirectory(GpsDirectory.class);
|
275 |
7
| if (gps.containsTag(GpsDirectory.TAG_GPS_LATITUDE) && gps.containsTag(GpsDirectory.TAG_GPS_LATITUDE_REF))
|
276 |
| { |
277 |
7
| photo.getPosition().setLatitude(getDegrees(gps.getRationalArray(GpsDirectory.TAG_GPS_LATITUDE),
|
278 |
| gps.getString(GpsDirectory.TAG_GPS_LATITUDE_REF))); |
279 |
| } |
280 |
7
| if (gps.containsTag(GpsDirectory.TAG_GPS_LONGITUDE) && gps.containsTag(GpsDirectory.TAG_GPS_LONGITUDE_REF))
|
281 |
| { |
282 |
7
| photo.getPosition().setLongitude(getDegrees(gps.getRationalArray(GpsDirectory.TAG_GPS_LONGITUDE),
|
283 |
| gps.getString(GpsDirectory.TAG_GPS_LONGITUDE_REF))); |
284 |
| } |
285 |
7
| if (gps.containsTag(GpsDirectory.TAG_GPS_ALTITUDE))
|
286 |
| { |
287 |
0
| photo.getPosition().setAltitude(new Double(gps.getDouble(GpsDirectory.TAG_GPS_ALTITUDE)));
|
288 |
| } |
289 |
| |
290 |
7
| return photo;
|
291 |
| } |
292 |
| |
293 |
| |
294 |
| |
295 |
| |
296 |
| |
297 |
| |
298 |
| |
299 |
| |
300 |
| public static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SZ"); |
301 |
| private static final DateFormat[] df = |
302 |
| { |
303 |
| DATE_FORMAT, |
304 |
| new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.S"), |
305 |
| new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"), |
306 |
| new SimpleDateFormat("yyyy-MM-dd'T'HH:mm"), |
307 |
| new SimpleDateFormat("yyyy-MM-dd"), |
308 |
| new SimpleDateFormat("yyyy-MM"), |
309 |
| new SimpleDateFormat("yyyy") |
310 |
| }; |
311 |
| |
312 |
11
| private Date parseDate(String date)
|
313 |
| { |
314 |
16
| for (int i = 0; i < df.length; i++)
|
315 |
| { |
316 |
16
| try { return df[i].parse(date); }
|
317 |
| catch (ParseException e) {} |
318 |
| } |
319 |
0
| log.warn("Unable to parse date string from RDF: " + date);
|
320 |
| |
321 |
0
| return null;
|
322 |
| } |
323 |
| |
324 |
14
| private Double getDegrees(Rational[] degreesArray, String reference)
|
325 |
| { |
326 |
14
| double decimalDegrees = degreesArray[0].doubleValue() + degreesArray[1].doubleValue()/60 + degreesArray[2].doubleValue()/3600;
|
327 |
14
| boolean negative = reference.equals("S") || reference.equals("W");
|
328 |
14
| return new Double((negative ? -1 : 1) * Math.round(decimalDegrees * 100000d) / 100000d);
|
329 |
| } |
330 |
| |
331 |
0
| public void setBaseUrl(String baseUrl)
|
332 |
| { |
333 |
0
| if (baseUrl.endsWith("/")) baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
|
334 |
0
| this.baseUrl = baseUrl;
|
335 |
| } |
336 |
| } |