package org.jcodec.containers.mkv;
import static org.jcodec.containers.mkv.Type.CueClusterPosition;
import static org.jcodec.containers.mkv.Type.CuePoint;
import static org.jcodec.containers.mkv.Type.CueTime;
import static org.jcodec.containers.mkv.Type.CueTrack;
import static org.jcodec.containers.mkv.Type.CueTrackPositions;
import static org.jcodec.containers.mkv.Type.Cues;
import static org.jcodec.containers.mkv.Type.createElementByType;
import static org.jcodec.containers.mkv.ebml.Element.getEbmlSize;
import static org.jcodec.containers.mkv.ebml.UnsignedIntegerElement.getMinByteSizeUnsigned;
import java.util.ArrayList;
import java.util.List;
import org.jcodec.containers.mkv.ebml.MasterElement;
import org.jcodec.containers.mkv.ebml.UnsignedIntegerElement;
import org.jcodec.containers.mkv.elements.Cluster;
public class CuesIndexer {
List<CuesIndexer.CuePointMock> a = new ArrayList<CuesIndexer.CuePointMock>();
private final long offsetBase;
private long currentDataOffset = 0;
private long videoTrackNr;
public CuesIndexer(long offset, long videoTrack){
this.offsetBase = offset;
this.videoTrackNr = videoTrack;
this.currentDataOffset += offsetBase;
}
public void add(CuesIndexer.CuePointMock z) {
z.dataOffset = currentDataOffset;
z.cueClusterPositionSize = getMinByteSizeUnsigned(z.dataOffset);
currentDataOffset += z.size;
// System.out.println("Added id:"+Reader.printAsHex(z.id)+" offset:"+z.dataOffset+" size:"+z.size+" cueClusterPosition size:"+z.cueClusterPositionSize);
a.add(z);
}
public MasterElement createCues(){
int estimatedSize = computeCuesSize();
MasterElement cues = Type.createElementByType(Cues);
for (CuesIndexer.CuePointMock cpm : a){
MasterElement cuePoint = createElementByType(CuePoint);
UnsignedIntegerElement cueTime = createElementByType(CueTime);
cueTime.set(cpm.timecode);
cuePoint.addChildElement(cueTime);
MasterElement cueTrackPositions = createElementByType(CueTrackPositions);
UnsignedIntegerElement cueTrack = createElementByType(CueTrack);
cueTrack.set(videoTrackNr);
cueTrackPositions.addChildElement(cueTrack);
UnsignedIntegerElement cueClusterPosition = createElementByType(CueClusterPosition);
cueClusterPosition.set(cpm.dataOffset+estimatedSize);
if (cueClusterPosition.getData().length != cpm.cueClusterPositionSize)
System.err.println("estimated size of CueClusterPosition differs from the one actually used. ElementId: "+Reader.printAsHex(cpm.id)+" "+cueClusterPosition.getData().length+" vs "+cpm.cueClusterPositionSize);
cueTrackPositions.addChildElement(cueClusterPosition);
cuePoint.addChildElement(cueTrackPositions);
cues.addChildElement(cuePoint);
}
return cues;
}
public int computeCuesSize() {
int cuesSize = estimateSize();
boolean reindex = false;
do {
reindex = false;
for (CuesIndexer.CuePointMock z : a) {
int minByteSize = getMinByteSizeUnsigned(z.dataOffset + cuesSize);
if (minByteSize > z.cueClusterPositionSize) {
System.err.println("Size "+cuesSize+" seems too small for element "+Reader.printAsHex(z.id)+" increasing size by one.");
z.cueClusterPositionSize +=1;
cuesSize += 1;
reindex = true;
break;
} else if (minByteSize < z.cueClusterPositionSize){
throw new RuntimeException("Downsizing the index is not well thought through");
/*
System.out.println("Size "+cuesSize+" seems too small for element "+Reader.printAsHex(z.id)+" increasing size by one.");
z.cueClusterPositionSize -=1;
cuesSize -= 1;
reindex = true;
break;
*/
}
}
} while (reindex);
return cuesSize;
}
int estimateSize() {
int s = 0;
for (CuesIndexer.CuePointMock cpm : a)
s += estimateCuePointSize(getMinByteSizeUnsigned(cpm.timecode), getMinByteSizeUnsigned(videoTrackNr), getMinByteSizeUnsigned(cpm.dataOffset));
s += Cues.id.length + getEbmlSize(s);
return s;
}
public static int estimateCuePointSize(int timecodeSizeInBytes, int trackNrSizeInBytes, int clusterPositionSizeInBytes) {
int cueTimeSize = CueTime.id.length + getEbmlSize(timecodeSizeInBytes) + timecodeSizeInBytes;
int cueTrackPositionSize = Type.CueTrack.id.length + getEbmlSize(trackNrSizeInBytes) + trackNrSizeInBytes +
CueClusterPosition.id.length + getEbmlSize(clusterPositionSizeInBytes) + clusterPositionSizeInBytes;
cueTrackPositionSize += Type.CueTrackPositions.id.length + getEbmlSize(cueTrackPositionSize);
int cuePointSize = CuePoint.id.length + getEbmlSize(cueTimeSize + cueTrackPositionSize) + cueTimeSize + cueTrackPositionSize;
return cuePointSize;
}
public static class CuePointMock {
public int cueClusterPositionSize;
public long dataOffset;
private long timecode;
private long size;
private byte[] id;
public static CuePointMock make(Cluster c){
UnsignedIntegerElement tc = (UnsignedIntegerElement) Type.findFirst(c, Type.Cluster, Type.Timecode);
return make(c.id, tc.get(), c.getSize());
}
public static CuePointMock make(byte[] id, long timecode, long size) {
CuesIndexer.CuePointMock mock = new CuePointMock();
mock.id = id;
mock.timecode = timecode;
mock.size = size;
return mock;
}
}
}