package com.googlecode.mp4parser.authoring;
import com.coremedia.iso.IsoFile;
import com.coremedia.iso.IsoTypeReader;
import com.coremedia.iso.boxes.*;
import com.coremedia.iso.boxes.fragment.*;
import com.googlecode.mp4parser.authoring.tracks.CencEncyprtedTrack;
import com.googlecode.mp4parser.boxes.basemediaformat.TrackEncryptionBox;
import com.googlecode.mp4parser.boxes.cenc.CencSampleAuxiliaryDataFormat;
import com.googlecode.mp4parser.util.Path;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import static com.googlecode.mp4parser.util.CastUtils.l2i;
/**
* Created by user on 04.07.2014.
*/
public class CencMp4TrackImplImpl extends Mp4TrackImpl implements CencEncyprtedTrack {
private List<CencSampleAuxiliaryDataFormat> sampleEncryptionEntries;
/**
* Creates a track from a TrackBox and potentially fragments. Use <b>fragements parameter
* only</b> to supply additional fragments that are not located in the main file.
*
* @param trackBox the <code>TrackBox</code> describing the track.
* @param fragments additional fragments if located in more than a single file
*/
public CencMp4TrackImplImpl(TrackBox trackBox, IsoFile... fragments) throws IOException {
super(trackBox, fragments);
SchemeTypeBox schm = (SchemeTypeBox) Path.getPath(trackBox, "mdia[0]/minf[0]/stbl[0]/stsd[0]/enc.[0]/sinf[0]/schm[0]");
assert schm != null && schm.getSchemeType().equals("cenc") : "Track must be CENC encrypted";
sampleEncryptionEntries = new ArrayList<CencSampleAuxiliaryDataFormat>();
long trackId = trackBox.getTrackHeaderBox().getTrackId();
if (trackBox.getParent().getBoxes(MovieExtendsBox.class).size() > 0) {
for (MovieFragmentBox movieFragmentBox : ((Box) trackBox.getParent()).getParent().getBoxes(MovieFragmentBox.class)) {
List<TrackFragmentBox> trafs = movieFragmentBox.getBoxes(TrackFragmentBox.class);
for (TrackFragmentBox traf : trafs) {
if (traf.getTrackFragmentHeaderBox().getTrackId() == trackId) {
TrackEncryptionBox tenc = (TrackEncryptionBox) Path.getPath(trackBox, "mdia[0]/minf[0]/stbl[0]/stsd[0]/enc.[0]/sinf[0]/schi[0]/tenc[0]");
Container base;
long baseOffset;
if (traf.getTrackFragmentHeaderBox().hasBaseDataOffset()) {
base = ((Box) trackBox.getParent()).getParent();
baseOffset = traf.getTrackFragmentHeaderBox().getBaseDataOffset();
} else {
base = movieFragmentBox;
baseOffset = 0;
}
FindSaioSaizPair saizSaioPair = new FindSaioSaizPair(traf).invoke();
SampleAuxiliaryInformationOffsetsBox saio = saizSaioPair.getSaio();
SampleAuxiliaryInformationSizesBox saiz = saizSaioPair.getSaiz();
// now we have the correct saio/saiz combo!
assert saio != null;
long[] saioOffsets = saio.getOffsets();
assert saioOffsets.length == traf.getBoxes(TrackRunBox.class).size();
assert saiz != null;
List<TrackRunBox> truns = traf.getBoxes(TrackRunBox.class);
int sampleNo = 0;
for (int i = 0; i < saioOffsets.length; i++) {
int numSamples = truns.get(i).getEntries().size();
long offset = saioOffsets[i];
long length = 0;
for (int j = sampleNo; j < sampleNo + numSamples; j++) {
length += saiz.getSize(j);
}
ByteBuffer trunsCencSampleAuxData = base.getByteBuffer(baseOffset + offset, length);
for (int j = sampleNo; j < sampleNo + numSamples; j++) {
int auxInfoSize = saiz.getSize(j);
sampleEncryptionEntries.add(
parseCencAuxDataFormat(tenc.getDefaultIvSize(), trunsCencSampleAuxData, auxInfoSize)
);
}
sampleNo += numSamples;
}
}
}
}
} else {
TrackEncryptionBox tenc = (TrackEncryptionBox) Path.getPath(trackBox, "mdia[0]/minf[0]/stbl[0]/stsd[0]/enc.[0]/sinf[0]/schi[0]/tenc[0]");
ChunkOffsetBox chunkOffsetBox = (ChunkOffsetBox) Path.getPath(trackBox, "mdia[0]/minf[0]/stbl[0]/stco[0]");
if (chunkOffsetBox == null) {
chunkOffsetBox = (ChunkOffsetBox) Path.getPath(trackBox, "mdia[0]/minf[0]/stbl[0]/co64[0]");
}
long[] chunkSizes = trackBox.getSampleTableBox().getSampleToChunkBox().blowup(chunkOffsetBox.getChunkOffsets().length);
FindSaioSaizPair saizSaioPair = new FindSaioSaizPair((Container) Path.getPath(trackBox, "mdia[0]/minf[0]/stbl[0]")).invoke();
SampleAuxiliaryInformationOffsetsBox saio = saizSaioPair.saio;
SampleAuxiliaryInformationSizesBox saiz = saizSaioPair.saiz;
Container topLevel = ((MovieBox) trackBox.getParent()).getParent();
int currentSampleNo = 0;
for (int i = 0; i < chunkSizes.length; i++) {
long offset = saio.getOffsets()[i];
long size = 0;
for (int j = currentSampleNo; j < currentSampleNo + chunkSizes[i]; j++) {
size += saiz.getSize(currentSampleNo + j);
}
ByteBuffer chunksCencSampleAuxData = topLevel.getByteBuffer(offset, size);
for (int j = 0; j < chunkSizes[i]; j++) {
long auxInfoSize = saiz.getSize(currentSampleNo + i);
sampleEncryptionEntries.add(
parseCencAuxDataFormat(tenc.getDefaultIvSize(), chunksCencSampleAuxData, auxInfoSize)
);
}
}
}
}
private CencSampleAuxiliaryDataFormat parseCencAuxDataFormat(int ivSize, ByteBuffer chunksCencSampleAuxData, long auxInfoSize) {
CencSampleAuxiliaryDataFormat cadf = new CencSampleAuxiliaryDataFormat();
cadf.iv = new byte[ivSize];
chunksCencSampleAuxData.get(cadf.iv);
if (auxInfoSize > ivSize) {
int numOfPairs = IsoTypeReader.readUInt16(chunksCencSampleAuxData);
cadf.pairs = new CencSampleAuxiliaryDataFormat.Pair[numOfPairs];
for (int i = 0; i < cadf.pairs.length; i++) {
cadf.pairs[i] = cadf.createPair(
IsoTypeReader.readUInt16(chunksCencSampleAuxData),
IsoTypeReader.readUInt32(chunksCencSampleAuxData));
}
}
return cadf;
}
public UUID getKeyId() {
return null;
}
public boolean hasSubSampleEncryption() {
return false;
}
public List<CencSampleAuxiliaryDataFormat> getSampleEncryptionEntries() {
return sampleEncryptionEntries;
}
@Override
public String toString() {
return "CencMp4TrackImpl{" +
"handler='" + getHandler() + '\'' +
'}';
}
private class FindSaioSaizPair {
private Container container;
private SampleAuxiliaryInformationSizesBox saiz;
private SampleAuxiliaryInformationOffsetsBox saio;
public FindSaioSaizPair(Container container) {
this.container = container;
}
public SampleAuxiliaryInformationSizesBox getSaiz() {
return saiz;
}
public SampleAuxiliaryInformationOffsetsBox getSaio() {
return saio;
}
public FindSaioSaizPair invoke() {
List<SampleAuxiliaryInformationSizesBox> saizs = container.getBoxes(SampleAuxiliaryInformationSizesBox.class);
List<SampleAuxiliaryInformationOffsetsBox> saios = container.getBoxes(SampleAuxiliaryInformationOffsetsBox.class);
assert saizs.size() == saios.size();
saiz = null;
saio = null;
for (int i = 0; i < saizs.size(); i++) {
if (saiz == null && (saizs.get(i).getAuxInfoType() == null) || "cenc".equals(saizs.get(i).getAuxInfoType())) {
saiz = saizs.get(i);
} else if (saiz != null && saiz.getAuxInfoType() == null && "cenc".equals(saizs.get(i).getAuxInfoType())) {
saiz = saizs.get(i);
} else {
throw new RuntimeException("BULL.SHIT.");
}
if (saio == null && (saios.get(i).getAuxInfoType() == null) || "cenc".equals(saios.get(i).getAuxInfoType())) {
saio = saios.get(i);
} else if (saio != null && saio.getAuxInfoType() == null && "cenc".equals(saios.get(i).getAuxInfoType())) {
saio = saios.get(i);
} else {
throw new RuntimeException("BULL.SHIT.");
}
}
return this;
}
}
}