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  
22  
23  package org.opencastproject.mediapackage.elementbuilder;
24  
25  import org.opencastproject.mediapackage.MediaPackageElement;
26  import org.opencastproject.mediapackage.MediaPackageElementFlavor;
27  import org.opencastproject.mediapackage.MediaPackageReferenceImpl;
28  import org.opencastproject.mediapackage.MediaPackageSerializer;
29  import org.opencastproject.mediapackage.Track;
30  import org.opencastproject.mediapackage.UnsupportedElementException;
31  import org.opencastproject.mediapackage.track.AudioStreamImpl;
32  import org.opencastproject.mediapackage.track.SubtitleStreamImpl;
33  import org.opencastproject.mediapackage.track.TrackImpl;
34  import org.opencastproject.mediapackage.track.VideoStreamImpl;
35  import org.opencastproject.util.Checksum;
36  import org.opencastproject.util.MimeType;
37  import org.opencastproject.util.MimeTypes;
38  
39  import org.apache.commons.lang3.StringUtils;
40  import org.slf4j.Logger;
41  import org.slf4j.LoggerFactory;
42  import org.w3c.dom.Node;
43  import org.w3c.dom.NodeList;
44  
45  import java.net.URI;
46  import java.net.URISyntaxException;
47  import java.security.NoSuchAlgorithmException;
48  
49  import javax.xml.xpath.XPathConstants;
50  import javax.xml.xpath.XPathException;
51  import javax.xml.xpath.XPathExpressionException;
52  
53  /**
54   * This implementation of the {@link MediaPackageElementBuilderPlugin} recognizes video tracks and provides the
55   * functionality of reading it on behalf of the media package.
56   */
57  public class TrackBuilderPlugin extends AbstractElementBuilderPlugin {
58  
59    /**
60     * the logging facility provided by log4j
61     */
62    private static final Logger logger = LoggerFactory.getLogger(TrackBuilderPlugin.class);
63  
64    /**
65     * @see org.opencastproject.mediapackage.elementbuilder.MediaPackageElementBuilderPlugin#accept(org.opencastproject.mediapackage.MediaPackageElement.Type,
66     *      org.opencastproject.mediapackage.MediaPackageElementFlavor)
67     */
68    @Override
69    public boolean accept(MediaPackageElement.Type type, MediaPackageElementFlavor flavor) {
70      return type.equals(MediaPackageElement.Type.Track);
71    }
72  
73    /**
74     * @see org.opencastproject.mediapackage.elementbuilder.MediaPackageElementBuilderPlugin#accept(org.w3c.dom.Node)
75     */
76    @Override
77    public boolean accept(Node elementNode) {
78      String name = elementNode.getNodeName();
79      if (name.contains(":")) {
80        name = name.substring(name.indexOf(":") + 1);
81      }
82      return name.equalsIgnoreCase(MediaPackageElement.Type.Track.toString());
83    }
84  
85    /**
86     * @see org.opencastproject.mediapackage.elementbuilder.MediaPackageElementBuilderPlugin#accept(URI,
87     *      org.opencastproject.mediapackage.MediaPackageElement.Type,
88     *      org.opencastproject.mediapackage.MediaPackageElementFlavor)
89     */
90    @Override
91    public boolean accept(URI uri, MediaPackageElement.Type type, MediaPackageElementFlavor flavor) {
92      return MediaPackageElement.Type.Track.equals(type);
93    }
94  
95    /**
96     * @see org.opencastproject.mediapackage.elementbuilder.MediaPackageElementBuilderPlugin#elementFromURI(URI)
97     */
98    @Override
99    public MediaPackageElement elementFromURI(URI uri) throws UnsupportedElementException {
100     logger.trace("Creating track from " + uri);
101     Track track = TrackImpl.fromURI(uri);
102     return track;
103   }
104 
105   /**
106    * @see org.opencastproject.mediapackage.elementbuilder.MediaPackageElementBuilderPlugin#newElement(org.opencastproject.mediapackage.MediaPackageElement.Type
107    *      ,org.opencastproject.mediapackage.MediaPackageElementFlavor)
108    */
109   @Override
110   public MediaPackageElement newElement(MediaPackageElement.Type type, MediaPackageElementFlavor flavor) {
111     Track track = new TrackImpl();
112     track.setFlavor(flavor);
113     return track;
114   }
115 
116   /**
117    * @see org.opencastproject.mediapackage.elementbuilder.MediaPackageElementBuilderPlugin#elementFromManifest(org.w3c.dom.Node,
118    *      org.opencastproject.mediapackage.MediaPackageSerializer)
119    */
120   @Override
121   public MediaPackageElement elementFromManifest(Node elementNode, MediaPackageSerializer serializer)
122           throws UnsupportedElementException {
123 
124     String id = null;
125     MimeType mimeType = null;
126     MediaPackageElementFlavor flavor = null;
127     TrackImpl.StreamingProtocol transport = null;
128     String reference = null;
129     URI url = null;
130     long size = -1;
131     Checksum checksum = null;
132 
133     try {
134       // id
135       id = (String) xpath.evaluate("@id", elementNode, XPathConstants.STRING);
136 
137       // url
138       url = serializer.decodeURI(new URI(xpath.evaluate("url/text()", elementNode).trim()));
139 
140       // reference
141       reference = (String) xpath.evaluate("@ref", elementNode, XPathConstants.STRING);
142 
143       // size
144       String trackSize = xpath.evaluate("size/text()", elementNode).trim();
145       if (!"".equals(trackSize))
146         size = Long.parseLong(trackSize);
147 
148       // flavor
149       String flavorValue = (String) xpath.evaluate("@type", elementNode, XPathConstants.STRING);
150       if (StringUtils.isNotEmpty(flavorValue))
151         flavor = MediaPackageElementFlavor.parseFlavor(flavorValue);
152 
153       // transport
154       String transportValue = (String) xpath.evaluate("@transport", elementNode, XPathConstants.STRING);
155       if (StringUtils.isNotEmpty(transportValue))
156         transport = TrackImpl.StreamingProtocol.valueOf(transportValue);
157 
158       // checksum
159       String checksumValue = (String) xpath.evaluate("checksum/text()", elementNode, XPathConstants.STRING);
160       String checksumType = (String) xpath.evaluate("checksum/@type", elementNode, XPathConstants.STRING);
161       if (StringUtils.isNotEmpty(checksumValue) && checksumType != null)
162         checksum = Checksum.create(checksumType.trim(), checksumValue.trim());
163 
164       // mimetype
165       String mimeTypeValue = (String) xpath.evaluate("mimetype/text()", elementNode, XPathConstants.STRING);
166       if (StringUtils.isNotEmpty(mimeTypeValue))
167         mimeType = MimeTypes.parseMimeType(mimeTypeValue);
168 
169       //
170       // Build the track
171 
172       TrackImpl track = TrackImpl.fromURI(url);
173 
174       if (StringUtils.isNotBlank(id))
175         track.setIdentifier(id);
176 
177       // Add url
178       track.setURI(url);
179 
180       // Add reference
181       if (StringUtils.isNotEmpty(reference))
182         track.referTo(MediaPackageReferenceImpl.fromString(reference));
183 
184       // Set size
185       if (size > 0)
186         track.setSize(size);
187 
188       // Set checksum
189       if (checksum != null)
190         track.setChecksum(checksum);
191 
192       // Set mimetpye
193       if (mimeType != null)
194         track.setMimeType(mimeType);
195 
196       if (flavor != null)
197         track.setFlavor(flavor);
198 
199       //set transport
200       if (transport != null)
201         track.setTransport(transport);
202 
203       // description
204       String description = (String) xpath.evaluate("description/text()", elementNode, XPathConstants.STRING);
205       if (StringUtils.isNotBlank(description))
206         track.setElementDescription(description.trim());
207 
208       // tags
209       NodeList tagNodes = (NodeList) xpath.evaluate("tags/tag", elementNode, XPathConstants.NODESET);
210       for (int i = 0; i < tagNodes.getLength(); i++) {
211         track.addTag(tagNodes.item(i).getTextContent());
212       }
213 
214       // duration
215       try {
216         String strDuration = (String) xpath.evaluate("duration/text()", elementNode, XPathConstants.STRING);
217         if (StringUtils.isNotEmpty(strDuration)) {
218           long duration = Long.parseLong(strDuration.trim());
219           track.setDuration(duration);
220         }
221       } catch (NumberFormatException e) {
222         throw new UnsupportedElementException("Duration of track " + url + " is malformatted");
223       }
224 
225       // is live
226       String strLive = (String) xpath.evaluate("live/text()", elementNode, XPathConstants.STRING);
227       if (StringUtils.isNotEmpty(strLive)) {
228         boolean live = Boolean.parseBoolean(strLive.trim());
229         track.setLive(live);
230       }
231 
232       // is an adaptive playlist
233       String strMaster = (String) xpath.evaluate("master/text()", elementNode, XPathConstants.STRING);
234       if (StringUtils.isNotEmpty(strMaster)) {
235         track.setMaster(Boolean.parseBoolean(strMaster.trim()));
236       }
237 
238       // has logical name - adaptive playlist reference
239       String strLogicalname = (String) xpath.evaluate("logicalname/text()", elementNode, XPathConstants.STRING);
240       if (StringUtils.isNotEmpty(strLogicalname)) {
241         track.setLogicalName(strLogicalname.trim());
242       }
243 
244       // audio settings
245       Node audioSettingsNode = (Node) xpath.evaluate("audio", elementNode, XPathConstants.NODE);
246       if (audioSettingsNode != null && audioSettingsNode.hasChildNodes()) {
247         try {
248           AudioStreamImpl as = AudioStreamImpl.fromManifest(createStreamID(track), audioSettingsNode, xpath);
249           track.addStream(as);
250         } catch (IllegalStateException e) {
251           throw new UnsupportedElementException("Illegal state encountered while reading audio settings from " + url
252                   + ": " + e.getMessage());
253         } catch (XPathException e) {
254           throw new UnsupportedElementException("Error while parsing audio settings from " + url + ": "
255                   + e.getMessage());
256         }
257       }
258 
259       // video settings
260       Node videoSettingsNode = (Node) xpath.evaluate("video", elementNode, XPathConstants.NODE);
261       if (videoSettingsNode != null && videoSettingsNode.hasChildNodes()) {
262         try {
263           VideoStreamImpl vs = VideoStreamImpl.fromManifest(createStreamID(track), videoSettingsNode, xpath);
264           track.addStream(vs);
265         } catch (IllegalStateException e) {
266           throw new UnsupportedElementException("Illegal state encountered while reading video settings from " + url
267                   + ": " + e.getMessage());
268         } catch (XPathException e) {
269           throw new UnsupportedElementException("Error while parsing video settings from " + url + ": "
270                   + e.getMessage());
271         }
272       }
273 
274       // subtitle settings
275       Node subtitleSettingsNode = (Node) xpath.evaluate("subtitle", elementNode, XPathConstants.NODE);
276       if (subtitleSettingsNode != null && subtitleSettingsNode.hasChildNodes()) {
277         try {
278           SubtitleStreamImpl ss = SubtitleStreamImpl.fromManifest(createStreamID(track), subtitleSettingsNode, xpath);
279           track.addStream(ss);
280         } catch (IllegalStateException e) {
281           throw new UnsupportedElementException("Illegal state encountered while reading subtitle settings from " + url
282               + ": " + e.getMessage());
283         } catch (XPathException e) {
284           throw new UnsupportedElementException("Error while parsing subtitle settings from " + url + ": "
285               + e.getMessage());
286         }
287       }
288 
289       return track;
290     } catch (XPathExpressionException e) {
291       throw new UnsupportedElementException("Error while reading track information from manifest: " + e.getMessage());
292     } catch (NoSuchAlgorithmException e) {
293       throw new UnsupportedElementException("Unsupported digest algorithm: " + e.getMessage());
294     } catch (URISyntaxException e) {
295       throw new UnsupportedElementException("Error while reading presenter track " + url + ": " + e.getMessage());
296     }
297   }
298 
299   private String createStreamID(Track track) {
300     return "stream-" + (track.getStreams().length + 1);
301   }
302 
303   @Override
304   public String toString() {
305     return "Track Builder Plugin";
306   }
307 
308 }