001: /******************************************************************************
002: * Transcoder.java
003: * ****************************************************************************/package org.openlaszlo.media;
004:
005: import java.io.InputStream;
006: import java.io.FileInputStream;
007: import java.io.IOException;
008: import java.io.BufferedInputStream;
009: import java.io.FileNotFoundException;
010: import java.io.File;
011: import java.awt.geom.Rectangle2D;
012: import java.util.Properties;
013:
014: // JGenerator APIs
015: import org.openlaszlo.iv.flash.api.*;
016: import org.openlaszlo.iv.flash.api.action.*;
017: import org.openlaszlo.iv.flash.api.sound.*;
018: import org.openlaszlo.iv.flash.api.image.*;
019: import org.openlaszlo.iv.flash.util.*;
020:
021: import org.openlaszlo.utils.SWFUtils;
022: import org.openlaszlo.utils.FileUtils;
023: import org.openlaszlo.server.LPS;
024:
025: import org.openlaszlo.sc.ScriptCompiler;
026:
027: // Logger
028: import org.apache.log4j.*;
029:
030: /**
031: * Simple Media Transcoder
032: *
033: * Someday this should get build out.
034: */
035: public class Transcoder {
036:
037: /** Logger */
038: private static Logger mLogger = Logger.getLogger(Transcoder.class);
039:
040: /**
041: * @return true if the transcoder can do the requested transcode
042: * @param in input mime type
043: * @param out output mime type
044: */
045: public static boolean canTranscode(String in, String out) {
046:
047: if (!out.equalsIgnoreCase(MimeType.SWF)) {
048:
049: if (out.equalsIgnoreCase(FontType.FFT)
050: && in.equalsIgnoreCase(FontType.TTF)) {
051: return true;
052: }
053: return false;
054: }
055:
056: if (in.equalsIgnoreCase(MimeType.JPEG)
057: || in.equalsIgnoreCase(MimeType.PNG)
058: || in.equalsIgnoreCase(MimeType.GIF)
059: || in.equalsIgnoreCase(MimeType.MP3)
060: || in.equalsIgnoreCase(MimeType.XMP3)
061: || in.equalsIgnoreCase(MimeType.SWF)) {
062: return true;
063: }
064: return false;
065: }
066:
067: /**
068: * @param input buffer of data to be transcoded, caller is responsible
069: * for closing this stream someday.
070: * @param from type of input data
071: * @param to type of output data
072: * @param doStream true if transcode should create streaming audio
073: * @throws TranscoderException if there is no transcoder for the request from/to
074: * types.
075: */
076: public static InputStream transcode(InputStream stream,
077: String from, String to, boolean doStream)
078: throws TranscoderException, IOException {
079:
080: if (!to.equalsIgnoreCase(MimeType.SWF)) {
081: throw new TranscoderException(
082: /* (non-Javadoc)
083: * @i18n.test
084: * @org-mes="Unknown output mime-type: " + p[0]
085: */
086: org.openlaszlo.i18n.LaszloMessages.getMessage(
087: Transcoder.class.getName(), "051018-93",
088: new Object[] { to }));
089: }
090:
091: mLogger.debug(
092: /* (non-Javadoc)
093: * @i18n.test
094: * @org-mes="Transcoding from " + p[0] + " to " + p[1]
095: */
096: org.openlaszlo.i18n.LaszloMessages.getMessage(Transcoder.class
097: .getName(), "051018-103", new Object[] { from, to }));
098:
099: // We assume this mime type is correct if we get it
100: // NOTE: This will keep us from copying big swf video files
101: // an extra time for now...
102: if (from.equalsIgnoreCase(MimeType.SWF)) {
103: return stream;
104: }
105:
106: // Try images
107: if (from.equalsIgnoreCase(MimeType.JPEG)
108: || from.equalsIgnoreCase(MimeType.PNG)
109: || from.equalsIgnoreCase(MimeType.GIF)
110: || from.indexOf("image") != -1) {
111: return convertImageToSWF(stream);
112: } else if (from.equalsIgnoreCase(MimeType.MP3)
113: || from.equalsIgnoreCase(MimeType.XMP3)
114: || from.indexOf("audio") != -1) {
115: // Try audio
116: return convertAudioToSWF(stream, doStream);
117: }
118:
119: BufferedInputStream bis = null;
120: try {
121: if (!stream.markSupported()) {
122: bis = new BufferedInputStream(stream);
123: stream = bis;
124: }
125: String mime = guessSupportedMimeTypeFromContent(stream);
126: if (mime != null) {
127: InputStream out = null;
128: if (mime.equals(MimeType.SWF)) {
129: out = bis;
130: } else {
131: out = transcode(bis, mime, to, doStream);
132: }
133: // Keep us from closing the stream
134: if (bis == out) {
135: bis = null;
136: }
137: return out;
138: } else {
139: throw new TranscoderException(
140: /* (non-Javadoc)
141: * @i18n.test
142: * @org-mes="can't guess a supported mime-type from content"
143: */
144: org.openlaszlo.i18n.LaszloMessages.getMessage(
145: Transcoder.class.getName(), "051018-152"));
146: }
147: } finally {
148: FileUtils.close(bis);
149: }
150: }
151:
152: /**
153: * @return mime type based on stream
154: */
155: public static String guessSupportedMimeTypeFromContent(
156: String fileName) throws IOException {
157: InputStream is = null;
158: try {
159: is = new BufferedInputStream(new FileInputStream(fileName));
160: return guessSupportedMimeTypeFromContent(is);
161: } finally {
162: FileUtils.close(is);
163: }
164: }
165:
166: /**
167: * @return mime type based on stream
168: * stream must be rewindable or we can't guess
169: */
170: public static String guessSupportedMimeTypeFromContent(
171: InputStream stream) throws IOException {
172: if (!stream.markSupported()) {
173: return null;
174: }
175:
176: try {
177: stream.mark(stream.available());
178:
179: mLogger.debug(
180: /* (non-Javadoc)
181: * @i18n.test
182: * @org-mes="trying swf"
183: */
184: org.openlaszlo.i18n.LaszloMessages.getMessage(
185: Transcoder.class.getName(), "051018-193"));
186: if (SWFUtils.hasSWFHeader(stream)) {
187: return MimeType.SWF;
188: }
189:
190: stream.reset();
191: if (GIF.is(stream)) {
192: return MimeType.GIF;
193: }
194:
195: stream.reset();
196: if (JPEG.is(stream)) {
197: return MimeType.JPEG;
198: }
199:
200: stream.reset();
201: if (PNG.is(stream)) {
202: return MimeType.PNG;
203: }
204:
205: stream.reset();
206: if (MP3.is(stream)) {
207: return MimeType.MP3;
208: }
209: } finally {
210: stream.reset();
211: }
212:
213: return null;
214: }
215:
216: /**
217: * @param input File to be transcoded
218: * @param from type of input data
219: * @param to type of output data
220: * @throws TranscoderException if there is no transcoder for
221: * the request from/to
222: */
223: public static InputStream transcode(File input, String from,
224: String to) throws TranscoderException, IOException {
225:
226: if (to.equalsIgnoreCase(FontType.FFT)) {
227: if (from.equalsIgnoreCase(FontType.TTF)) {
228: return TTF2FFT.convert(input);
229: } else {
230: throw new TranscoderException(
231: /* (non-Javadoc)
232: * @i18n.test
233: * @org-mes="Unknown input font type: " + p[0]
234: */
235: org.openlaszlo.i18n.LaszloMessages.getMessage(
236: Transcoder.class.getName(), "051018-245",
237: new Object[] { from }));
238: }
239: } else {
240: InputStream fis = new FileInputStream(input);
241: InputStream out = null;
242: try {
243: out = transcode(fis, from, to,
244: /* non-streaming media */false);
245: return out;
246: } finally {
247: if (fis != null && fis != out) {
248: fis.close();
249: }
250: }
251: }
252: }
253:
254: /**
255: * @param stream image input stream
256: */
257: private static final InputStream convertImageToSWF(
258: InputStream stream) throws IOException, TranscoderException {
259:
260: try {
261: mLogger.debug("converting image to SWF");
262:
263: Bitmap bitmap = Bitmap.newBitmap(new FlashBuffer(stream));
264: if (bitmap == null) {
265: String msg = "corrupt image or unknown image type";
266: throw new TranscoderException(msg);
267: }
268: mLogger.debug("done bitmap file");
269: Instance inst = bitmap.newInstance();
270: Script script;
271: script = new Script(1);
272: script.setMain();
273: script.newFrame().addInstance(inst, 1);
274: FlashFile file = FlashFile.newFlashFile();
275: file.setVersion(5);
276: file.setFrameSize(inst.getBounds());
277: file.setMainScript(script);
278: mLogger.debug(
279: /* (non-Javadoc)
280: * @i18n.test
281: * @org-mes="starting generate"
282: */
283: org.openlaszlo.i18n.LaszloMessages.getMessage(
284: Transcoder.class.getName(), "051018-292"));
285: FlashOutput out = file.generate();
286: mLogger.debug(
287: /* (non-Javadoc)
288: * @i18n.test
289: * @org-mes="ending generate"
290: */
291: org.openlaszlo.i18n.LaszloMessages.getMessage(
292: Transcoder.class.getName(), "051018-301"));
293: return out.getInputStream();
294: } catch (IVException e) {
295: throw new TranscoderException("iv exception:"
296: + e.getMessage());
297: }
298: }
299:
300: /**
301: * @param stream audio input stream
302: * @param doStream if true, convert to streaming audio output
303: */
304: private static final InputStream convertAudioToSWF(
305: InputStream stream, boolean doStream) throws IOException,
306: TranscoderException {
307:
308: // Stream and add a stop play command.
309: try {
310: return convertAudioToSWF(stream, doStream, true, 0, 0);
311: } catch (IVException e) {
312: throw new TranscoderException("iv exception:"
313: + e.getMessage());
314: }
315: }
316:
317: /**
318: * @param stream audio input stream
319: */
320: private static final InputStream convertAudioToSWF(InputStream in,
321: boolean stream, boolean stopAction, int delay,
322: int startframe) throws IOException, IVException {
323:
324: Script script;
325: script = new Script(1);
326:
327: FlashFile file = FlashFile.newFlashFile();
328:
329: // 30 FPS gets us 16000/30/60 = 8 minutes 53 sec max
330: final int MAX_SWF_FRAMES = 16000;
331: int mFrameRate = 30;
332: try {
333: String f = LPS.getProperty("lps.swf.audio.framerate", "30");
334: mFrameRate = Integer.parseInt(f);
335: } catch (Exception e) {
336: mLogger.error(
337: /* (non-Javadoc)
338: * @i18n.test
339: * @org-mes="Can't read property file for lps.swf.audio.framerate"
340: */
341: org.openlaszlo.i18n.LaszloMessages.getMessage(
342: Transcoder.class.getName(), "051018-348"));
343: }
344:
345: file.setFrameRate(mFrameRate << 8);
346:
347: Frame stopFrame = null;
348:
349: FlashBuffer fib = new FlashBuffer(in);
350:
351: if (stream) {
352: mLogger.debug(
353: /* (non-Javadoc)
354: * @i18n.test
355: * @org-mes="transcoding streaming mp3"
356: */
357: org.openlaszlo.i18n.LaszloMessages.getMessage(
358: Transcoder.class.getName(), "051018-365"));
359: SoundStreamBuilder ssb = SoundStreamBuilder
360: .newSoundStreamBuilder(fib, file.getFrameRate());
361: SoundStreamHead head = ssb.getSoundStreamHead();
362:
363: // Add the SoundStreamHead to the current startframe in the script
364: script.getFrameAt(startframe).addFlashObject(head);
365:
366: int frameCount = script.getFrameCount();
367: int f = startframe;
368: SoundStreamBlock block;
369:
370: while ((block = ssb.getNextSoundStreamBlock()) != null) {
371: if (f >= frameCount) {
372: script.newFrame().addFlashObject(block);
373: } else {
374: script.getFrameAt(f).addFlashObject(block);
375: }
376:
377: f++;
378: if (f >= MAX_SWF_FRAMES) {
379: String msg =
380: /* (non-Javadoc)
381: * @i18n.test
382: * @org-mes="LPS hit max SWF frame count when converting this clip" + "; truncating it at " + p[0] + " frames"
383: */
384: org.openlaszlo.i18n.LaszloMessages
385: .getMessage(Transcoder.class.getName(),
386: "051018-392",
387: new Object[] { new Integer(
388: MAX_SWF_FRAMES) });
389: mLogger.warn(msg);
390: script.getFrameAt(0).addFlashObject(
391: WarningProgram(msg));
392: break;
393: }
394: }
395:
396: stopFrame = script.getFrameAt(f - 1);
397: } else {
398: mLogger.debug("transcoding non-streaming mp3");
399: MP3Sound sound = MP3Sound.newMP3Sound(fib);
400: // Set the delay if provided
401: if (delay != 0) {
402: sound.setDelaySeek(delay);
403: }
404:
405: SoundInfo soundInfo = SoundInfo.newSoundInfo(0);
406: StartSound startSound = StartSound.newStartSound(sound,
407: soundInfo);
408:
409: Frame newFrame = script.newFrame();
410: newFrame.addFlashObject(startSound);
411:
412: stopFrame = newFrame;
413: }
414:
415: if (stopAction) {
416: stopFrame.addStopAction();
417: }
418:
419: file.setVersion(5);
420: file.setFrameSize(GeomHelper.newRectangle(0, 0, 0, 0));
421: file.setMainScript(script);
422: FlashOutput out = file.generate();
423: return out.getInputStream();
424: }
425:
426: /**
427: * Return a FlashObject that contains a program that
428: * will print an LFC warning to the debugger
429: */
430: private static FlashObject WarningProgram(String msg) {
431: String p = "_root.debug.write('" + msg + "');";
432: byte[] action = ScriptCompiler.compileToByteArray(p,
433: new Properties());
434: return new DoAction(new Program(action, 0, action.length));
435: }
436: }
|