!7 import org.jcodec.api.awt.AWTSequenceEncoder8Bit; static int framesToEncode = 100000; p { inputFilePath("Video to modify", voidfunc(final File video) { thread { invertVideo(video); } }); } svoid invertVideo(File video) { File outFile = prepareProgramFile(addSuffix("inverted-" + video.getName(), ".mp4")); final LeftQueue lq = new LeftQueue(video); final RightQueue rq = new RightQueue(outFile); lq.rightQueue = rq; stepThread(lq); stepThread(rq); } svoid stepThread(Steppable s) { Thread thread = new Thread("Stepper Thread") { public void run() { s.step(); while licensed { while (s.shouldStep) { s.shouldStep = false; s.step(); } sleep(); } } }; s.thread = thread; thread.start(); } abstract sclass Steppable { volatile bool shouldStep; Thread thread; void shouldStep() { shouldStep = true; if (thread != null) thread.interrupt(); } abstract void step; } // decoding part Steppable > LeftQueue { Iterator stream; RightQueue rightQueue; *(File video) { stream = framesFromVideo_reordering(video); } void step { if (stream != null && rightQueue.needImage()) if (stream.hasNext()) { rightQueue.receive(stream.next()); shouldStep(); } else { stream = null; rightQueue.close(); } } } // encoding part Steppable > RightQueue { File outMP4; AWTSequenceEncoder8Bit enc; volatile BufferedImage img; int frames; volatile bool shouldClose; *(File *outMP4) { AWTSequenceEncoder8Bit enc = AWTSequenceEncoder8Bit.create25Fps(outMP4); enc.getEncoder().setKeyInterval(25); } void receive(BufferedImage img) { if (this.img != null) fail(); this.img = img; shouldStep(); } bool needImage() { ret img == null; } void step { if (img != null) { img = invertedImage(img); enc.encodeImage(img); img = null; ++frames; print("Frames: " + frames + "/" + framesToEncode); if (frames >= framesToEncode) shouldClose = true; else shouldStep(); } } void close { enc.finish(); print("Wrote " + f2s(outMP4) + " (" + frames + " frames)"); } }