TikaMp4VideoHandler.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tika.parser.mp4;
import java.io.IOException;
import com.drew.imaging.mp4.Mp4Handler;
import com.drew.metadata.Metadata;
import com.drew.metadata.mp4.Mp4Context;
import com.drew.metadata.mp4.media.Mp4VideoHandler;
import org.apache.tika.io.EndianUtils;
import org.apache.tika.metadata.Video;
/**
* Extends the video track handling with what the base handler does not read
* from the sample description: the average bitrate from the 'btrt' BitRateBox.
* See TIKA-4802.
*/
class TikaMp4VideoHandler extends Mp4VideoHandler {
/**
* Fixed size of a VisualSampleEntry (ISO/IEC 14496-12) after its box
* header, up to where the child boxes start: 8 bytes of SampleEntry (6
* reserved, 2 data reference index) and 70 bytes of visual fields ending
* with the 32 byte compressor name, the depth and a pre-defined field.
*/
private static final int VISUAL_ENTRY_SIZE = 78;
private final org.apache.tika.metadata.Metadata tikaMetadata;
TikaMp4VideoHandler(Metadata metadata, Mp4Context context,
org.apache.tika.metadata.Metadata tikaMetadata) {
super(metadata, context);
this.tikaMetadata = tikaMetadata;
}
@Override
public Mp4Handler<?> processBox(String type, byte[] payload, long boxSize,
Mp4Context context) throws IOException {
if ("stsd".equals(type) && payload != null) {
extractFromSampleDescriptions(payload);
}
return super.processBox(type, payload, boxSize, context);
}
private void extractFromSampleDescriptions(byte[] b) {
Mp4SampleEntries.walk(b, this::sampleEntry);
}
private void sampleEntry(String fourCC, byte[] b, int start, int end) {
int children = start + VISUAL_ENTRY_SIZE;
//the FourCC is the video codec ('avc1', 'hev1', ...) or, for protected
//streams, the protected sample entry format ('encv'/'drmi') with the
//original one kept in a child 'sinf'/'frma' box
if (Mp4SampleEntries.isProtected(fourCC)) {
String original = Mp4SampleEntries.originalFormat(b, children, end);
if (original != null) {
fourCC = original;
}
}
if (fourCC != null) {
tikaMetadata.set(Video.FOURCC, fourCC);
}
int bitRate = findBtrtAverageBitRate(b, children, end);
if (bitRate > 0) {
tikaMetadata.set(Video.BITRATE, bitRate);
}
}
/**
* Scans the child boxes of a sample entry for a 'btrt' BitRateBox and
* returns its average bitrate, or 0 if there is none. The box body is the
* decoding buffer size, the maximum bitrate and the average bitrate.
*/
private static int findBtrtAverageBitRate(byte[] b, int pos, int end) {
while (pos >= 0 && pos + 8 <= end) {
long size = EndianUtils.getUIntBE(b, pos);
if (size < 8 || size > end - pos) {
return 0;
}
if ("btrt".equals(Mp4SampleEntries.fourCC(b, pos + 4)) && pos + 20 <= end) {
long averageBitRate = EndianUtils.getUIntBE(b, pos + 16);
return averageBitRate > 0 && averageBitRate <= Integer.MAX_VALUE
? (int) averageBitRate : 0;
}
pos += (int) size;
}
return 0;
}
}