001 /*
002 *
003 * $Revision: 15022 $ $Date: 2009-03-26 14:53:00 +0100 (Thu, 26 Mar 2009) $
004 *
005 * This file is part of *** M y C o R e ***
006 * See http://www.mycore.de/ for details.
007 *
008 * This program is free software; you can use it, redistribute it
009 * and / or modify it under the terms of the GNU General Public License
010 * (GPL) as published by the Free Software Foundation; either version 2
011 * of the License or (at your option) any later version.
012 *
013 * This program is distributed in the hope that it will be useful, but
014 * WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program, in a file called gpl.txt or license.txt.
020 * If not, write to the Free Software Foundation Inc.,
021 * 59 Temple Place - Suite 330, Boston, MA 02111-1307 USA
022 */
023
024 package org.mycore.datamodel.ifs;
025
026 import java.io.ByteArrayOutputStream;
027 import java.io.IOException;
028 import java.io.InputStream;
029 import java.io.OutputStream;
030 import java.net.MalformedURLException;
031 import java.net.URL;
032 import java.net.URLConnection;
033 import java.text.DecimalFormat;
034
035 import org.mycore.common.MCRConfiguration;
036 import org.mycore.common.MCRConfigurationException;
037 import org.mycore.common.MCRException;
038 import org.mycore.common.MCRPersistenceException;
039 import org.mycore.common.MCRUtils;
040
041 /**
042 * For MCRFiles that contain streaming audio/video, instances of this class
043 * provide methods to get technical metadata like bitrate, framerate, duration,
044 * size etc. and to start a player to stream the asset to a browser.
045 *
046 * @author Frank Lützenkirchen
047 * @version $Revision: 15022 $ $Date: 2009-03-26 14:53:00 +0100 (Thu, 26 Mar 2009) $
048 */
049 public abstract class MCRAudioVideoExtender {
050 /** Constant for media type = video */
051 public final static boolean VIDEO = true;
052
053 /** Constant for media type = audio */
054 public final static boolean AUDIO = false;
055
056 /** The bitrate of the asset in number of bits per second */
057 protected int bitRate = 0;
058
059 /** The framerate of the asset in number of frames per second */
060 protected double frameRate = 0;
061
062 /** The media type of the asset, either AUDIO or VIDEO */
063 protected boolean mediaType = VIDEO;
064
065 /** The hours part of the duration of the asset */
066 protected int durationHours = 0;
067
068 /** The minutes part of the duration of the asset */
069 protected int durationMinutes = 0;
070
071 /** The seconds part of the duration of the asset */
072 protected int durationSeconds = 0;
073
074 /** The size of the asset in bytes */
075 protected long size = 0;
076
077 /** The content type of the asset */
078 protected String contentTypeID = "unknown";
079
080 /** The URL where clients can download a player for the asset */
081 protected String playerDownloadURL = "";
082
083 /** The MIME type a servlet has to send with the player starter */
084 protected String playerStarterCT = "";
085
086 /** The base URL where to get a metafile that starts a player in browser */
087 protected String basePlayerStarter = "";
088
089 /** The base URL of a cgi that provides technical metadata about the asset */
090 protected String baseMetadata = "";
091
092 /** The asset file this extender belongs to */
093 protected MCRFileReader file;
094
095 /**
096 * Creates a new MCRAudioVideoExtender. The instance has to be initialized
097 * by invoking init() before it can be used.
098 */
099 public MCRAudioVideoExtender() {
100 }
101
102 /**
103 * Initializes this AudioVideoExtender and gets technical metadata from the
104 * server that holds the streaming asset. Subclasses must override this
105 * method!
106 *
107 * @param file
108 * the MCRFile that this extender belongs to
109 */
110 public void init(MCRFileReader file) throws MCRException {
111 this.file = file;
112 }
113
114 /**
115 * Returns the maximum number of bits per seconds when asset is streamed
116 *
117 * @return the maximum number of bits per seconds when asset is streamed
118 */
119 public int getBitRate() {
120 return bitRate;
121 }
122
123 /**
124 * Returns the streaming bitrate formatted as a String, e. g. "1.3 MBit" or
125 * "300 kBit".
126 *
127 * @return the streaming bitrate formatted as a String
128 */
129 public String getBitRateFormatted() {
130 if (bitRate > (1024 * 1024)) {
131 double b = Math.round(bitRate / 10485.76) / 100.0;
132 return new DecimalFormat("##0.##").format(b) + " MBit";
133 }
134 double b = Math.round(bitRate / 102.4) / 10.0;
135 return new DecimalFormat("##0.#").format(b) + " kBit";
136 }
137
138 /**
139 * Returns the maximum number of frames per second for a streaming video
140 * asset
141 *
142 * @return the maximum number of frames per second for a streaming video
143 * asset
144 */
145 public double getFrameRate() {
146 return frameRate;
147 }
148
149 /**
150 * Returns the framerate formatted as a String, e. g. "25.0"
151 *
152 * @return the framerate formatted as a String, e. g. "25.0"
153 */
154 public String getFrameRateFormatted() {
155 // double r = (double)( Math.round( frameRate * 10.0 ) ) / 10.0;
156 return new DecimalFormat("##.#").format(frameRate);
157 }
158
159 /**
160 * Returns the media type, either AUDIO od VIDEO for this asset
161 *
162 * @return the media type, compare to the boolean constants in this class
163 */
164 public boolean getMediaType() {
165 return mediaType;
166 }
167
168 /**
169 * Returns true, if this asset is an audio asset.
170 *
171 * @return true, if this asset is an audio asset.
172 */
173 public boolean isAudio() {
174 return (mediaType == AUDIO);
175 }
176
177 /**
178 * Returns true, if this asset is a video asset.
179 *
180 * @return true, if this asset is a video asset.
181 */
182 public boolean isVideo() {
183 return (mediaType == VIDEO);
184 }
185
186 /**
187 * Returns the hours part of the duration of this asset
188 *
189 * @return the hours part of the duration of this asset
190 */
191 public int getDurationHours() {
192 return durationHours;
193 }
194
195 /**
196 * Returns the minutes part of the duration of this asset
197 *
198 * @return the minutes part of the duration of this asset
199 */
200 public int getDurationMinutes() {
201 return durationMinutes;
202 }
203
204 /**
205 * Returns the seconds part of the duration of this asset
206 *
207 * @return the seconds part of the duration of this asset
208 */
209 public int getDurationSeconds() {
210 return durationSeconds;
211 }
212
213 /**
214 * Returns the duration of this asset, formatted as a String for output. For
215 * example, <tt>getDurationFormatted( "Std.", "Min.", "Sek." )</tt> will
216 * return the String "1 Std. 15 Min." for an asset that is one hour and 15
217 * minutes long. If duration is less than one hour, only minutes and seconds
218 * go into the output string, otherwise hours and minutes are used.
219 *
220 * @param hourLabel
221 * the label for the hours part of the duration
222 * @param minutesLabel
223 * the label for the minutes part of the duration
224 * @param secondsLabel
225 * the label for the seconds part of the duration
226 * @return the duration of this asset, formatted as a String
227 */
228 public String getDurationFormatted(String hourLabel, String minutesLabel, String secondsLabel) {
229 StringBuffer sb = new StringBuffer();
230
231 if (durationHours > 0) {
232 sb.append(durationHours);
233 sb.append(" ").append(hourLabel).append(" ");
234 sb.append(durationMinutes);
235 sb.append(" ").append(minutesLabel);
236 } else if (durationMinutes > 0) {
237 sb.append(durationMinutes);
238 sb.append(" ").append(minutesLabel).append(" ");
239 sb.append(durationSeconds);
240 sb.append(" ").append(secondsLabel);
241 } else {
242 sb.append(durationSeconds);
243 sb.append(" ").append(secondsLabel);
244 }
245
246 return sb.toString();
247 }
248
249 /**
250 * Returns the duration of the asset, formatted as a timcode, e. g.
251 * "01:15:00" for an asset thats duration is one hour and 15 minutes.
252 *
253 * @return the duration foramatted as a timecode like "hh:mm:ss"
254 */
255 public String getDurationTimecode() {
256 DecimalFormat formatter = new DecimalFormat("00");
257 StringBuffer sb = new StringBuffer();
258 sb.append(formatter.format(durationHours));
259 sb.append(":");
260 sb.append(formatter.format(durationMinutes));
261 sb.append(":");
262 sb.append(formatter.format(durationSeconds));
263
264 return sb.toString();
265 }
266
267 /**
268 * Returns the asset size in number of bytes.
269 *
270 * @return the asset size in number of bytes
271 */
272 public long getSize() {
273 return size;
274 }
275
276 /**
277 * Returns the asset size, formatted as a String.
278 *
279 * @return the asset size, formatted as a String
280 */
281 public String getSizeFormatted() {
282 return MCRFilesystemNode.getSizeFormatted(size);
283 }
284
285 /**
286 * Returns the ID of the content type of this asset
287 *
288 * @return the ID of the content type of this asset
289 */
290 public String getContentTypeID() {
291 return contentTypeID;
292 }
293
294 /**
295 * Returns the URL where clients can download a player for this asset
296 *
297 * @return the URL where clients can download a player for this asset
298 */
299 public String getPlayerDownloadURL() {
300 return playerDownloadURL;
301 }
302
303 /**
304 * Writes a metafile that starts a streaming player for this asset to an
305 * OutputStream, e. g. a ServletOutputStream. The browser then streams the
306 * asset. The client may provide a start and stop position to play only a
307 * certain part of the asset.
308 *
309 * @param out
310 * the OutputStream to write the player starter to
311 * @param startPos
312 * the optional start position in the format "hh:mm:ss"
313 * @param stopPos
314 * the optional stop position in the format "hh:mm:ss"
315 */
316 public abstract void getPlayerStarterTo(OutputStream out, String startPos, String stopPos) throws MCRPersistenceException;
317
318 /**
319 * Returns the MIME type a servlet has to set in the HTTP response that
320 * delivers the player starter metafile to the browser
321 *
322 * @return the MIME type of the player starter metafile
323 */
324 public String getPlayerStarterContentType() {
325 return playerStarterCT;
326 }
327
328 /**
329 * Returns a string representation of this extender's data, useful for
330 * debugging.
331 *
332 * @return a String containing useful information about this extender's data
333 */
334 public String toString() {
335 StringBuffer sb = new StringBuffer();
336 sb.append("Media Type : ");
337 sb.append(isVideo() ? "Video\n" : "Audio\n");
338 sb.append("Bitrate : ").append(getBitRateFormatted()).append("/sec.");
339 sb.append(" (").append(getBitRate()).append(")\n");
340
341 if (isVideo()) {
342 sb.append("Framerate : ").append(getFrameRateFormatted()).append(" fps");
343 sb.append(" (").append(getFrameRate()).append(")\n");
344 }
345
346 sb.append("Duration : ").append(getDurationFormatted("h", "min", "sec"));
347 sb.append(" (").append(getDurationTimecode()).append(")\n");
348 sb.append("Size : ").append(getSizeFormatted());
349 sb.append(" (").append(getSize()).append(")\n");
350 sb.append("Content Type : ").append(getContentTypeID()).append("\n");
351 sb.append("Player Download : ").append(playerDownloadURL);
352
353 return sb.toString();
354 }
355
356 /**
357 * Helper method to get a substring that lies between a prefix and a suffix
358 * string. If either prefix or suffix are not found in the string, the
359 * defaultValue is returned. This helper method is used by subclasses to
360 * parse metadata that is read from the server.
361 *
362 * @param prefix
363 * the string before the substring
364 * @param suffix
365 * the string after the substring
366 * @param data
367 * the string to search through
368 * @param defaultValue
369 * the default to return when no match is found
370 * @return the substring between prefix and suffix
371 */
372 protected String getBetween(String prefix, String suffix, String data, String defaultValue) {
373 int from = data.indexOf(prefix);
374
375 if (from == -1) {
376 return defaultValue;
377 }
378
379 from += prefix.length();
380
381 int to = data.indexOf(suffix, from);
382
383 if (to == -1) {
384 return defaultValue;
385 }
386
387 return data.substring(from, to).trim();
388 }
389
390 /**
391 * Helper method that reads all data from an URLConnection input stream and
392 * forwards it to the given output stream.
393 *
394 * @param connection
395 * the URLConnection to get the InputStream from
396 * @param out
397 * the OutputStream to write the bytes to
398 */
399 protected void forwardData(URLConnection connection, OutputStream out) throws IOException {
400 InputStream in = connection.getInputStream();
401 MCRUtils.copyStream(in, out);
402 out.close();
403 }
404
405 /**
406 * Helper method that creates a URLConnection to a given URL and wraps
407 * possible IOException or MalformedURLExceptions
408 *
409 * @param url
410 * the URL to connect to
411 */
412 protected URLConnection getConnection(String url) throws MCRPersistenceException {
413 try {
414 return new URL(url).openConnection();
415 } catch (MalformedURLException exc) {
416 String msg = "Malformed Audio/Video Store URL: " + url;
417 throw new MCRConfigurationException(msg, exc);
418 } catch (IOException exc) {
419 String msg = "Could not get connection to Audio/Video Store URL: " + url;
420 throw new MCRPersistenceException(msg, exc);
421 }
422 }
423
424 /**
425 * Helper method that connects to the given URL and returns the response as
426 * a String
427 *
428 * @param url
429 * the URL to connect to
430 * @return the response content as a String
431 */
432 protected String getMetadata(String url) throws MCRPersistenceException {
433 try {
434 URLConnection connection = getConnection(url);
435 connection.setConnectTimeout(getConnectTimeout());
436 ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
437 forwardData(connection, out);
438
439 return new String(out.toByteArray());
440 } catch (IOException exc) {
441 String msg = "Could not get metadata from Audio/Video Store URL: " + url;
442 throw new MCRPersistenceException(msg, exc);
443 }
444 }
445
446 protected int getConnectTimeout() {
447 return MCRConfiguration.instance().getInt("MCR.IFS.AVExtender.ConnectTimeout", 0);
448 }
449 }