View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   *
20   */
21  package org.opencastproject.smil.api.util;
22  
23  import org.opencastproject.mediapackage.Catalog;
24  import org.opencastproject.mediapackage.MediaPackage;
25  import org.opencastproject.mediapackage.MediaPackageElement;
26  import org.opencastproject.mediapackage.MediaPackageElementFlavor;
27  import org.opencastproject.mediapackage.selector.AbstractMediaPackageElementSelector;
28  import org.opencastproject.mediapackage.selector.CatalogSelector;
29  import org.opencastproject.util.NotFoundException;
30  import org.opencastproject.util.XmlUtil;
31  import org.opencastproject.util.data.Either;
32  import org.opencastproject.util.data.functions.Misc;
33  import org.opencastproject.workspace.api.Workspace;
34  
35  import com.android.mms.dom.smil.parser.SmilXmlParser;
36  
37  import org.apache.commons.httpclient.util.URIUtil;
38  import org.apache.commons.io.IOUtils;
39  import org.slf4j.Logger;
40  import org.slf4j.LoggerFactory;
41  import org.w3c.dom.Document;
42  import org.w3c.dom.Element;
43  import org.w3c.dom.Node;
44  import org.w3c.dom.smil.SMILDocument;
45  import org.xml.sax.InputSource;
46  import org.xml.sax.SAXException;
47  
48  import java.io.File;
49  import java.io.FileInputStream;
50  import java.io.IOException;
51  import java.io.InputStream;
52  import java.net.URI;
53  import java.util.Collection;
54  
55  /**
56   * General purpose utility functions for dealing with SMIL.
57   */
58  public final class SmilUtil {
59  
60    private static final Logger logger = LoggerFactory.getLogger(SmilUtil.class);
61  
62    public static final String SMIL_NODE_NAME = "smil";
63    public static final String SMIL_NS_URI = "http://www.w3.org/ns/SMIL";
64  
65    public enum TrackType {
66      PRESENTER, PRESENTATION
67    }
68  
69    private SmilUtil() {
70    }
71  
72    /**
73     * Load the SMIL document identified by <code>mpe</code>. Throws an exception if it does not exist or cannot be loaded
74     * by any reason.
75     *
76     * @return the document
77     */
78    public static Document loadSmilDocument(InputStream in, MediaPackageElement mpe) {
79      try {
80        Either<Exception, org.w3c.dom.Document> eitherDocument = XmlUtil.parseNs(new InputSource(in));
81        if (eitherDocument.isRight()) {
82          return eitherDocument.right().value();
83        }
84  
85        throw eitherDocument.left().value();
86      } catch (Exception e) {
87        logger.warn("Unable to load smil document from catalog '{}'", mpe, e);
88        return Misc.chuck(e);
89      }
90    }
91  
92    /**
93     * Creates a skeleton SMIL document
94     *
95     * @return the SMIL document
96     */
97    public static Document createSmil() {
98      Document smilDocument = XmlUtil.newDocument();
99      smilDocument.setXmlVersion("1.1");
100     Element smil = smilDocument.createElementNS(SMIL_NS_URI, SMIL_NODE_NAME);
101     smil.setAttribute("version", "3.0");
102     smilDocument.appendChild(smil);
103     Node head = smilDocument.createElement("head");
104     smil.appendChild(head);
105     Node body = smilDocument.createElement("body");
106     smil.appendChild(body);
107     Element parallel = smilDocument.createElement("par");
108     parallel.setAttribute("dur", "0ms");
109     body.appendChild(parallel);
110     return smilDocument;
111   }
112 
113   /**
114    * Adds a track to the SMIL document.
115    *
116    * @param smilDocument
117    *          the SMIL document
118    * @param trackType
119    *          the track type
120    * @param hasVideo
121    *          whether the track has a video stream
122    * @param startTime
123    *          the start time
124    * @param duration
125    *          the duration
126    * @param uri
127    *          the track URI
128    * @param trackId
129    *          the Id of the track
130    * @return the augmented SMIL document
131    */
132   public static Document addTrack(Document smilDocument, TrackType trackType, boolean hasVideo, long startTime,
133           long duration, URI uri, String trackId) {
134     Element parallel = (Element) smilDocument.getElementsByTagName("par").item(0);
135     if (parallel.getChildNodes().getLength() == 0) {
136       Node presenterSeq = smilDocument.createElement("seq");
137       parallel.appendChild(presenterSeq);
138       Node presentationSeq = smilDocument.createElement("seq");
139       parallel.appendChild(presentationSeq);
140     }
141 
142     String trackDurationString = parallel.getAttribute("dur");
143     Long oldTrackDuration = Long.parseLong(trackDurationString.substring(0, trackDurationString.indexOf("ms")));
144     Long newTrackDuration = startTime + duration;
145     if (newTrackDuration > oldTrackDuration) {
146       parallel.setAttribute("dur", newTrackDuration + "ms");
147     }
148 
149     Node sequence;
150     switch (trackType) {
151       case PRESENTER:
152         sequence = parallel.getChildNodes().item(0);
153         break;
154       case PRESENTATION:
155         sequence = parallel.getChildNodes().item(1);
156         break;
157       default:
158         throw new IllegalStateException("Unknown track type " + trackType.toString());
159     }
160 
161     Element element = smilDocument.createElement(hasVideo ? "video" : "audio");
162     element.setAttribute("begin", Long.toString(startTime) + "ms");
163     element.setAttribute("dur", Long.toString(duration) + "ms");
164     element.setAttribute("src", URIUtil.getPath(uri.toString()));
165     if (trackId != null) {
166       element.setAttribute("xml:id", trackId);
167     }
168     sequence.appendChild(element);
169     return smilDocument;
170   }
171 
172 
173   public static SMILDocument getSmilDocumentFromMediaPackage(MediaPackage mp, MediaPackageElementFlavor smilFlavor,
174       Workspace workspace)
175           throws IOException, SAXException, NotFoundException {
176     final AbstractMediaPackageElementSelector<Catalog> smilSelector = new CatalogSelector();
177     smilSelector.addFlavor(smilFlavor);
178     final Collection<Catalog> smilCatalog = smilSelector.select(mp, false);
179     if (smilCatalog.size() == 1) {
180       return getSmilDocument(smilCatalog.iterator().next(), workspace);
181     } else {
182       logger.error("More or less than one smil catalog found: {}", smilCatalog);
183       throw new IllegalStateException("More or less than one smil catalog found!");
184     }
185   }
186 
187   /** Get the SMIL document from a catalog. */
188   private static SMILDocument getSmilDocument(final Catalog smilCatalog, Workspace workspace) throws NotFoundException,
189           IOException, SAXException {
190     FileInputStream in = null;
191     try {
192       File smilXmlFile = workspace.get(smilCatalog.getURI());
193       SmilXmlParser smilParser = new SmilXmlParser();
194       in = new FileInputStream(smilXmlFile);
195       return smilParser.parse(in);
196     } finally {
197       IOUtils.closeQuietly(in);
198     }
199   }
200 
201 }