package org.jcodec.containers.mp4.boxes; import java.util.List; import java.util.ListIterator; import org.jcodec.common.model.Rational; import org.jcodec.common.model.Size; import org.jcodec.containers.mp4.MP4TrackType; import org.jcodec.containers.mp4.boxes.TimeToSampleBox.TimeToSampleEntry; /** * This class is part of JCodec ( www.jcodec.org ) This software is distributed * under FreeBSD License * * Creates MP4 file out of a set of samples * * @author The JCodec project * */ public class TrakBox extends NodeBox { public static String fourcc() { return "trak"; } public static TrakBox createTrakBox() { return new TrakBox(new Header(fourcc())); } public TrakBox(Header atom) { super(atom); } public void setDataRef(String url) { MediaInfoBox minf = getMdia().getMinf(); DataInfoBox dinf = minf.getDinf(); if (dinf == null) { dinf = DataInfoBox.createDataInfoBox(); minf.add(dinf); } DataRefBox dref = dinf.getDref(); UrlBox urlBox = UrlBox.createUrlBox(url); if (dref == null) { dref = DataRefBox.createDataRefBox(); dinf.add(dref); dref.add(urlBox); } else { ListIterator<Box> lit = dref.boxes.listIterator(); while (lit.hasNext()) { FullBox box = (FullBox) lit.next(); if ((box.getFlags() & 0x1) != 0) lit.set(urlBox); } } } public MediaBox getMdia() { return NodeBox.findFirst(this, MediaBox.class, "mdia"); } public TrackHeaderBox getTrackHeader() { return NodeBox.findFirst(this, TrackHeaderBox.class, "tkhd"); } public List<Edit> getEdits() { EditListBox elst = NodeBox.findFirstPath(this, EditListBox.class, Box.path("edts.elst")); if (elst == null) return null; return elst.getEdits(); } public void setEdits(List<Edit> edits) { NodeBox edts = NodeBox.findFirst(this, NodeBox.class, "edts"); if (edts == null) { edts = new NodeBox(new Header("edts")); this.add(edts); } edts.removeChildren("elst"); edts.add(EditListBox.createEditListBox(edits)); getTrackHeader().setDuration(getEditedDuration(this)); } public boolean isVideo() { return "vide".equals(getHandlerType()); } public boolean isTimecode() { return "tmcd".equals(getHandlerType()); } public String getHandlerType() { HandlerBox handlerBox = NodeBox.findFirstPath(this, HandlerBox.class, Box.path("mdia.hdlr")); if (handlerBox == null) return null; String type = handlerBox.getComponentSubType(); return type; } public boolean isAudio() { return "soun".equals(getHandlerType()); } /** * Gets 'media timescale' of this track. This is the timescale used to * represent the durations of samples inside mdia/minf/stbl/stts box. * * @return 'media timescale' of the track. */ public int getTimescale() { return NodeBox.findFirstPath(this, MediaHeaderBox.class, Box.path("mdia.mdhd")).getTimescale(); } /** * Sets the 'media timescale' of this track. This is the time timescale used * to represent sample durations. * * @param timescale * A new 'media timescale' of this track. */ public void setTimescale(int timescale) { NodeBox.findFirstPath(this, MediaHeaderBox.class, Box.path("mdia.mdhd")).setTimescale(timescale); } public long rescale(long tv, long ts) { return (tv * getTimescale()) / ts; } public void setDuration(long duration) { getTrackHeader().setDuration(duration); } public long getDuration() { return getTrackHeader().getDuration(); } public long getMediaDuration() { return NodeBox.findFirstPath(this, MediaHeaderBox.class, Box.path("mdia.mdhd")).getDuration(); } public boolean isPureRef() { MediaInfoBox minf = getMdia().getMinf(); DataInfoBox dinf = minf.getDinf(); if (dinf == null) { return false; } DataRefBox dref = dinf.getDref(); if (dref == null) return false; for (Box box : dref.boxes) { if ((((FullBox) box).getFlags() & 0x1) != 0) return false; } return true; } public boolean hasDataRef() { DataInfoBox dinf = getMdia().getMinf().getDinf(); if (dinf == null) { return false; } DataRefBox dref = dinf.getDref(); if (dref == null) return false; boolean result = false; for (Box box : dref.boxes) { result |= (((FullBox) box).getFlags() & 0x1) != 0x1; } return result; } public Rational getPAR() { PixelAspectExt pasp = NodeBox.findFirstPath(this, PixelAspectExt.class, new String[] { "mdia", "minf", "stbl", "stsd", null, "pasp" }); return pasp == null ? new Rational(1, 1) : pasp.getRational(); } public void setPAR(Rational par) { SampleEntry[] sampleEntries = getSampleEntries(); for (int i = 0; i < sampleEntries.length; i++) { SampleEntry sampleEntry = sampleEntries[i]; sampleEntry.removeChildren("pasp"); sampleEntry.add(PixelAspectExt.createPixelAspectExt(par)); } } public SampleEntry[] getSampleEntries() { return NodeBox.findAllPath(this, SampleEntry.class, new String[]{"mdia", "minf", "stbl", "stsd", null}); } public void setClipRect(short x, short y, short width, short height) { NodeBox clip = NodeBox.findFirst(this, NodeBox.class, "clip"); if (clip == null) { clip = new NodeBox(new Header("clip")); add(clip); } clip.replace("crgn", ClipRegionBox.createClipRegionBox(x, y, width, height)); } public long getSampleCount() { return NodeBox.findFirstPath(this, SampleSizesBox.class, Box.path("mdia.minf.stbl.stsz")).getCount(); } public void setAperture(Size sar, Size dar) { removeChildren("tapt"); NodeBox tapt = new NodeBox(new Header("tapt")); tapt.add(ClearApertureBox.createClearApertureBox(dar.getWidth(), dar.getHeight())); tapt.add(ProductionApertureBox.createProductionApertureBox(dar.getWidth(), dar.getHeight())); tapt.add(EncodedPixelBox.createEncodedPixelBox(sar.getWidth(), sar.getHeight())); add(tapt); } public void setDimensions(Size dd) { getTrackHeader().setWidth((float) dd.getWidth()); getTrackHeader().setHeight((float) dd.getHeight()); } public int getFrameCount() { SampleSizesBox stsz = NodeBox.findFirstPath(this, SampleSizesBox.class, Box.path("mdia.minf.stbl.stsz")); return stsz.getDefaultSize() != 0 ? stsz.getCount() : stsz.getSizes().length; } public String getName() { NameBox nb = NodeBox.findFirstPath(this, NameBox.class, Box.path("udta.name")); return nb == null ? null : nb.getName(); } public void fixMediaTimescale(int ts) { MediaHeaderBox mdhd = NodeBox.findFirstPath(this, MediaHeaderBox.class, Box.path("mdia.mdhd")); int oldTs = mdhd.getTimescale(); mdhd.setTimescale(ts); mdhd.setDuration((ts * mdhd.getDuration()) / oldTs); List<Edit> edits = getEdits(); if (edits != null) { for (Edit edit : edits) { edit.setMediaTime((ts * edit.getMediaTime()) / oldTs); } } TimeToSampleBox tts = NodeBox.findFirstPath(this, TimeToSampleBox.class, Box.path("mdia.minf.stbl.stts")); TimeToSampleEntry[] entries = tts.getEntries(); for (int i = 0; i < entries.length; i++) { TimeToSampleEntry tte = entries[i]; tte.setSampleDuration((ts * tte.getSampleDuration()) / oldTs); } } public void setName(String string) { NodeBox udta = NodeBox.findFirst(this, NodeBox.class, "udta"); if (udta == null) { udta = new NodeBox(new Header("udta")); this.add(udta); } udta.removeChildren("name"); udta.add(NameBox.createNameBox(string)); } /** * Retrieves coded size of this video track. * * Note: May be different from video display dimension. * * @return */ public Size getCodedSize() { SampleEntry se = getSampleEntries()[0]; if (!(se instanceof VideoSampleEntry)) throw new IllegalArgumentException("Not a video track"); VideoSampleEntry vse = (VideoSampleEntry) se; return new Size(vse.getWidth(), vse.getHeight()); } protected void getModelFields(List<String> model) { } public TimeToSampleBox getStts() { return NodeBox.findFirstPath(this, TimeToSampleBox.class, Box.path("mdia.minf.stbl.stts")); } public ChunkOffsetsBox getStco() { return NodeBox.findFirstPath(this, ChunkOffsetsBox.class, Box.path("mdia.minf.stbl.stco" )); } public ChunkOffsets64Box getCo64() { return NodeBox.findFirstPath(this, ChunkOffsets64Box.class, Box.path("mdia.minf.stbl.co64" )); } public SampleSizesBox getStsz() { return NodeBox.findFirstPath(this, SampleSizesBox.class, Box.path("mdia.minf.stbl.stsz" )); } public SampleToChunkBox getStsc() { return NodeBox.findFirstPath(this, SampleToChunkBox.class, Box.path("mdia.minf.stbl.stsc" )); } public SampleDescriptionBox getStsd() { return NodeBox.findFirstPath(this, SampleDescriptionBox.class, Box.path("mdia.minf.stbl.stsd" )); } public SyncSamplesBox getStss() { return NodeBox.findFirstPath(this, SyncSamplesBox.class, Box.path("mdia.minf.stbl.stss" )); } public CompositionOffsetsBox getCtts() { return NodeBox.findFirstPath(this, CompositionOffsetsBox.class, Box.path("mdia.minf.stbl.ctts" )); } public static MP4TrackType getTrackType(TrakBox trak) { HandlerBox handler = NodeBox.findFirstPath(trak, HandlerBox.class, Box.path("mdia.hdlr")); return MP4TrackType.fromHandler(handler.getComponentSubType()); } /** * Calculates track duration considering edits * * @param track * @return */ public static long getEditedDuration(TrakBox track) { List<Edit> edits = track.getEdits(); if (edits == null) return track.getDuration(); long duration = 0; for (Edit edit : edits) { duration += edit.getDuration(); } return duration; } }