diff options
Diffstat (limited to 'isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/FragmentedMp4Builder.java.svn-base')
-rw-r--r-- | isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/FragmentedMp4Builder.java.svn-base | 742 |
1 files changed, 742 insertions, 0 deletions
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/FragmentedMp4Builder.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/FragmentedMp4Builder.java.svn-base new file mode 100644 index 0000000..c65ff1c --- /dev/null +++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/FragmentedMp4Builder.java.svn-base @@ -0,0 +1,742 @@ +/* + * Copyright 2012 Sebastian Annies, Hamburg + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.googlecode.mp4parser.authoring.builder; + +import com.coremedia.iso.BoxParser; +import com.coremedia.iso.IsoFile; +import com.coremedia.iso.IsoTypeWriter; +import com.coremedia.iso.boxes.*; +import com.coremedia.iso.boxes.fragment.*; +import com.googlecode.mp4parser.authoring.DateHelper; +import com.googlecode.mp4parser.authoring.Movie; +import com.googlecode.mp4parser.authoring.Track; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.GatheringByteChannel; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; +import java.util.*; +import java.util.logging.Logger; + +import static com.googlecode.mp4parser.util.CastUtils.l2i; + +/** + * Creates a fragmented MP4 file. + */ +public class FragmentedMp4Builder implements Mp4Builder { + private static final Logger LOG = Logger.getLogger(FragmentedMp4Builder.class.getName()); + + protected FragmentIntersectionFinder intersectionFinder; + + public FragmentedMp4Builder() { + this.intersectionFinder = new SyncSampleIntersectFinderImpl(); + } + + public List<String> getAllowedHandlers() { + return Arrays.asList("soun", "vide"); + } + + public Box createFtyp(Movie movie) { + List<String> minorBrands = new LinkedList<String>(); + minorBrands.add("isom"); + minorBrands.add("iso2"); + minorBrands.add("avc1"); + return new FileTypeBox("isom", 0, minorBrands); + } + + /** + * Some formats require sorting of the fragments. E.g. Ultraviolet CFF files are required + * to contain the fragments size sort: + * <ul> + * <li>video[1].getBytes().length < audio[1].getBytes().length < subs[1].getBytes().length</li> + * <li> audio[2].getBytes().length < video[2].getBytes().length < subs[2].getBytes().length</li> + * </ul> + * + * make this fragment: + * + * <ol> + * <li>video[1]</li> + * <li>audio[1]</li> + * <li>subs[1]</li> + * <li>audio[2]</li> + * <li>video[2]</li> + * <li>subs[2]</li> + * </ol> + * + * @param tracks the list of tracks to returned sorted + * @param cycle current fragment (sorting may vary between the fragments) + * @param intersectionMap a map from tracks to their fragments' first samples. + * @return the list of tracks in order of appearance in the fragment + */ + protected List<Track> sortTracksInSequence(List<Track> tracks, final int cycle, final Map<Track, long[]> intersectionMap) { + tracks = new LinkedList<Track>(tracks); + Collections.sort(tracks, new Comparator<Track>() { + public int compare(Track o1, Track o2) { + long[] startSamples1 = intersectionMap.get(o1); + long startSample1 = startSamples1[cycle]; + // one based sample numbers - the first sample is 1 + long endSample1 = cycle + 1 < startSamples1.length ? startSamples1[cycle + 1] : o1.getSamples().size() + 1; + long[] startSamples2 = intersectionMap.get(o2); + long startSample2 = startSamples2[cycle]; + // one based sample numbers - the first sample is 1 + long endSample2 = cycle + 1 < startSamples2.length ? startSamples2[cycle + 1] : o2.getSamples().size() + 1; + List<ByteBuffer> samples1 = o1.getSamples().subList(l2i(startSample1) - 1, l2i(endSample1) - 1); + List<ByteBuffer> samples2 = o2.getSamples().subList(l2i(startSample2) - 1, l2i(endSample2) - 1); + int size1 = 0; + for (ByteBuffer byteBuffer : samples1) { + size1 += byteBuffer.limit(); + } + int size2 = 0; + for (ByteBuffer byteBuffer : samples2) { + size2 += byteBuffer.limit(); + } + return size1 - size2; + } + }); + return tracks; + } + + protected List<Box> createMoofMdat(final Movie movie) { + List<Box> boxes = new LinkedList<Box>(); + HashMap<Track, long[]> intersectionMap = new HashMap<Track, long[]>(); + int maxNumberOfFragments = 0; + for (Track track : movie.getTracks()) { + long[] intersects = intersectionFinder.sampleNumbers(track, movie); + intersectionMap.put(track, intersects); + maxNumberOfFragments = Math.max(maxNumberOfFragments, intersects.length); + } + + + int sequence = 1; + // this loop has two indices: + + for (int cycle = 0; cycle < maxNumberOfFragments; cycle++) { + + final List<Track> sortedTracks = sortTracksInSequence(movie.getTracks(), cycle, intersectionMap); + + for (Track track : sortedTracks) { + if (getAllowedHandlers().isEmpty() || getAllowedHandlers().contains(track.getHandler())) { + long[] startSamples = intersectionMap.get(track); + //some tracks may have less fragments -> skip them + if (cycle < startSamples.length) { + + long startSample = startSamples[cycle]; + // one based sample numbers - the first sample is 1 + long endSample = cycle + 1 < startSamples.length ? startSamples[cycle + 1] : track.getSamples().size() + 1; + + // if startSample == endSample the cycle is empty! + if (startSample != endSample) { + boxes.add(createMoof(startSample, endSample, track, sequence)); + boxes.add(createMdat(startSample, endSample, track, sequence++)); + } + } + } + } + } + return boxes; + } + + /** + * {@inheritDoc} + */ + public IsoFile build(Movie movie) { + LOG.fine("Creating movie " + movie); + IsoFile isoFile = new IsoFile(); + + + isoFile.addBox(createFtyp(movie)); + isoFile.addBox(createMoov(movie)); + + for (Box box : createMoofMdat(movie)) { + isoFile.addBox(box); + } + isoFile.addBox(createMfra(movie, isoFile)); + + return isoFile; + } + + protected Box createMdat(final long startSample, final long endSample, final Track track, final int i) { + + class Mdat implements Box { + ContainerBox parent; + + public ContainerBox getParent() { + return parent; + } + + public void setParent(ContainerBox parent) { + this.parent = parent; + } + + public long getSize() { + long size = 8; // I don't expect 2gig fragments + for (ByteBuffer sample : getSamples(startSample, endSample, track, i)) { + size += sample.limit(); + } + return size; + } + + public String getType() { + return "mdat"; + } + + public void getBox(WritableByteChannel writableByteChannel) throws IOException { + List<ByteBuffer> bbs = getSamples(startSample, endSample, track, i); + final List<ByteBuffer> samples = ByteBufferHelper.mergeAdjacentBuffers(bbs); + ByteBuffer header = ByteBuffer.allocate(8); + IsoTypeWriter.writeUInt32(header, l2i(getSize())); + header.put(IsoFile.fourCCtoBytes(getType())); + header.rewind(); + writableByteChannel.write(header); + if (writableByteChannel instanceof GatheringByteChannel) { + + int STEPSIZE = 1024; + // This is required to prevent android from crashing + // it seems that {@link GatheringByteChannel#write(java.nio.ByteBuffer[])} + // just handles up to 1024 buffers + for (int i = 0; i < Math.ceil((double) samples.size() / STEPSIZE); i++) { + List<ByteBuffer> sublist = samples.subList( + i * STEPSIZE, // start + (i + 1) * STEPSIZE < samples.size() ? (i + 1) * STEPSIZE : samples.size()); // end + ByteBuffer sampleArray[] = sublist.toArray(new ByteBuffer[sublist.size()]); + do { + ((GatheringByteChannel) writableByteChannel).write(sampleArray); + } while (sampleArray[sampleArray.length - 1].remaining() > 0); + } + //System.err.println(bytesWritten); + } else { + for (ByteBuffer sample : samples) { + sample.rewind(); + writableByteChannel.write(sample); + } + } + + } + + public void parse(ReadableByteChannel readableByteChannel, ByteBuffer header, long contentSize, BoxParser boxParser) throws IOException { + + } + } + + return new Mdat(); + } + + protected Box createTfhd(long startSample, long endSample, Track track, int sequenceNumber) { + TrackFragmentHeaderBox tfhd = new TrackFragmentHeaderBox(); + SampleFlags sf = new SampleFlags(); + + tfhd.setDefaultSampleFlags(sf); + tfhd.setBaseDataOffset(-1); + tfhd.setTrackId(track.getTrackMetaData().getTrackId()); + return tfhd; + } + + protected Box createMfhd(long startSample, long endSample, Track track, int sequenceNumber) { + MovieFragmentHeaderBox mfhd = new MovieFragmentHeaderBox(); + mfhd.setSequenceNumber(sequenceNumber); + return mfhd; + } + + protected Box createTraf(long startSample, long endSample, Track track, int sequenceNumber) { + TrackFragmentBox traf = new TrackFragmentBox(); + traf.addBox(createTfhd(startSample, endSample, track, sequenceNumber)); + for (Box trun : createTruns(startSample, endSample, track, sequenceNumber)) { + traf.addBox(trun); + } + + return traf; + } + + + /** + * Gets the all samples starting with <code>startSample</code> (one based -> one is the first) and + * ending with <code>endSample</code> (exclusive). + * + * @param startSample low endpoint (inclusive) of the sample sequence + * @param endSample high endpoint (exclusive) of the sample sequence + * @param track source of the samples + * @param sequenceNumber the fragment index of the requested list of samples + * @return a <code>List<ByteBuffer></code> of raw samples + */ + protected List<ByteBuffer> getSamples(long startSample, long endSample, Track track, int sequenceNumber) { + // since startSample and endSample are one-based substract 1 before addressing list elements + return track.getSamples().subList(l2i(startSample) - 1, l2i(endSample) - 1); + } + + /** + * Gets the sizes of a sequence of samples- + * + * @param startSample low endpoint (inclusive) of the sample sequence + * @param endSample high endpoint (exclusive) of the sample sequence + * @param track source of the samples + * @param sequenceNumber the fragment index of the requested list of samples + * @return + */ + protected long[] getSampleSizes(long startSample, long endSample, Track track, int sequenceNumber) { + List<ByteBuffer> samples = getSamples(startSample, endSample, track, sequenceNumber); + + long[] sampleSizes = new long[samples.size()]; + for (int i = 0; i < sampleSizes.length; i++) { + sampleSizes[i] = samples.get(i).limit(); + } + return sampleSizes; + } + + /** + * Creates one or more track run boxes for a given sequence. + * + * @param startSample low endpoint (inclusive) of the sample sequence + * @param endSample high endpoint (exclusive) of the sample sequence + * @param track source of the samples + * @param sequenceNumber the fragment index of the requested list of samples + * @return the list of TrackRun boxes. + */ + protected List<? extends Box> createTruns(long startSample, long endSample, Track track, int sequenceNumber) { + TrackRunBox trun = new TrackRunBox(); + long[] sampleSizes = getSampleSizes(startSample, endSample, track, sequenceNumber); + + trun.setSampleDurationPresent(true); + trun.setSampleSizePresent(true); + List<TrackRunBox.Entry> entries = new ArrayList<TrackRunBox.Entry>(l2i(endSample - startSample)); + + + Queue<TimeToSampleBox.Entry> timeQueue = new LinkedList<TimeToSampleBox.Entry>(track.getDecodingTimeEntries()); + long left = startSample - 1; + long curEntryLeft = timeQueue.peek().getCount(); + while (left > curEntryLeft) { + left -= curEntryLeft; + timeQueue.remove(); + curEntryLeft = timeQueue.peek().getCount(); + } + curEntryLeft -= left; + + + Queue<CompositionTimeToSample.Entry> compositionTimeQueue = + track.getCompositionTimeEntries() != null && track.getCompositionTimeEntries().size() > 0 ? + new LinkedList<CompositionTimeToSample.Entry>(track.getCompositionTimeEntries()) : null; + long compositionTimeEntriesLeft = compositionTimeQueue != null ? compositionTimeQueue.peek().getCount() : -1; + + + trun.setSampleCompositionTimeOffsetPresent(compositionTimeEntriesLeft > 0); + + // fast forward composition stuff + for (long i = 1; i < startSample; i++) { + if (compositionTimeQueue != null) { + //trun.setSampleCompositionTimeOffsetPresent(true); + if (--compositionTimeEntriesLeft == 0 && compositionTimeQueue.size() > 1) { + compositionTimeQueue.remove(); + compositionTimeEntriesLeft = compositionTimeQueue.element().getCount(); + } + } + } + + boolean sampleFlagsRequired = (track.getSampleDependencies() != null && !track.getSampleDependencies().isEmpty() || + track.getSyncSamples() != null && track.getSyncSamples().length != 0); + + trun.setSampleFlagsPresent(sampleFlagsRequired); + + for (int i = 0; i < sampleSizes.length; i++) { + TrackRunBox.Entry entry = new TrackRunBox.Entry(); + entry.setSampleSize(sampleSizes[i]); + if (sampleFlagsRequired) { + //if (false) { + SampleFlags sflags = new SampleFlags(); + + if (track.getSampleDependencies() != null && !track.getSampleDependencies().isEmpty()) { + SampleDependencyTypeBox.Entry e = track.getSampleDependencies().get(i); + sflags.setSampleDependsOn(e.getSampleDependsOn()); + sflags.setSampleIsDependedOn(e.getSampleIsDependentOn()); + sflags.setSampleHasRedundancy(e.getSampleHasRedundancy()); + } + if (track.getSyncSamples() != null && track.getSyncSamples().length > 0) { + // we have to mark non-sync samples! + if (Arrays.binarySearch(track.getSyncSamples(), startSample + i) >= 0) { + sflags.setSampleIsDifferenceSample(false); + sflags.setSampleDependsOn(2); + } else { + sflags.setSampleIsDifferenceSample(true); + sflags.setSampleDependsOn(1); + } + } + // i don't have sample degradation + entry.setSampleFlags(sflags); + + } + + entry.setSampleDuration(timeQueue.peek().getDelta()); + if (--curEntryLeft == 0 && timeQueue.size() > 1) { + timeQueue.remove(); + curEntryLeft = timeQueue.peek().getCount(); + } + + if (compositionTimeQueue != null) { + entry.setSampleCompositionTimeOffset(compositionTimeQueue.peek().getOffset()); + if (--compositionTimeEntriesLeft == 0 && compositionTimeQueue.size() > 1) { + compositionTimeQueue.remove(); + compositionTimeEntriesLeft = compositionTimeQueue.element().getCount(); + } + } + entries.add(entry); + } + + trun.setEntries(entries); + + return Collections.singletonList(trun); + } + + /** + * Creates a 'moof' box for a given sequence of samples. + * + * @param startSample low endpoint (inclusive) of the sample sequence + * @param endSample high endpoint (exclusive) of the sample sequence + * @param track source of the samples + * @param sequenceNumber the fragment index of the requested list of samples + * @return the list of TrackRun boxes. + */ + protected Box createMoof(long startSample, long endSample, Track track, int sequenceNumber) { + MovieFragmentBox moof = new MovieFragmentBox(); + moof.addBox(createMfhd(startSample, endSample, track, sequenceNumber)); + moof.addBox(createTraf(startSample, endSample, track, sequenceNumber)); + + TrackRunBox firstTrun = moof.getTrackRunBoxes().get(0); + firstTrun.setDataOffset(1); // dummy to make size correct + firstTrun.setDataOffset((int) (8 + moof.getSize())); // mdat header + moof size + + return moof; + } + + /** + * Creates a single 'mvhd' movie header box for a given movie. + * + * @param movie the concerned movie + * @return an 'mvhd' box + */ + protected Box createMvhd(Movie movie) { + MovieHeaderBox mvhd = new MovieHeaderBox(); + mvhd.setVersion(1); + mvhd.setCreationTime(DateHelper.convert(new Date())); + mvhd.setModificationTime(DateHelper.convert(new Date())); + long movieTimeScale = movie.getTimescale(); + long duration = 0; + + for (Track track : movie.getTracks()) { + long tracksDuration = getDuration(track) * movieTimeScale / track.getTrackMetaData().getTimescale(); + if (tracksDuration > duration) { + duration = tracksDuration; + } + + + } + + mvhd.setDuration(duration); + mvhd.setTimescale(movieTimeScale); + // find the next available trackId + long nextTrackId = 0; + for (Track track : movie.getTracks()) { + nextTrackId = nextTrackId < track.getTrackMetaData().getTrackId() ? track.getTrackMetaData().getTrackId() : nextTrackId; + } + mvhd.setNextTrackId(++nextTrackId); + return mvhd; + } + + /** + * Creates a fully populated 'moov' box with all child boxes. Child boxes are: + * <ul> + * <li>{@link #createMvhd(com.googlecode.mp4parser.authoring.Movie) mvhd}</li> + * <li>{@link #createMvex(com.googlecode.mp4parser.authoring.Movie) mvex}</li> + * <li>a {@link #createTrak(com.googlecode.mp4parser.authoring.Track, com.googlecode.mp4parser.authoring.Movie) trak} for every track</li> + * </ul> + * + * @param movie the concerned movie + * @return fully populated 'moov' + */ + protected Box createMoov(Movie movie) { + MovieBox movieBox = new MovieBox(); + + movieBox.addBox(createMvhd(movie)); + movieBox.addBox(createMvex(movie)); + + for (Track track : movie.getTracks()) { + movieBox.addBox(createTrak(track, movie)); + } + // metadata here + return movieBox; + + } + + /** + * Creates a 'tfra' - track fragment random access box for the given track with the isoFile. + * The tfra contains a map of random access points with time as key and offset within the isofile + * as value. + * + * @param track the concerned track + * @param isoFile the track is contained in + * @return a track fragment random access box. + */ + protected Box createTfra(Track track, IsoFile isoFile) { + TrackFragmentRandomAccessBox tfra = new TrackFragmentRandomAccessBox(); + tfra.setVersion(1); // use long offsets and times + List<TrackFragmentRandomAccessBox.Entry> offset2timeEntries = new LinkedList<TrackFragmentRandomAccessBox.Entry>(); + List<Box> boxes = isoFile.getBoxes(); + long offset = 0; + long duration = 0; + for (Box box : boxes) { + if (box instanceof MovieFragmentBox) { + List<TrackFragmentBox> trafs = ((MovieFragmentBox) box).getBoxes(TrackFragmentBox.class); + for (int i = 0; i < trafs.size(); i++) { + TrackFragmentBox traf = trafs.get(i); + if (traf.getTrackFragmentHeaderBox().getTrackId() == track.getTrackMetaData().getTrackId()) { + // here we are at the offset required for the current entry. + List<TrackRunBox> truns = traf.getBoxes(TrackRunBox.class); + for (int j = 0; j < truns.size(); j++) { + List<TrackFragmentRandomAccessBox.Entry> offset2timeEntriesThisTrun = new LinkedList<TrackFragmentRandomAccessBox.Entry>(); + TrackRunBox trun = truns.get(j); + for (int k = 0; k < trun.getEntries().size(); k++) { + TrackRunBox.Entry trunEntry = trun.getEntries().get(k); + SampleFlags sf = null; + if (k == 0 && trun.isFirstSampleFlagsPresent()) { + sf = trun.getFirstSampleFlags(); + } else if (trun.isSampleFlagsPresent()) { + sf = trunEntry.getSampleFlags(); + } else { + List<MovieExtendsBox> mvexs = isoFile.getMovieBox().getBoxes(MovieExtendsBox.class); + for (MovieExtendsBox mvex : mvexs) { + List<TrackExtendsBox> trexs = mvex.getBoxes(TrackExtendsBox.class); + for (TrackExtendsBox trex : trexs) { + if (trex.getTrackId() == track.getTrackMetaData().getTrackId()) { + sf = trex.getDefaultSampleFlags(); + } + } + } + + } + if (sf == null) { + throw new RuntimeException("Could not find any SampleFlags to indicate random access or not"); + } + if (sf.getSampleDependsOn() == 2) { + offset2timeEntriesThisTrun.add(new TrackFragmentRandomAccessBox.Entry( + duration, + offset, + i + 1, j + 1, k + 1)); + } + duration += trunEntry.getSampleDuration(); + } + if (offset2timeEntriesThisTrun.size() == trun.getEntries().size() && trun.getEntries().size() > 0) { + // Oooops every sample seems to be random access sample + // is this an audio track? I don't care. + // I just use the first for trun sample for tfra random access + offset2timeEntries.add(offset2timeEntriesThisTrun.get(0)); + } else { + offset2timeEntries.addAll(offset2timeEntriesThisTrun); + } + } + } + } + } + + + offset += box.getSize(); + } + tfra.setEntries(offset2timeEntries); + tfra.setTrackId(track.getTrackMetaData().getTrackId()); + return tfra; + } + + /** + * Creates a 'mfra' - movie fragment random access box for the given movie in the given + * isofile. Uses {@link #createTfra(com.googlecode.mp4parser.authoring.Track, com.coremedia.iso.IsoFile)} + * to generate the child boxes. + * + * @param movie concerned movie + * @param isoFile concerned isofile + * @return a complete 'mfra' box + */ + protected Box createMfra(Movie movie, IsoFile isoFile) { + MovieFragmentRandomAccessBox mfra = new MovieFragmentRandomAccessBox(); + for (Track track : movie.getTracks()) { + mfra.addBox(createTfra(track, isoFile)); + } + + MovieFragmentRandomAccessOffsetBox mfro = new MovieFragmentRandomAccessOffsetBox(); + mfra.addBox(mfro); + mfro.setMfraSize(mfra.getSize()); + return mfra; + } + + protected Box createTrex(Movie movie, Track track) { + TrackExtendsBox trex = new TrackExtendsBox(); + trex.setTrackId(track.getTrackMetaData().getTrackId()); + trex.setDefaultSampleDescriptionIndex(1); + trex.setDefaultSampleDuration(0); + trex.setDefaultSampleSize(0); + SampleFlags sf = new SampleFlags(); + if ("soun".equals(track.getHandler())) { + // as far as I know there is no audio encoding + // where the sample are not self contained. + sf.setSampleDependsOn(2); + sf.setSampleIsDependedOn(2); + } + trex.setDefaultSampleFlags(sf); + return trex; + } + + /** + * Creates a 'mvex' - movie extends box and populates it with 'trex' boxes + * by calling {@link #createTrex(com.googlecode.mp4parser.authoring.Movie, com.googlecode.mp4parser.authoring.Track)} + * for each track to generate them + * + * @param movie the source movie + * @return a complete 'mvex' + */ + protected Box createMvex(Movie movie) { + MovieExtendsBox mvex = new MovieExtendsBox(); + final MovieExtendsHeaderBox mved = new MovieExtendsHeaderBox(); + for (Track track : movie.getTracks()) { + final long trackDuration = getTrackDuration(movie, track); + if (mved.getFragmentDuration() < trackDuration) { + mved.setFragmentDuration(trackDuration); + } + } + mvex.addBox(mved); + + for (Track track : movie.getTracks()) { + mvex.addBox(createTrex(movie, track)); + } + return mvex; + } + + protected Box createTkhd(Movie movie, Track track) { + TrackHeaderBox tkhd = new TrackHeaderBox(); + tkhd.setVersion(1); + int flags = 0; + if (track.isEnabled()) { + flags += 1; + } + + if (track.isInMovie()) { + flags += 2; + } + + if (track.isInPreview()) { + flags += 4; + } + + if (track.isInPoster()) { + flags += 8; + } + tkhd.setFlags(flags); + + tkhd.setAlternateGroup(track.getTrackMetaData().getGroup()); + tkhd.setCreationTime(DateHelper.convert(track.getTrackMetaData().getCreationTime())); + // We need to take edit list box into account in trackheader duration + // but as long as I don't support edit list boxes it is sufficient to + // just translate media duration to movie timescale + tkhd.setDuration(getTrackDuration(movie, track)); + tkhd.setHeight(track.getTrackMetaData().getHeight()); + tkhd.setWidth(track.getTrackMetaData().getWidth()); + tkhd.setLayer(track.getTrackMetaData().getLayer()); + tkhd.setModificationTime(DateHelper.convert(new Date())); + tkhd.setTrackId(track.getTrackMetaData().getTrackId()); + tkhd.setVolume(track.getTrackMetaData().getVolume()); + return tkhd; + } + + private long getTrackDuration(Movie movie, Track track) { + return getDuration(track) * movie.getTimescale() / track.getTrackMetaData().getTimescale(); + } + + protected Box createMdhd(Movie movie, Track track) { + MediaHeaderBox mdhd = new MediaHeaderBox(); + mdhd.setCreationTime(DateHelper.convert(track.getTrackMetaData().getCreationTime())); + mdhd.setDuration(getDuration(track)); + mdhd.setTimescale(track.getTrackMetaData().getTimescale()); + mdhd.setLanguage(track.getTrackMetaData().getLanguage()); + return mdhd; + } + + protected Box createStbl(Movie movie, Track track) { + SampleTableBox stbl = new SampleTableBox(); + + stbl.addBox(track.getSampleDescriptionBox()); + stbl.addBox(new TimeToSampleBox()); + //stbl.addBox(new SampleToChunkBox()); + stbl.addBox(new StaticChunkOffsetBox()); + return stbl; + } + + protected Box createMinf(Track track, Movie movie) { + MediaInformationBox minf = new MediaInformationBox(); + minf.addBox(track.getMediaHeaderBox()); + minf.addBox(createDinf(movie, track)); + minf.addBox(createStbl(movie, track)); + return minf; + } + + protected Box createMdiaHdlr(Track track, Movie movie) { + HandlerBox hdlr = new HandlerBox(); + hdlr.setHandlerType(track.getHandler()); + return hdlr; + } + + protected Box createMdia(Track track, Movie movie) { + MediaBox mdia = new MediaBox(); + mdia.addBox(createMdhd(movie, track)); + + + mdia.addBox(createMdiaHdlr(track, movie)); + + + mdia.addBox(createMinf(track, movie)); + return mdia; + } + + protected Box createTrak(Track track, Movie movie) { + LOG.fine("Creating Track " + track); + TrackBox trackBox = new TrackBox(); + trackBox.addBox(createTkhd(movie, track)); + trackBox.addBox(createMdia(track, movie)); + return trackBox; + } + + protected DataInformationBox createDinf(Movie movie, Track track) { + DataInformationBox dinf = new DataInformationBox(); + DataReferenceBox dref = new DataReferenceBox(); + dinf.addBox(dref); + DataEntryUrlBox url = new DataEntryUrlBox(); + url.setFlags(1); + dref.addBox(url); + return dinf; + } + + public FragmentIntersectionFinder getFragmentIntersectionFinder() { + return intersectionFinder; + } + + public void setIntersectionFinder(FragmentIntersectionFinder intersectionFinder) { + this.intersectionFinder = intersectionFinder; + } + + protected long getDuration(Track track) { + long duration = 0; + for (TimeToSampleBox.Entry entry : track.getDecodingTimeEntries()) { + duration += entry.getCount() * entry.getDelta(); + } + return duration; + } + + +} |