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