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 SAXParserFactory factory = XmlSafeParser.newSAXParserFactory();
154
155 factory.setValidating(false);
156 factory.setNamespaceAware(true);
157 SAXParser parser = factory.newSAXParser();
158 parser.parse(is, this);
159
160
161 if (!isMpeg7)
162 throw new IllegalArgumentException("Content of input stream is not mpeg-7");
163 return mpeg7Doc;
164 }
165
166
167
168
169
170
171
172 @Override
173 public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
174 super.startElement(uri, localName, name, attributes);
175 tagContent = new StringBuffer();
176
177
178
179 if (!isMpeg7 && "Mpeg7".equals(name))
180 isMpeg7 = true;
181
182
183 if ("MultimediaContent".equals(localName)) {
184 state = ParserState.MultimediaContent;
185 }
186
187
188 if ("Audio".equals(localName) || "Video".equals(localName) || "AudioVisual".equals(localName)) {
189 contentType = MultimediaContentType.Type.valueOf(localName);
190 contentId = attributes.getValue("id");
191 if (MultimediaContentType.Type.Audio.equals(contentType))
192 multimediaContent = mpeg7Doc.addAudioContent(contentId, mediaTime, mediaLocator);
193 else if (MultimediaContentType.Type.Video.equals(contentType))
194 multimediaContent = mpeg7Doc.addVideoContent(contentId, mediaTime, mediaLocator);
195 else if (MultimediaContentType.Type.AudioVisual.equals(contentType))
196 multimediaContent = mpeg7Doc.addAudioVisualContent(contentId, mediaTime, mediaLocator);
197 }
198
199
200 if ("TemporalDecomposition".equals(localName)) {
201 String hasGap = attributes.getValue("gap");
202 String isOverlapping = attributes.getValue("overlap");
203 String criteria = attributes.getValue("criteria");
204 if (!"temporal".equals(criteria))
205 throw new IllegalStateException("Decompositions other than temporal are not supported");
206 temporalDecomposition = multimediaContent.getTemporalDecomposition();
207 temporalDecomposition.setGap("true".equals(hasGap));
208 temporalDecomposition.setOverlapping("overlap".equals(isOverlapping));
209 }
210
211
212 if ("AudioSegment".equals(localName) || "VideoSegment".equals(localName) || "AudioVisualSegment".equals(localName)) {
213 String segmentId = attributes.getValue("id");
214 segment = temporalDecomposition.createSegment(segmentId);
215 state = ParserState.Segment;
216 }
217
218
219 if ("TextAnnotation".equals(localName)) {
220 String language = attributes.getValue("xml:lang");
221 float confidence = -1.0f;
222 float relevance = -1.0f;
223 try {
224 confidence = floatFormat.parse(attributes.getValue("confidence")).floatValue();
225 } catch (Exception e) {
226 confidence = -1.0f;
227 }
228 try {
229 relevance = floatFormat.parse(attributes.getValue("relevance")).floatValue();
230 } catch (Exception e) {
231 relevance = -1.0f;
232 }
233 textAnnotation = segment.createTextAnnotation(confidence, relevance, language);
234 }
235
236
237 if ("SpatioTemporalDecomposition".equals(localName)) {
238 String hasGap = attributes.getValue("gap");
239 String isOverlapping = attributes.getValue("overlap");
240 if (!(segment instanceof VideoSegment))
241 throw new IllegalStateException("Can't have a spatio temporal decomposition outside of a video segment");
242 boolean gap = "true".equalsIgnoreCase(attributes.getValue("gap"));
243 boolean overlap = "true".equalsIgnoreCase(attributes.getValue("overlap"));
244 spatioTemporalDecomposition = ((VideoSegment) segment).createSpatioTemporalDecomposition(gap, overlap);
245 spatioTemporalDecomposition.setGap("true".equals(hasGap));
246 spatioTemporalDecomposition.setOverlapping("overlap".equals(isOverlapping));
247 }
248
249
250 if ("VideoText".equals(localName)) {
251 String id = attributes.getValue("id");
252 videoText = new VideoTextImpl(id);
253 state = ParserState.VideoText;
254 }
255
256
257 if ("Text".equals(localName)) {
258 String language = attributes.getValue("xml:lang");
259 textual = new TextualImpl();
260 textual.setLanguage(language);
261 }
262
263 }
264
265
266
267
268 @Override
269 public void endElement(String uri, String localName, String name) throws SAXException {
270 super.endElement(uri, localName, name);
271
272
273 if ("MultimediaContent".equals(localName))
274 state = ParserState.Document;
275 else if ("AudioSegment".equals(localName) || "VideoSegment".equals(localName)
276 || "AudioVisualSegment".equals(localName))
277 state = ParserState.MultimediaContent;
278
279
280 if ("MediaUri".equals(localName)) {
281 MediaLocatorImpl locator = new MediaLocatorImpl();
282 URI mediaUri = URI.create(getTagContent());
283 locator.setMediaURI(mediaUri);
284 if (ParserState.MultimediaContent.equals(state)) {
285 multimediaContent.setMediaLocator(locator);
286 }
287 }
288
289
290 if ("MediaTime".equals(localName)) {
291 if (ParserState.MultimediaContent.equals(state)) {
292 mediaTime = new MediaTimeImpl(mediaTimePoint, mediaDuration);
293 multimediaContent.setMediaTime(mediaTime);
294 } else if (ParserState.Segment.equals(state)) {
295 mediaTime = new MediaTimeImpl(mediaTimePoint, mediaDuration);
296 segment.setMediaTime(mediaTime);
297 } else if (ParserState.VideoText.equals(state)) {
298 SpatioTemporalLocator spatioTemporalLocator = new SpatioTemporalLocatorImpl(mediaTime);
299 videoText.setSpatioTemporalLocator(spatioTemporalLocator);
300 }
301 }
302
303
304 if ("MediaTimePoint".equals(localName)) {
305 mediaTimePoint = MediaTimePointImpl.parseTimePoint(getTagContent());
306 if (ParserState.MultimediaContent.equals(state)) {
307 contentTimePoint = mediaTimePoint;
308 }
309 }
310
311
312 if ("MediaRelTimePoint".equals(localName)) {
313 MediaRelTimePointImpl tp = MediaRelTimePointImpl.parseTimePoint(getTagContent());
314 mediaTimePoint = tp;
315 if (ParserState.MultimediaContent.equals(state))
316 contentTimePoint = tp;
317 else if (ParserState.Segment.equals(state)) {
318 tp.setReferenceTimePoint(contentTimePoint);
319 }
320 }
321
322
323 if ("MediaDuration".equals(localName)) {
324 mediaDuration = MediaDurationImpl.parseDuration(getTagContent());
325 }
326
327
328 if ("Keyword".equals(localName)) {
329 KeywordAnnotation keyword = new KeywordAnnotationImpl(tagContent.toString());
330 textAnnotation.addKeywordAnnotation(keyword);
331 }
332
333
334 if ("FreeTextAnnotation".equals(localName)) {
335 FreeTextAnnotation freeText = new FreeTextAnnotationImpl(tagContent.toString());
336 textAnnotation.addFreeTextAnnotation(freeText);
337 }
338
339
340 if ("VideoText".equals(localName)) {
341 spatioTemporalDecomposition.addVideoText(videoText);
342 }
343
344
345 if ("SpatioTemporalLocator".equals(localName)) {
346 videoText.setSpatioTemporalLocator(new SpatioTemporalLocatorImpl(mediaTime));
347 }
348
349
350 if ("Text".equals(localName)) {
351 textual.setText(tagContent.toString());
352 videoText.setText(textual);
353 }
354
355
356 if ("Box".equals(localName)) {
357 String[] coords = tagContent.toString().trim().split(" ");
358 if (coords.length != 4)
359 throw new IllegalStateException("Box coordinates '" + tagContent + "' is malformatted");
360 int[] coordsL = new int[4];
361 for (int i = 0; i < 4; i++)
362 try {
363 coordsL[i] = (int) floatFormat.parse(coords[i]).floatValue();
364 } catch (ParseException e) {
365 throw new SAXException(e);
366 }
367 videoText.setBoundary(new Rectangle(coordsL[0], coordsL[1], (coordsL[2] - coordsL[0]), coordsL[3] - coordsL[1]));
368 }
369
370 }
371
372
373
374
375 @Override
376 public void characters(char[] ch, int start, int length) throws SAXException {
377 super.characters(ch, start, length);
378 tagContent.append(ch, start, length);
379 }
380
381
382
383
384
385
386 private String getTagContent() {
387 String str = tagContent.toString().trim();
388 return str;
389 }
390
391
392
393
394 @Override
395 public void error(SAXParseException e) throws SAXException {
396 logger.warn("Error while parsing mpeg-7 catalog: " + e.getMessage());
397 super.error(e);
398 }
399
400
401
402
403 @Override
404 public void fatalError(SAXParseException e) throws SAXException {
405 logger.warn("Fatal error while parsing mpeg-7 catalog: " + e.getMessage());
406 super.fatalError(e);
407 }
408
409
410
411
412 @Override
413 public void warning(SAXParseException e) throws SAXException {
414 logger.warn("Warning while parsing mpeg-7 catalog: " + e.getMessage());
415 super.warning(e);
416 }
417
418 }