1 package com.hammurapi.jcapture; 2 3 import java.awt.AlphaComposite; 4 import java.awt.Dimension; 5 import java.awt.Graphics2D; 6 import java.awt.Point; 7 import java.awt.RenderingHints; 8 import java.awt.image.BufferedImage; 9 import java.io.ByteArrayInputStream; 10 import java.io.ByteArrayOutputStream; 11 import java.io.File; 12 import java.io.OutputStream; 13 import java.text.MessageFormat; 14 import java.util.ArrayList; 15 import java.util.IdentityHashMap; 16 import java.util.List; 17 import java.util.Map; 18 import java.util.concurrent.atomic.AtomicInteger; 19 20 import javax.imageio.ImageIO; 21 import javax.swing.JOptionPane; 22 import javax.swing.ProgressMonitor; 23 24 import com.flagstone.transform.Background; 25 import com.flagstone.transform.DefineTag; 26 import com.flagstone.transform.DoAction; 27 import com.flagstone.transform.Movie; 28 import com.flagstone.transform.MovieHeader; 29 import com.flagstone.transform.MovieTag; 30 import com.flagstone.transform.Place2; 31 import com.flagstone.transform.Remove; 32 import com.flagstone.transform.Remove2; 33 import com.flagstone.transform.ShowFrame; 34 import com.flagstone.transform.action.Action; 35 import com.flagstone.transform.action.BasicAction; 36 import com.flagstone.transform.coder.Coder; 37 import com.flagstone.transform.datatype.Bounds; 38 import com.flagstone.transform.datatype.CoordTransform; 39 import com.flagstone.transform.datatype.WebPalette; 40 import com.flagstone.transform.image.ImageTag; 41 import com.flagstone.transform.util.image.ImageDecoder; 42 import com.flagstone.transform.util.image.ImageRegistry; 43 import com.flagstone.transform.util.image.ImageShape; 44 import com.flagstone.transform.util.shape.Canvas; 45 import com.flagstone.transform.util.sound.SoundFactory; 46 import com.hammurapi.jcapture.VideoEncoder.Fragment.Frame; 47 import com.hammurapi.jcapture.VideoEncoder.Fragment.Frame.Shape; 48 import com.hammurapi.jcapture.VideoEncoder.Fragment.Frame.Shape.Image; 49 import com.hammurapi.jcapture.VideoEncoder.Fragment.Frame.Shape.ImageReference; 50 import com.hammurapi.jcapture.VideoEncoder.Fragment.Frame.Shape.ShapeContent; 51 52 public class SwfEncoder implements VideoEncoder { 53 54 @Override getFileExtension()55 public String getFileExtension() { 56 return "swf"; 57 } 58 59 @Override getMimeType()60 public String getMimeType() { 61 return "application/x-shockwave-flash"; 62 } 63 64 @Override toString()65 public String toString() { 66 return "SWF"; 67 } 68 69 @Override encode(Config config, com.hammurapi.jcapture.Movie source, OutputStream out)70 public Dimension encode(Config config, 71 com.hammurapi.jcapture.Movie source, 72 OutputStream out) 73 throws Exception { 74 75 AtomicInteger uid = new AtomicInteger(); 76 77 /** 78 * For reusing shape id's. 79 */ 80 int maxId = Coder.USHORT_MAX; 81 82 ButtonManager manager = null; 83 if (config.isToolBar()) { 84 manager = new ButtonManager(); 85 manager.loadLibrary(getClass().getResource("toolbar_buttons.swf")); 86 uid.set(manager.maxIdentifier()+1); 87 } 88 89 Canvas path = new Canvas(); 90 path.setPixels(false); 91 92 int minImgLayer = 10; 93 int imgLayer = minImgLayer; 94 int maxImgLayer = maxId - 1000; 95 96 int mouseLayer = maxImgLayer+1; 97 int mouseUid = -1; 98 Place2 mousePlace = null; 99 ImageTag mouseImage = null; 100 101 int layer = maxImgLayer+2; 102 103 int totalWork = 0; 104 for (Fragment frg: source.getFragments()) { 105 totalWork = frg.getFrames().size()+1; 106 } 107 108 ProgressMonitor progressMonitor = new ProgressMonitor(config.getParentComponent(), "Encoding to SWF", "Composing movie", 0, totalWork); 109 int progressCounter = 0; 110 111 progressMonitor.setNote("Composing movie"); 112 boolean firstFrame = true; 113 Dimension ret = null; 114 Map<Image, ImageTag> imageCache = new IdentityHashMap<Image, ImageTag>(); 115 116 Movie movie = new Movie(); 117 118 Point prevMouseLocation = null; 119 120 int frameNo = 0; 121 122 for (Fragment fragment: source.getFragments()) { 123 124 SoundFactory soundFactory = null; 125 boolean soundHeaderAdded = false; 126 File audio = fragment.getAudio(); 127 if (audio!=null) { 128 progressMonitor.setNote("Loading sound"); 129 soundFactory = new SoundFactory(); 130 131 // MP3 conversion 132 if (config.getMp3command()!=null && config.getMp3command().trim().length()>0) { 133 audio = new File(audio.getAbsolutePath()+".mp3"); 134 Runtime runtime = Runtime.getRuntime(); 135 Process proc = runtime.exec(MessageFormat.format(config.getMp3command(), new Object[] {fragment.getAudio().getAbsolutePath(), audio.getAbsolutePath()})); 136 proc.waitFor(); 137 if (!fragment.getAudio().delete()) { 138 fragment.getAudio().deleteOnExit(); 139 } 140 } 141 142 soundFactory.read(audio); 143 } 144 145 progressMonitor.setProgress(++progressCounter); 146 147 if (progressMonitor.isCanceled()) { 148 return null; 149 } 150 151 for (Frame frame: fragment.getFrames()) { 152 if (progressMonitor.isCanceled()) { 153 return null; 154 } 155 156 boolean addStop = false; 157 158 ++frameNo; 159 160 if (firstFrame) { 161 firstFrame = false; 162 163 MovieHeader header = new MovieHeader(); 164 header.setCompressed(true); 165 header.setFrameRate(source.getFramesPerSecond()); 166 167 int toolbarHeight = 29 * 20; 168 int toolbarWidth = 495 * 20; 169 170 int toolbarX = 0; // - image.getWidth()*20/2; 171 int toolbarY = frame.getSize().height*20; 172 173 int movieWidth = frame.getSize().width*20; 174 int movieHeight = frame.getSize().height*20; 175 if (config.isToolBar()) { 176 movieHeight+=toolbarHeight; 177 } 178 179 ret = new Dimension(movieWidth/20, movieHeight/20); 180 181 float toolbarScaleX = (float) movieWidth / (float) toolbarWidth; 182 float toolbarScaleY = 1.0f; 183 184 Bounds movieBounds = new Bounds(0, 0, movieWidth, movieHeight); 185 header.setFrameSize(movieBounds); 186 movie.add(header); 187 movie.add(new Background(WebPalette.WHITE.color())); 188 189 if (config.isToolBar()) { 190 // Add all the shapes etc used for buttons 191 List<DefineTag> toolbarDefinitions = manager.getDefinitions(); 192 movie.getObjects().addAll(toolbarDefinitions); 193 194 Place2 placeBackground = manager.getButton("background", layer++, 0, 0); 195 placeBackground.setTransform(new CoordTransform(toolbarScaleX, toolbarScaleY, 0, 0, toolbarX, toolbarY)); 196 197 // Get the button to use and give its position 198 movie.add(placeBackground); 199 movie.add(manager.getButton("play_button", layer++, toolbarX + 500, toolbarY + toolbarHeight / 2)); 200 movie.add(manager.getButton("progress_bar", layer++, toolbarX + 1000, toolbarY + toolbarHeight / 2)); 201 movie.add(manager.getButton("volume_control", layer++, toolbarX + 5600, toolbarY + toolbarHeight / 2)); 202 203 if (!config.isPlay()) { 204 addStop = true; 205 } 206 } 207 } 208 209 if (!soundHeaderAdded && soundFactory!=null) { 210 movie.add(soundFactory.streamHeader(source.getFramesPerSecond())); 211 soundHeaderAdded = true; 212 } 213 214 if (soundFactory!=null) { 215 MovieTag soundBlock = soundFactory.streamSound(); 216 if (soundBlock != null) { 217 movie.add(soundBlock); 218 } 219 } 220 221 for (Shape shape: frame.getShapes()) { 222 if (shape.getContent().coversEverything() || imgLayer==maxImgLayer) { 223 for (int i=minImgLayer; i<=imgLayer; ++i) { 224 movie.add(new Remove2(i)); 225 } 226 imgLayer = minImgLayer; 227 } 228 229 ShapeContent shapeContent = shape.getContent(); 230 Image image; 231 if (shapeContent instanceof Image) { 232 image = (Image) shapeContent; 233 } else if (shape.getContent() instanceof ImageReference) { 234 image = ((ImageReference) shapeContent).getImage(); 235 } else { 236 throw new IllegalArgumentException("Unexpected content type: "+shapeContent); 237 } 238 239 ImageTag imageTag = imageCache.get(image); 240 if (imageTag==null) { 241 try { 242 ImageDecoder decoder = ImageRegistry.getImageProvider("image/"+config.getImageFormat().toLowerCase()); 243 decoder.read(new ByteArrayInputStream(image.getImage().getImageBytes())); 244 imageTag = decoder.defineImage(uid.incrementAndGet()); 245 imageCache.put(image, imageTag); 246 movie.add(imageTag); 247 } catch (Exception e) { 248 // Doing our best to create movie, even with flaws. 249 System.err.println("Error encoding image at frame "+frameNo+": "+e); 250 e.printStackTrace(); 251 if (JOptionPane.showConfirmDialog(config.getParentComponent(), 252 "Error encoding image ("+image.getImage().getWidth()+"*"+image.getImage().getHeight()+") at frame "+frameNo+": "+e+". Continue encoding?", 253 "Encoding error", 254 JOptionPane.YES_NO_OPTION, 255 JOptionPane.ERROR_MESSAGE)==JOptionPane.NO_OPTION) { 256 throw e; 257 } 258 } 259 } 260 261 int shapeId = uid.incrementAndGet(); 262 DefineTag shapeTag = new ImageShape().defineShape(shapeId, imageTag); 263 Place2 place = Place2.show(shapeTag.getIdentifier(), imgLayer++, shape.getLocation().x*20, shape.getLocation().y*20); 264 movie.add(shapeTag); 265 movie.add(place); 266 } 267 268 Point mouseLocation = frame.getMousePointer(); 269 if (mouseLocation!=null) { 270 if (mouseImage==null) { 271 BufferedImage mouseBi = ImageIO.read(getClass().getResource("mouse.png")); 272 if (config.getScreenScale()<0.99 || config.getScreenScale() > 1.01) { 273 BufferedImage scaled = new BufferedImage((int) (mouseBi.getWidth()*config.getScreenScale()), (int) (mouseBi.getHeight()*config.getScreenScale()), mouseBi.getType()); 274 Graphics2D g = scaled.createGraphics(); 275 g.setComposite(AlphaComposite.Src); 276 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR); 277 g.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY); 278 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); 279 g.drawImage(mouseBi, 0, 0, scaled.getWidth(), scaled.getHeight(), null); 280 g.dispose(); 281 mouseBi = scaled; 282 } 283 284 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 285 ImageIO.write(mouseBi, "PNG", baos); 286 baos.close(); 287 ImageDecoder decoder = ImageRegistry.getImageProvider("image/png"); 288 decoder.read(new ByteArrayInputStream(baos.toByteArray())); 289 mouseImage = decoder.defineImage(uid.incrementAndGet()); 290 movie.add(mouseImage); 291 } 292 293 if (!mouseLocation.equals(prevMouseLocation)) { 294 prevMouseLocation = mouseLocation; 295 mouseUid = uid.incrementAndGet(); 296 DefineTag mShape = new ImageShape().defineShape(uid.incrementAndGet(), mouseImage); //createRect(mouseUid, 100, 100, WebPalette.RED.color()); 297 if (mousePlace==null) { 298 mousePlace = Place2.show(mShape.getIdentifier(), mouseLayer, mouseLocation.x*20, mouseLocation.y*20); 299 } else { 300 mousePlace = Place2.replace(mShape.getIdentifier(), mouseLayer, mouseLocation.x*20, mouseLocation.y*20); 301 } 302 movie.add(mShape); 303 movie.add(mousePlace); 304 } 305 } else if (mouseUid!=-1) { 306 Remove remove = new Remove(mouseUid, mouseLayer); 307 movie.add(remove); 308 } 309 310 if (addStop) { 311 DoAction cmd = new DoAction(new ArrayList<Action>()); 312 cmd.add(BasicAction.STOP); 313 movie.add(cmd); 314 } 315 movie.add(ShowFrame.getInstance()); 316 317 progressMonitor.setProgress(++progressCounter); 318 } 319 320 progressMonitor.setProgress(++progressCounter); 321 if (soundFactory!=null) { 322 progressMonitor.setNote("Recording trailing sound"); 323 MovieTag block; 324 while ((block = soundFactory.streamSound()) != null) { 325 movie.add(block); 326 movie.add(ShowFrame.getInstance()); 327 } 328 } 329 330 if (audio!=null) { 331 if (!audio.delete()) { 332 audio.deleteOnExit(); 333 } 334 } 335 } 336 337 if (!config.isLoop()) { 338 List<Action> actions = new ArrayList<Action>(); 339 actions.add(BasicAction.STOP); 340 actions.add(BasicAction.END); 341 DoAction doAction = new DoAction(actions); 342 movie.add(doAction); 343 movie.add(ShowFrame.getInstance()); 344 } 345 346 progressMonitor.setProgress(++progressCounter); 347 progressMonitor.setNote("Encoding movie"); 348 movie.encodeToStream(out); 349 source.close(); 350 return ret; 351 } 352 353 } 354