/** * Licensed to The Apereo Foundation under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * * The Apereo Foundation licenses this file to you under the Educational * Community License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License * at: * * http://opensource.org/licenses/ecl2.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. * */ package org.opencastproject.caption.converters; import org.opencastproject.caption.api.Caption; import org.opencastproject.caption.api.CaptionConverter; import org.opencastproject.caption.api.CaptionConverterException; import org.opencastproject.caption.api.IllegalTimeFormatException; import org.opencastproject.caption.api.Time; import org.opencastproject.caption.impl.CaptionImpl; import org.opencastproject.caption.impl.TimeImpl; import org.opencastproject.metadata.mpeg7.Audio; import org.opencastproject.metadata.mpeg7.AudioSegment; import org.opencastproject.metadata.mpeg7.FreeTextAnnotation; import org.opencastproject.metadata.mpeg7.FreeTextAnnotationImpl; import org.opencastproject.metadata.mpeg7.MediaDuration; import org.opencastproject.metadata.mpeg7.MediaTime; import org.opencastproject.metadata.mpeg7.MediaTimeImpl; import org.opencastproject.metadata.mpeg7.MediaTimePoint; import org.opencastproject.metadata.mpeg7.Mpeg7Catalog; import org.opencastproject.metadata.mpeg7.Mpeg7CatalogImpl; import org.opencastproject.metadata.mpeg7.TemporalDecomposition; import org.opencastproject.metadata.mpeg7.TextAnnotation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Calendar; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.TimeZone; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerFactoryConfigurationError; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; /** * This is converter for Mpeg7 caption format. */ public class Mpeg7CaptionConverter implements CaptionConverter { /** File extension for mpeg 7 catalogs */ private static final String EXTENSION = "xml"; /** The logger */ private static final Logger logger = LoggerFactory.getLogger(Mpeg7CaptionConverter.class); /** * @see org.opencastproject.caption.api.CaptionConverter#importCaption(java.io.InputStream, java.lang.String) */ @SuppressWarnings("unchecked") @Override public List<Caption> importCaption(InputStream inputStream, String language) throws CaptionConverterException { List<Caption> captions = new ArrayList<Caption>(); Mpeg7Catalog catalog = new Mpeg7CatalogImpl(inputStream); Iterator<Audio> audioContentIterator = catalog.audioContent(); if (audioContentIterator == null) return captions; content: while (audioContentIterator.hasNext()) { Audio audioContent = audioContentIterator.next(); TemporalDecomposition<AudioSegment> audioSegments = (TemporalDecomposition<AudioSegment>) audioContent .getTemporalDecomposition(); Iterator<AudioSegment> audioSegmentIterator = audioSegments.segments(); if (audioSegmentIterator == null) continue content; while (audioSegmentIterator.hasNext()) { AudioSegment segment = audioSegmentIterator.next(); Iterator<TextAnnotation> annotationIterator = segment.textAnnotations(); if (annotationIterator == null) continue content; while (annotationIterator.hasNext()) { TextAnnotation annotation = annotationIterator.next(); if (!annotation.getLanguage().equals(language)) { logger.debug("Skipping audio content '{}' because of language mismatch", audioContent.getId()); continue content; } List<String> captionLines = new ArrayList<String>(); Iterator<FreeTextAnnotation> freeTextAnnotationIterator = annotation.freeTextAnnotations(); if (freeTextAnnotationIterator == null) continue; while (freeTextAnnotationIterator.hasNext()) { FreeTextAnnotation freeTextAnnotation = freeTextAnnotationIterator.next(); captionLines.add(freeTextAnnotation.getText()); } MediaTime segmentTime = segment.getMediaTime(); MediaTimePoint stp = segmentTime.getMediaTimePoint(); MediaDuration d = segmentTime.getMediaDuration(); Calendar startCalendar = Calendar.getInstance(); int millisAtStart = (int) (stp.getTimeInMilliseconds() - (((stp.getHour() * 60 + stp.getMinutes()) * 60 + stp .getSeconds()) * 1000)); int millisAtEnd = (int) (d.getDurationInMilliseconds() - (((d.getHours() * 60 + d.getMinutes()) * 60 + d .getSeconds()) * 1000)); startCalendar.set(Calendar.HOUR, stp.getHour()); startCalendar.set(Calendar.MINUTE, stp.getMinutes()); startCalendar.set(Calendar.SECOND, stp.getSeconds()); startCalendar.set(Calendar.MILLISECOND, millisAtStart); startCalendar.add(Calendar.HOUR, d.getHours()); startCalendar.add(Calendar.MINUTE, d.getMinutes()); startCalendar.add(Calendar.SECOND, d.getSeconds()); startCalendar.set(Calendar.MILLISECOND, millisAtEnd); try { Time startTime = new TimeImpl(stp.getHour(), stp.getMinutes(), stp.getSeconds(), millisAtStart); Time endTime = new TimeImpl(startCalendar.get(Calendar.HOUR), startCalendar.get(Calendar.MINUTE), startCalendar.get(Calendar.SECOND), startCalendar.get(Calendar.MILLISECOND)); Caption caption = new CaptionImpl(startTime, endTime, captionLines.toArray(new String[captionLines.size()])); captions.add(caption); } catch (IllegalTimeFormatException e) { logger.warn("Error setting caption time: {}", e.getMessage()); } } } } return captions; } @Override public void exportCaption(OutputStream outputStream, List<Caption> captions, String language) throws IOException { Mpeg7Catalog mpeg7 = Mpeg7CatalogImpl.newInstance(); MediaTime mediaTime = new MediaTimeImpl(0, 0); Audio audioContent = mpeg7.addAudioContent("captions", mediaTime, null); @SuppressWarnings("unchecked") TemporalDecomposition<AudioSegment> captionDecomposition = (TemporalDecomposition<AudioSegment>) audioContent .getTemporalDecomposition(); int segmentCount = 0; for (Caption caption : captions) { // Get all the words/parts for the transcript String[] words = caption.getCaption(); if (words.length == 0) continue; // Create a new segment AudioSegment segment = captionDecomposition.createSegment("segment-" + segmentCount++); Time captionST = caption.getStartTime(); Time captionET = caption.getStopTime(); // Calculate start time Calendar startTime = Calendar.getInstance(TimeZone.getTimeZone("UTC")); startTime.setTimeInMillis(0); startTime.add(Calendar.HOUR_OF_DAY, captionST.getHours()); startTime.add(Calendar.MINUTE, captionST.getMinutes()); startTime.add(Calendar.SECOND, captionST.getSeconds()); startTime.add(Calendar.MILLISECOND, captionST.getMilliseconds()); // Calculate end time Calendar endTime = Calendar.getInstance(TimeZone.getTimeZone("UTC")); endTime.setTimeInMillis(0); endTime.add(Calendar.HOUR_OF_DAY, captionET.getHours()); endTime.add(Calendar.MINUTE, captionET.getMinutes()); endTime.add(Calendar.SECOND, captionET.getSeconds()); endTime.add(Calendar.MILLISECOND, captionET.getMilliseconds()); long startTimeInMillis = startTime.getTimeInMillis(); long endTimeInMillis = endTime.getTimeInMillis(); long duration = endTimeInMillis - startTimeInMillis; segment.setMediaTime(new MediaTimeImpl(startTimeInMillis, duration)); TextAnnotation textAnnotation = segment.createTextAnnotation(0, 0, language); // Collect all the words in the segment StringBuffer captionLine = new StringBuffer(); // Add each words/parts as segment to the catalog for (String word : words) { if (captionLine.length() > 0) captionLine.append(' '); captionLine.append(word); } // Append the text to the annotation textAnnotation.addFreeTextAnnotation(new FreeTextAnnotationImpl(captionLine.toString())); } Transformer tf = null; try { tf = TransformerFactory.newInstance().newTransformer(); DOMSource xmlSource = new DOMSource(mpeg7.toXml()); tf.transform(xmlSource, new StreamResult(outputStream)); } catch (TransformerConfigurationException e) { logger.warn("Error serializing mpeg7 captions catalog: {}", e.getMessage()); throw new IOException(e); } catch (TransformerFactoryConfigurationError e) { logger.warn("Error serializing mpeg7 captions catalog: {}", e.getMessage()); throw new IOException(e); } catch (TransformerException e) { logger.warn("Error serializing mpeg7 captions catalog: {}", e.getMessage()); throw new IOException(e); } catch (ParserConfigurationException e) { logger.warn("Error serializing mpeg7 captions catalog: {}", e.getMessage()); throw new IOException(e); } } /** * @see org.opencastproject.caption.api.CaptionConverter#getLanguageList(java.io.InputStream) */ @SuppressWarnings("unchecked") @Override public String[] getLanguageList(InputStream inputStream) throws CaptionConverterException { Set<String> languages = new HashSet<String>(); Mpeg7Catalog catalog = new Mpeg7CatalogImpl(inputStream); Iterator<Audio> audioContentIterator = catalog.audioContent(); if (audioContentIterator == null) return languages.toArray(new String[languages.size()]); content: while (audioContentIterator.hasNext()) { Audio audioContent = audioContentIterator.next(); TemporalDecomposition<AudioSegment> audioSegments = (TemporalDecomposition<AudioSegment>) audioContent .getTemporalDecomposition(); Iterator<AudioSegment> audioSegmentIterator = audioSegments.segments(); if (audioSegmentIterator == null) continue content; while (audioSegmentIterator.hasNext()) { AudioSegment segment = audioSegmentIterator.next(); Iterator<TextAnnotation> annotationIterator = segment.textAnnotations(); if (annotationIterator == null) continue content; while (annotationIterator.hasNext()) { TextAnnotation annotation = annotationIterator.next(); String language = annotation.getLanguage(); if (language != null) languages.add(language); } } } return languages.toArray(new String[languages.size()]); } /** * @see org.opencastproject.caption.api.CaptionConverter#getExtension() */ @Override public String getExtension() { return EXTENSION; } }