package org.jcodec.containers.mkv;
import static org.jcodec.containers.mkv.MKVType.Cluster;
import static org.jcodec.containers.mkv.MKVType.CueClusterPosition;
import static org.jcodec.containers.mkv.MKVType.CuePoint;
import static org.jcodec.containers.mkv.MKVType.CueTime;
import static org.jcodec.containers.mkv.MKVType.CueTrack;
import static org.jcodec.containers.mkv.MKVType.CueTrackPositions;
import static org.jcodec.containers.mkv.MKVType.Cues;
import static org.jcodec.containers.mkv.MKVType.Timecode;
import static org.jcodec.containers.mkv.MKVType.createByType;
import static org.jcodec.containers.mkv.MKVType.findFirst;
import static org.jcodec.containers.mkv.boxes.EbmlUint.calculatePayloadSize;
import org.jcodec.containers.mkv.boxes.EbmlMaster;
import org.jcodec.containers.mkv.boxes.EbmlUint;
import org.jcodec.containers.mkv.util.EbmlUtil;
import java.lang.System;
import java.util.ArrayList;
import java.util.List;
/**
* This class is part of JCodec ( www.jcodec.org ) This software is distributed under FreeBSD License
*
* EBML IO implementation
*
* @author The JCodec project
*
*/
public class CuesFactory {
List<CuePointMock> a;
private final long offsetBase;
private long currentDataOffset = 0;
private long videoTrackNr;
public CuesFactory(long offset, long videoTrack){
this.a = new ArrayList<CuePointMock>();
this.offsetBase = offset;
this.videoTrackNr = videoTrack;
this.currentDataOffset += offsetBase;
}
public void addFixedSize(CuePointMock z) {
z.elementOffset = currentDataOffset;
z.cueClusterPositionSize = 8;
currentDataOffset += z.size;
a.add(z);
}
public void add(CuePointMock z) {
z.elementOffset = currentDataOffset;
z.cueClusterPositionSize = calculatePayloadSize(z.elementOffset);
currentDataOffset += z.size;
a.add(z);
}
public EbmlMaster createCues(){
int estimatedSize = computeCuesSize();
EbmlMaster cues = createByType(Cues);
for (CuePointMock cpm : a){
EbmlMaster cuePoint = createByType(CuePoint);
EbmlUint cueTime = createByType(CueTime);
cueTime.setUint(cpm.timecode);
cuePoint.add(cueTime);
EbmlMaster cueTrackPositions = createByType(CueTrackPositions);
EbmlUint cueTrack = createByType(CueTrack);
cueTrack.setUint(videoTrackNr);
cueTrackPositions.add(cueTrack);
EbmlUint cueClusterPosition = createByType(CueClusterPosition);
cueClusterPosition.setUint(cpm.elementOffset+estimatedSize);
if (cueClusterPosition.data.limit() != cpm.cueClusterPositionSize)
System.err.println("estimated size of CueClusterPosition differs from the one actually used. ElementId: "+EbmlUtil.toHexString(cpm.id)+" "+cueClusterPosition.getData().limit()+" vs "+cpm.cueClusterPositionSize);
cueTrackPositions.add(cueClusterPosition);
cuePoint.add(cueTrackPositions);
cues.add(cuePoint);
}
return cues;
}
public int computeCuesSize() {
int cuesSize = estimateSize();
boolean reindex = false;
do {
reindex = false;
for (CuePointMock z : a) {
int minByteSize = calculatePayloadSize(z.elementOffset + cuesSize);
if (minByteSize > z.cueClusterPositionSize) {
System.out.println(minByteSize + ">" + z.cueClusterPositionSize);
System.err.println("Size "+cuesSize+" seems too small for element "+EbmlUtil.toHexString(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;
}
public int estimateFixedSize(int numberOfClusters) {
int s = 34*numberOfClusters;
// for (CuePointMock cpm : a)
// s += estimateCuePointSize(8, 8, 8);
s += Cues.id.length + EbmlUtil.ebmlLength(s);
return s;
}
public int estimateSize() {
int s = 0;
for (CuePointMock cpm : a)
s += estimateCuePointSize(calculatePayloadSize(cpm.timecode), calculatePayloadSize(videoTrackNr), calculatePayloadSize(cpm.elementOffset));
s += Cues.id.length + EbmlUtil.ebmlLength(s);
return s;
}
public static int estimateCuePointSize(int timecodeSizeInBytes, int trackNrSizeInBytes, int clusterPositionSizeInBytes) {
int cueTimeSize = CueTime.id.length + EbmlUtil.ebmlLength(timecodeSizeInBytes) + timecodeSizeInBytes;
int cueTrackPositionSize = CueTrack.id.length + EbmlUtil.ebmlLength(trackNrSizeInBytes) + trackNrSizeInBytes +
CueClusterPosition.id.length + EbmlUtil.ebmlLength(clusterPositionSizeInBytes) + clusterPositionSizeInBytes;
cueTrackPositionSize += CueTrackPositions.id.length + EbmlUtil.ebmlLength(cueTrackPositionSize);
int cuePointSize = CuePoint.id.length + EbmlUtil.ebmlLength(cueTimeSize + cueTrackPositionSize) + cueTimeSize + cueTrackPositionSize;
return cuePointSize;
}
public static class CuePointMock {
public int cueClusterPositionSize;
public long elementOffset;
private long timecode;
private long size;
private byte[] id;
public static CuePointMock make(EbmlMaster c){
MKVType[] path = { Cluster, Timecode };
EbmlUint tc = (EbmlUint) findFirst(c, path);
return doMake(c.id, tc.getUint(), c.size());
}
public static CuePointMock doMake(byte[] id, long timecode, long size) {
CuePointMock mock = new CuePointMock();
mock.id = id;
mock.timecode = timecode;
mock.size = size;
return mock;
}
}
}