1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 package org.opencastproject.metadata.mpeg7;
24
25 import org.opencastproject.util.XmlSafeParser;
26
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29 import org.xml.sax.Attributes;
30 import org.xml.sax.SAXException;
31 import org.xml.sax.SAXParseException;
32 import org.xml.sax.helpers.DefaultHandler;
33
34 import java.awt.Rectangle;
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.net.URI;
38 import java.text.DecimalFormat;
39 import java.text.DecimalFormatSymbols;
40 import java.text.ParseException;
41 import java.util.Locale;
42
43 import javax.xml.parsers.ParserConfigurationException;
44 import javax.xml.parsers.SAXParser;
45 import javax.xml.parsers.SAXParserFactory;
46
47
48
49
50
51 public class Mpeg7Parser extends DefaultHandler {
52
53
54 private static final Logger logger = LoggerFactory.getLogger(Mpeg7Parser.class.getName());
55
56
57 enum ParserState {
58 Document, MultimediaContent, Segment, VideoText
59 };
60
61
62
63
64
65 private static DecimalFormatSymbols standardSymbols = new DecimalFormatSymbols(Locale.US);
66
67
68 private Mpeg7CatalogImpl mpeg7Doc = null;
69
70
71 private StringBuffer tagContent = new StringBuffer();
72
73
74 private MultimediaContentType multimediaContent = null;
75
76
77 private MultimediaContentType.Type contentType = null;
78
79
80 private String contentId = null;
81
82
83 private MediaLocator mediaLocator = null;
84
85
86 private MediaTimePoint contentTimePoint = null;
87
88
89 private MediaTimePoint mediaTimePoint = null;
90
91
92 private MediaDuration mediaDuration = null;
93
94
95 private MediaTime mediaTime = null;
96
97
98 private TemporalDecomposition<?> temporalDecomposition = null;
99
100
101 private Segment segment = null;
102
103
104 private SpatioTemporalDecomposition spatioTemporalDecomposition = null;
105
106
107 private TextAnnotation textAnnotation = null;
108
109
110 private VideoText videoText = null;
111
112
113 private Textual textual = null;
114
115
116 private Mpeg7Parser.ParserState state = ParserState.Document;
117
118
119 private boolean isMpeg7 = false;
120
121 private DecimalFormat floatFormat = new DecimalFormat();
122
123
124
125
126 public Mpeg7Parser() {
127 floatFormat.setDecimalFormatSymbols(standardSymbols);
128 }
129
130 public Mpeg7Parser(Mpeg7CatalogImpl catalog) {
131 this.mpeg7Doc = catalog;
132 floatFormat.setDecimalFormatSymbols(standardSymbols);
133 }
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150 public Mpeg7CatalogImpl parse(InputStream is) throws ParserConfigurationException, SAXException, IOException {
151 if (mpeg7Doc == null) {
152 mpeg7Doc = new Mpeg7CatalogImpl();
153 }
154 SAXParserFactory factory = XmlSafeParser.newSAXParserFactory();
155
156 factory.setValidating(false);
157 factory.setNamespaceAware(true);
158 SAXParser parser = factory.newSAXParser();
159 parser.parse(is, this);
160
161
162 if (!isMpeg7) {
163 throw new IllegalArgumentException("Content of input stream is not mpeg-7");
164 }
165 return mpeg7Doc;
166 }
167
168
169
170
171
172
173
174 @Override
175 public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
176 super.startElement(uri, localName, name, attributes);
177 tagContent = new StringBuffer();
178
179
180
181 if (!isMpeg7 && "Mpeg7".equals(name)) {
182 isMpeg7 = true;
183 }
184
185
186 if ("MultimediaContent".equals(localName)) {
187 state = ParserState.MultimediaContent;
188 }
189
190
191 if ("Audio".equals(localName) || "Video".equals(localName) || "AudioVisual".equals(localName)) {
192 contentType = MultimediaContentType.Type.valueOf(localName);
193 contentId = attributes.getValue("id");
194 if (MultimediaContentType.Type.Audio.equals(contentType)) {
195 multimediaContent = mpeg7Doc.addAudioContent(contentId, mediaTime, mediaLocator);
196 } else if (MultimediaContentType.Type.Video.equals(contentType)) {
197 multimediaContent = mpeg7Doc.addVideoContent(contentId, mediaTime, mediaLocator);
198 } else if (MultimediaContentType.Type.AudioVisual.equals(contentType)) {
199 multimediaContent = mpeg7Doc.addAudioVisualContent(contentId, mediaTime, mediaLocator);
200 }
201 }
202
203
204 if ("TemporalDecomposition".equals(localName)) {
205 String hasGap = attributes.getValue("gap");
206 String isOverlapping = attributes.getValue("overlap");
207 String criteria = attributes.getValue("criteria");
208 if (!"temporal".equals(criteria)) {
209 throw new IllegalStateException("Decompositions other than temporal are not supported");
210 }
211 temporalDecomposition = multimediaContent.getTemporalDecomposition();
212 temporalDecomposition.setGap("true".equals(hasGap));
213 temporalDecomposition.setOverlapping("overlap".equals(isOverlapping));
214 }
215
216
217 if ("AudioSegment".equals(localName) || "VideoSegment".equals(localName)
218 || "AudioVisualSegment".equals(localName)) {
219 String segmentId = attributes.getValue("id");
220 segment = temporalDecomposition.createSegment(segmentId);
221 state = ParserState.Segment;
222 }
223
224
225 if ("TextAnnotation".equals(localName)) {
226 String language = attributes.getValue("xml:lang");
227 float confidence = -1.0f;
228 float relevance = -1.0f;
229 try {
230 confidence = floatFormat.parse(attributes.getValue("confidence")).floatValue();
231 } catch (Exception e) {
232 confidence = -1.0f;
233 }
234 try {
235 relevance = floatFormat.parse(attributes.getValue("relevance")).floatValue();
236 } catch (Exception e) {
237 relevance = -1.0f;
238 }
239 textAnnotation = segment.createTextAnnotation(confidence, relevance, language);
240 }
241
242
243 if ("SpatioTemporalDecomposition".equals(localName)) {
244 String hasGap = attributes.getValue("gap");
245 String isOverlapping = attributes.getValue("overlap");
246 if (!(segment instanceof VideoSegment)) {
247 throw new IllegalStateException("Can't have a spatio temporal decomposition outside of a video segment");
248 }
249 boolean gap = "true".equalsIgnoreCase(attributes.getValue("gap"));
250 boolean overlap = "true".equalsIgnoreCase(attributes.getValue("overlap"));
251 spatioTemporalDecomposition = ((VideoSegment) segment).createSpatioTemporalDecomposition(gap, overlap);
252 spatioTemporalDecomposition.setGap("true".equals(hasGap));
253 spatioTemporalDecomposition.setOverlapping("overlap".equals(isOverlapping));
254 }
255
256
257 if ("VideoText".equals(localName)) {
258 String id = attributes.getValue("id");
259 videoText = new VideoTextImpl(id);
260 state = ParserState.VideoText;
261 }
262
263
264 if ("Text".equals(localName)) {
265 String language = attributes.getValue("xml:lang");
266 textual = new TextualImpl();
267 textual.setLanguage(language);
268 }
269
270 }
271
272
273
274
275 @Override
276 public void endElement(String uri, String localName, String name) throws SAXException {
277 super.endElement(uri, localName, name);
278
279
280 if ("MultimediaContent".equals(localName)) {
281 state = ParserState.Document;
282 } else if ("AudioSegment".equals(localName) || "VideoSegment".equals(localName)
283 || "AudioVisualSegment".equals(localName)) {
284 state = ParserState.MultimediaContent;
285 }
286
287
288 if ("MediaUri".equals(localName)) {
289 MediaLocatorImpl locator = new MediaLocatorImpl();
290 URI mediaUri = URI.create(getTagContent());
291 locator.setMediaURI(mediaUri);
292 if (ParserState.MultimediaContent.equals(state)) {
293 multimediaContent.setMediaLocator(locator);
294 }
295 }
296
297
298 if ("MediaTime".equals(localName)) {
299 if (ParserState.MultimediaContent.equals(state)) {
300 mediaTime = new MediaTimeImpl(mediaTimePoint, mediaDuration);
301 multimediaContent.setMediaTime(mediaTime);
302 } else if (ParserState.Segment.equals(state)) {
303 mediaTime = new MediaTimeImpl(mediaTimePoint, mediaDuration);
304 segment.setMediaTime(mediaTime);
305 } else if (ParserState.VideoText.equals(state)) {
306 SpatioTemporalLocator spatioTemporalLocator = new SpatioTemporalLocatorImpl(mediaTime);
307 videoText.setSpatioTemporalLocator(spatioTemporalLocator);
308 }
309 }
310
311
312 if ("MediaTimePoint".equals(localName)) {
313 mediaTimePoint = MediaTimePointImpl.parseTimePoint(getTagContent());
314 if (ParserState.MultimediaContent.equals(state)) {
315 contentTimePoint = mediaTimePoint;
316 }
317 }
318
319
320 if ("MediaRelTimePoint".equals(localName)) {
321 MediaRelTimePointImpl tp = MediaRelTimePointImpl.parseTimePoint(getTagContent());
322 mediaTimePoint = tp;
323 if (ParserState.MultimediaContent.equals(state)) {
324 contentTimePoint = tp;
325 } else if (ParserState.Segment.equals(state)) {
326 tp.setReferenceTimePoint(contentTimePoint);
327 }
328 }
329
330
331 if ("MediaDuration".equals(localName)) {
332 mediaDuration = MediaDurationImpl.parseDuration(getTagContent());
333 }
334
335
336 if ("Keyword".equals(localName)) {
337 KeywordAnnotation keyword = new KeywordAnnotationImpl(tagContent.toString());
338 textAnnotation.addKeywordAnnotation(keyword);
339 }
340
341
342 if ("FreeTextAnnotation".equals(localName)) {
343 FreeTextAnnotation freeText = new FreeTextAnnotationImpl(tagContent.toString());
344 textAnnotation.addFreeTextAnnotation(freeText);
345 }
346
347
348 if ("VideoText".equals(localName)) {
349 spatioTemporalDecomposition.addVideoText(videoText);
350 }
351
352
353 if ("SpatioTemporalLocator".equals(localName)) {
354 videoText.setSpatioTemporalLocator(new SpatioTemporalLocatorImpl(mediaTime));
355 }
356
357
358 if ("Text".equals(localName)) {
359 textual.setText(tagContent.toString());
360 videoText.setText(textual);
361 }
362
363
364 if ("Box".equals(localName)) {
365 String[] coords = tagContent.toString().trim().split(" ");
366 if (coords.length != 4) {
367 throw new IllegalStateException("Box coordinates '" + tagContent + "' is malformatted");
368 }
369 int[] coordsL = new int[4];
370 for (int i = 0; i < 4; i++) {
371 try {
372 coordsL[i] = (int) floatFormat.parse(coords[i]).floatValue();
373 } catch (ParseException e) {
374 throw new SAXException(e);
375 }
376 }
377 videoText.setBoundary(new Rectangle(coordsL[0], coordsL[1], (coordsL[2] - coordsL[0]), coordsL[3] - coordsL[1]));
378 }
379
380 }
381
382
383
384
385 @Override
386 public void characters(char[] ch, int start, int length) throws SAXException {
387 super.characters(ch, start, length);
388 tagContent.append(ch, start, length);
389 }
390
391
392
393
394
395
396 private String getTagContent() {
397 String str = tagContent.toString().trim();
398 return str;
399 }
400
401
402
403
404 @Override
405 public void error(SAXParseException e) throws SAXException {
406 logger.warn("Error while parsing mpeg-7 catalog: " + e.getMessage());
407 super.error(e);
408 }
409
410
411
412
413 @Override
414 public void fatalError(SAXParseException e) throws SAXException {
415 logger.warn("Fatal error while parsing mpeg-7 catalog: " + e.getMessage());
416 super.fatalError(e);
417 }
418
419
420
421
422 @Override
423 public void warning(SAXParseException e) throws SAXException {
424 logger.warn("Warning while parsing mpeg-7 catalog: " + e.getMessage());
425 super.warning(e);
426 }
427
428 }