package org.jcodec.movtool; import java.lang.IllegalStateException; import java.lang.System; import static java.util.Arrays.fill; import static org.jcodec.common.io.NIOUtils.readableChannel; import static org.jcodec.common.io.NIOUtils.writableChannel; import static org.jcodec.containers.mp4.MP4Util.createRefMovie; import static org.jcodec.movtool.Util.forceEditListMov; import static org.jcodec.movtool.Util.insertTo; import static org.jcodec.movtool.Util.shift; import static org.jcodec.movtool.Util.spread; import org.jcodec.common.io.SeekableByteChannel; import org.jcodec.containers.mp4.MP4Util; import org.jcodec.containers.mp4.boxes.Box; import org.jcodec.containers.mp4.boxes.ClipRegionBox; import org.jcodec.containers.mp4.boxes.LoadSettingsBox; import org.jcodec.containers.mp4.boxes.MovieBox; import org.jcodec.containers.mp4.boxes.NodeBox; import org.jcodec.containers.mp4.boxes.SampleSizesBox; import org.jcodec.containers.mp4.boxes.SoundMediaHeaderBox; import org.jcodec.containers.mp4.boxes.TrackHeaderBox; import org.jcodec.containers.mp4.boxes.TrakBox; import org.jcodec.containers.mp4.boxes.VideoMediaHeaderBox; import org.jcodec.platform.Platform; import java.io.File; /** * This class is part of JCodec ( www.jcodec.org ) This software is distributed * under FreeBSD License * * Paste on ref movies * * @author The JCodec project * */ public class Paste { public static void main1(String[] args) throws Exception { if (args.length < 2) { System.out.println("Syntax: paste <to movie> <from movie> [second]"); System.exit(-1); } File toFile = new File(args[0]); SeekableByteChannel to = null; SeekableByteChannel from = null; SeekableByteChannel out = null; try { File outFile = new File(toFile.getParentFile(), toFile.getName().replaceAll("\\.mov$", "") + ".paste.mov"); Platform.deleteFile(outFile); out = writableChannel(outFile); to = writableChannel(toFile); File fromFile = new File(args[1]); from = readableChannel(fromFile); MovieBox toMov = createRefMovie(to, "file://" + toFile.getCanonicalPath()); MovieBox fromMov = createRefMovie(from, "file://" + fromFile.getCanonicalPath()); new Strip().strip(fromMov); if (args.length > 2) { new Paste().paste(toMov, fromMov, Double.parseDouble(args[2])); } else { new Paste().addToMovie(toMov, fromMov); } MP4Util.writeMovie(out, toMov); } finally { if (to != null) to.close(); if (from != null) from.close(); if (out != null) out.close(); } } public void paste(MovieBox to, MovieBox from, double sec) { TrakBox videoTrack = to.getVideoTrack(); if (videoTrack != null && videoTrack.getTimescale() != to.getTimescale()) to.fixTimescale(videoTrack.getTimescale()); long displayTv = (long) (to.getTimescale() * sec); forceEditListMov(to); forceEditListMov(from); TrakBox[] fromTracks = from.getTracks(); TrakBox[] toTracks = to.getTracks(); int[][] matches = findMatches(fromTracks, toTracks); for (int i = 0; i < matches[0].length; i++) { TrakBox localTrack = to.importTrack(from, fromTracks[i]); if (matches[0][i] != -1) { insertTo(to, toTracks[matches[0][i]], localTrack, displayTv); } else { to.appendTrack(localTrack); shift(to, localTrack, displayTv); } } for (int i = 0; i < matches[1].length; i++) { if (matches[1][i] == -1) { spread(to, toTracks[i], displayTv, to.rescale(from.getDuration(), from.getTimescale())); } } to.updateDuration(); } public void addToMovie(MovieBox to, MovieBox from) { TrakBox[] tracks = from.getTracks(); for (int i = 0; i < tracks.length; i++) { TrakBox track = tracks[i]; to.appendTrack(to.importTrack(from, track)); } } long[] tv; private long getFrameTv(TrakBox videoTrack, int frame) { if (tv == null) { tv = Util.getTimevalues(videoTrack); } return tv[frame]; } private int[][] findMatches(TrakBox[] fromTracks, TrakBox[] toTracks) { int[] f2t = new int[fromTracks.length]; int[] t2f = new int[toTracks.length]; fill(f2t, -1); fill(t2f, -1); for (int i = 0; i < fromTracks.length; i++) { if (f2t[i] != -1) continue; for (int j = 0; j < toTracks.length; j++) { if (t2f[j] != -1) continue; if (matches(fromTracks[i], toTracks[j])) { f2t[i] = j; t2f[j] = i; break; } } } return new int[][] { f2t, t2f }; } private boolean matches(TrakBox trakBox1, TrakBox trakBox2) { return trakBox1.getHandlerType().equals(trakBox2.getHandlerType()) && matchHeaders(trakBox1, trakBox2) && matchSampleSizes(trakBox1, trakBox2) && matchMediaHeader(trakBox1, trakBox2) && matchClip(trakBox1, trakBox2) && matchLoad(trakBox1, trakBox2); } private boolean matchSampleSizes(TrakBox trakBox1, TrakBox trakBox2) { SampleSizesBox stsz1 = NodeBox.findFirstPath(trakBox1, SampleSizesBox.class, Box.path("mdia.minf.stbl.stsz")); SampleSizesBox stsz2 = NodeBox.findFirstPath(trakBox1, SampleSizesBox.class, Box.path("mdia.minf.stbl.stsz")); return stsz1.getDefaultSize() == stsz2.getDefaultSize(); } private boolean matchMediaHeader(TrakBox trakBox1, TrakBox trakBox2) { VideoMediaHeaderBox vmhd1 = NodeBox.findFirstPath(trakBox1, VideoMediaHeaderBox.class, Box.path("mdia.minf.vmhd")); VideoMediaHeaderBox vmhd2 = NodeBox.findFirstPath(trakBox2, VideoMediaHeaderBox.class, Box.path("mdia.minf.vmhd")); if ((vmhd1 != null && vmhd2 == null) || (vmhd1 == null && vmhd2 != null)) return false; else if (vmhd1 != null && vmhd2 != null) { return vmhd1.getGraphicsMode() == vmhd2.getGraphicsMode() && vmhd1.getbOpColor() == vmhd2.getbOpColor() && vmhd1.getgOpColor() == vmhd2.getgOpColor() && vmhd1.getrOpColor() == vmhd2.getrOpColor(); } else { SoundMediaHeaderBox smhd1 = NodeBox.findFirstPath(trakBox1, SoundMediaHeaderBox.class, Box.path("mdia.minf.smhd")); SoundMediaHeaderBox smhd2 = NodeBox.findFirstPath(trakBox2, SoundMediaHeaderBox.class, Box.path("mdia.minf.smhd")); if ((smhd1 == null && smhd2 != null) || (smhd1 != null && smhd2 == null)) return false; else if (smhd1 != null && smhd2 != null) return smhd1.getBalance() == smhd1.getBalance(); } return true; } private boolean matchHeaders(TrakBox trakBox1, TrakBox trakBox2) { TrackHeaderBox th1 = trakBox1.getTrackHeader(); TrackHeaderBox th2 = trakBox2.getTrackHeader(); return ("vide".equals(trakBox1.getHandlerType()) && Platform.arrayEqualsInt(th1.getMatrix(), th2.getMatrix()) && th1.getLayer() == th2.getLayer() && th1.getWidth() == th2.getWidth() && th1.getHeight() == th2 .getHeight()) || ("soun".equals(trakBox1.getHandlerType()) && th1.getVolume() == th2.getVolume()) || "tmcd".equals(trakBox1.getHandlerType()); } private boolean matchLoad(TrakBox trakBox1, TrakBox trakBox2) { LoadSettingsBox load1 = NodeBox.findFirst(trakBox1, LoadSettingsBox.class, "load"); LoadSettingsBox load2 = NodeBox.findFirst(trakBox2, LoadSettingsBox.class, "load"); if (load1 == null && load2 == null) return true; if ((load1 == null && load2 != null) || (load1 != null && load2 == null)) return false; return load1.getPreloadStartTime() == load2.getPreloadStartTime() && load1.getPreloadDuration() == load2.getPreloadDuration() && load1.getPreloadFlags() == load2.getPreloadFlags() && load1.getDefaultHints() == load2.getDefaultHints(); } private boolean matchClip(TrakBox trakBox1, TrakBox trakBox2) { ClipRegionBox crgn1 = NodeBox.findFirstPath(trakBox1, ClipRegionBox.class, Box.path("clip.crgn")); ClipRegionBox crgn2 = NodeBox.findFirstPath(trakBox2, ClipRegionBox.class, Box.path("clip.crgn")); if ((crgn1 == null && crgn2 != null) || (crgn1 != null && crgn2 == null)) return false; if (crgn1 == null && crgn2 == null) return true; return crgn1.getRgnSize() == crgn2.getRgnSize() && crgn1.getX() == crgn2.getX() && crgn1.getY() == crgn2.getY() && crgn1.getWidth() == crgn2.getWidth() && crgn1.getHeight() == crgn2.getHeight(); } }