private void selectInner(List<Edit> edits, Slice cut, MovieBox movie, TrakBox trakBox) { long inMv = (long) (movie.getTimescale() * cut.inSec); long outMv = (long) (movie.getTimescale() * cut.outSec); long editStart = 0; ListIterator<Edit> lit = edits.listIterator(); while (lit.hasNext()) { Edit edit = lit.next(); if (editStart + edit.getDuration() <= inMv || editStart >= outMv) lit.remove(); editStart += edit.getDuration(); } }
private void selectOuter( List<Edit> edits, List<Slice> commands, MovieBox movie, TrakBox trakBox) { long[] inMv = new long[commands.size()]; long[] outMv = new long[commands.size()]; for (int i = 0; i < commands.size(); i++) { inMv[i] = (long) (commands.get(i).inSec * movie.getTimescale()); outMv[i] = (long) (commands.get(i).outSec * movie.getTimescale()); } long editStartMv = 0; ListIterator<Edit> lit = edits.listIterator(); while (lit.hasNext()) { Edit edit = lit.next(); for (int i = 0; i < inMv.length; i++) { if (editStartMv + edit.getDuration() > inMv[i] && editStartMv < outMv[i]) lit.remove(); } editStartMv += edit.getDuration(); } }
private int calcSpaceReq(MovieBox movie) { int sum = 0; TrakBox[] tracks = movie.getTracks(); for (int i = 0; i < tracks.length; i++) { TrakBox trakBox = tracks[i]; ChunkOffsetsBox stco = trakBox.getStco(); if (stco != null) sum += stco.getChunkOffsets().length * 4; } return sum; }
@Test public void testAVCClip() throws IOException { File f1 = new File("src/test/resources/AVCClipCatTest/seq_1.mp4"); MovieBox m1 = MP4Util.parseMovie(f1); VirtualTrack t1 = new AVCClipTrack(new RealTrack(m1, m1.getVideoTrack(), new FilePool(f1, 10)), 60, 120); VirtualMovie vm = new VirtualMP4Movie(t1); MovieRange range = new MovieRange(vm, 0, vm.size()); BufferedOutputStream out = new BufferedOutputStream( new FileOutputStream( new File(System.getProperty("user.home"), "Desktop/precise_clip.mp4"))); IOUtils.copy(range, out); out.flush(); out.close(); }
@Test public void testAVCClipCat() throws IOException { File f1 = new File("src/test/resources/AVCClipCatTest/seq_1.mp4"); File f2 = new File("src/test/resources/AVCClipCatTest/seq_2.mp4"); File f3 = new File("src/test/resources/AVCClipCatTest/seq_3.mp4"); MovieBox m1 = MP4Util.parseMovie(f1); MovieBox m2 = MP4Util.parseMovie(f2); MovieBox m3 = MP4Util.parseMovie(f3); VirtualTrack t1 = new AVCClipTrack(new RealTrack(m1, m1.getVideoTrack(), new FilePool(f1, 10)), 60, 120); VirtualTrack t2 = new AVCClipTrack(new RealTrack(m2, m2.getVideoTrack(), new FilePool(f2, 10)), 60, 120); VirtualTrack t3 = new AVCClipTrack(new RealTrack(m3, m3.getVideoTrack(), new FilePool(f3, 10)), 60, 120); AVCConcatTrack ct = new AVCConcatTrack(t1, t2, t3); VirtualMovie vm = new VirtualMP4Movie(ct); MovieRange range = new MovieRange(vm, 0, vm.size()); BufferedOutputStream out = new BufferedOutputStream( new FileOutputStream( new File(System.getProperty("user.home"), "Desktop/cat_avc_clip.mp4"))); IOUtils.copy(range, out); out.flush(); out.close(); }
protected SeekableByteChannel[][] getInputs(MovieBox movie) throws IOException { TrakBox[] tracks = movie.getTracks(); SeekableByteChannel[][] result = new SeekableByteChannel[tracks.length][]; for (int i = 0; i < tracks.length; i++) { DataRefBox drefs = NodeBox.findFirstPath(tracks[i], DataRefBox.class, Box.path("mdia.minf.dinf.dref")); if (drefs == null) { throw new RuntimeException("No data references"); } List<Box> entries = drefs.getBoxes(); SeekableByteChannel[] e = new SeekableByteChannel[entries.size()]; SeekableByteChannel[] inputs = new SeekableByteChannel[entries.size()]; for (int j = 0; j < e.length; j++) { inputs[j] = resolveDataRef(entries.get(j)); } result[i] = inputs; } return result; }
public List<MovieBox> cut(MovieBox movie, List<Slice> commands) { TrakBox videoTrack = movie.getVideoTrack(); if (videoTrack != null && videoTrack.getTimescale() != movie.getTimescale()) movie.fixTimescale(videoTrack.getTimescale()); TrakBox[] tracks = movie.getTracks(); for (int i = 0; i < tracks.length; i++) { TrakBox trakBox = tracks[i]; forceEditList(movie, trakBox); List<Edit> edits = trakBox.getEdits(); for (Slice cut : commands) { split(edits, cut.inSec, movie, trakBox); split(edits, cut.outSec, movie, trakBox); } } ArrayList<MovieBox> result = new ArrayList<MovieBox>(); for (Slice cut : commands) { MovieBox clone = (MovieBox) NodeBox.cloneBox(movie, 16 * 1024 * 1024, BoxFactory.getDefault()); for (TrakBox trakBox : clone.getTracks()) { selectInner(trakBox.getEdits(), cut, movie, trakBox); } result.add(clone); } long movDuration = 0; for (TrakBox trakBox : movie.getTracks()) { selectOuter(trakBox.getEdits(), commands, movie, trakBox); trakBox.setEdits(trakBox.getEdits()); movDuration = max(movDuration, trakBox.getDuration()); } movie.setDuration(movDuration); return result; }
public void flatternChannel(MovieBox movie, SeekableByteChannel out) throws IOException { if (!movie.isPureRefMovie(movie)) throw new IllegalArgumentException("movie should be reference"); ByteBuffer buf = ByteBuffer.allocate(16 * 1024 * 1024); FileTypeBox ftyp = FileTypeBox.createFileTypeBox("qt ", 0x20050300, Arrays.asList(new String[] {"qt "})); ftyp.write(buf); long movieOff = buf.position(); movie.write(buf); int extraSpace = calcSpaceReq(movie); Header.createHeader("free", 8 + extraSpace).write(buf); NIOUtils.skip(buf, extraSpace); long mdatOff = buf.position(); Header.createHeader("mdat", 0x100000001L).write(buf); buf.flip(); out.write(buf); SeekableByteChannel[][] inputs = getInputs(movie); TrakBox[] tracks = movie.getTracks(); ChunkReader[] readers = new ChunkReader[tracks.length]; ChunkWriter[] writers = new ChunkWriter[tracks.length]; Chunk[] head = new Chunk[tracks.length]; int totalChunks = 0, writtenChunks = 0, lastProgress = 0; long[] off = new long[tracks.length]; for (int i = 0; i < tracks.length; i++) { readers[i] = new ChunkReader(tracks[i]); totalChunks += readers[i].size(); writers[i] = new ChunkWriter(tracks[i], inputs[i], out); head[i] = readers[i].next(); if (tracks[i].isVideo()) off[i] = 2 * movie.getTimescale(); } while (true) { int min = -1; for (int i = 0; i < readers.length; i++) { if (head[i] == null) continue; if (min == -1) min = i; else { long iTv = movie.rescale(head[i].getStartTv(), tracks[i].getTimescale()) + off[i]; long minTv = movie.rescale(head[min].getStartTv(), tracks[min].getTimescale()) + off[min]; if (iTv < minTv) min = i; } } if (min == -1) break; writers[min].write(head[min]); head[min] = readers[min].next(); writtenChunks++; lastProgress = calcProgress(totalChunks, writtenChunks, lastProgress); } long mdatSize = out.position() - mdatOff; for (int i = 0; i < tracks.length; i++) { writers[i].apply(); } out.setPosition(movieOff); MP4Util.writeMovie(out, movie); long extra = mdatOff - out.position(); if (extra < 0) throw new RuntimeException("Not enough space to write the header"); out.write( (ByteBuffer) ByteBuffer.allocate(8).putInt((int) extra).put(new byte[] {'f', 'r', 'e', 'e'}).flip()); out.setPosition(mdatOff + 8); out.write(ByteBuffer.allocate(8).putLong(mdatSize)); }
private void split(List<Edit> edits, double sec, MovieBox movie, TrakBox trakBox) { Util.split(movie, trakBox, (long) (sec * movie.getTimescale())); }