Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions tika-core/src/main/java/org/apache/tika/metadata/Video.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* 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.metadata;

/**
* Video metadata properties that have no suitable XMPDM equivalent.
* See TIKA-4802.
*
* @since Apache Tika 4.0.0
*/
public interface Video {

/**
* Average bitrate in bits per second, from the video track's BitRateBox
* ('btrt'). A per-stream value: in a file with several video tracks it
* reflects the last one.
*/
Property BITRATE = Property.internalInteger("video:bitrate");
}
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,7 @@
{"class":"org.apache.tika.metadata.TikaPagedText","field":"PAGE_NUMBER","key":"tk:page:number"},
{"class":"org.apache.tika.metadata.TikaPagedText","field":"PAGE_NUMBERS","key":"tk:page:numbers"},
{"class":"org.apache.tika.metadata.TikaPagedText","field":"PAGE_ROTATION","key":"tk:page:rotation"},
{"class":"org.apache.tika.metadata.Video","field":"BITRATE","key":"video:bitrate"},
{"class":"org.apache.tika.metadata.WARC","field":"WARC_PAYLOAD_CONTENT_TYPE","key":"warc:payload-content-type"},
{"class":"org.apache.tika.metadata.WARC","field":"WARC_RECORD_CONTENT_TYPE","key":"warc:record-content-type"},
{"class":"org.apache.tika.metadata.WARC","field":"WARC_RECORD_ID","key":"warc:WARC-Record-ID"},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,7 @@
{"key":"tk:version-count","namespace":"tk","valueType":"INTEGER","cardinality":"SIMPLE"},
{"key":"tk:version-number","namespace":"tk","valueType":"INTEGER","cardinality":"SIMPLE"},
{"key":"tk:warn:truncated-metadata","namespace":"tk","valueType":"BOOLEAN","cardinality":"SIMPLE"},
{"key":"video:bitrate","namespace":"video","valueType":"INTEGER","cardinality":"SIMPLE"},
{"key":"w:Comments","namespace":"w","valueType":"TEXT","cardinality":"BAG"},
{"key":"warc:WARC-Record-ID","namespace":"warc","valueType":"TEXT","cardinality":"SIMPLE"},
{"key":"warc:payload-content-type","namespace":"warc","valueType":"TEXT","cardinality":"SIMPLE"},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ public Mp4Handler<?> processBox(@NotNull String box, @Nullable byte[] payload,
//sound track: our handler additionally reads DRM markers and the
//esds average bitrate from the sample description
return new TikaMp4SoundHandler(metadata, context, tikaMetadata);
} else if (box.equals("hdlr") && payload != null && payload.length >= 12
&& payload[8] == 'v' && payload[9] == 'i'
&& payload[10] == 'd' && payload[11] == 'e') {
//video track: our handler additionally reads the btrt average
//bitrate from the sample description
return new TikaMp4VideoHandler(metadata, context, tikaMetadata);
}

return super.processBox(box, payload, size, context);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* 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 java.nio.charset.StandardCharsets;

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) before its child
* boxes: the 8 byte box header, 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 = 86;

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);
}

/**
* Walks the sample description entries: 4 bytes version and flags, a
* 4 byte entry count, then one sample entry per count, each starting with
* its own size and format fourcc.
*/
private void extractFromSampleDescriptions(byte[] b) {
if (b.length < 8) {
return;
}
long entryCount = EndianUtils.getUIntBE(b, 4);
int pos = 8;
for (long i = 0; i < entryCount && pos + 8 <= b.length; i++) {
long size = EndianUtils.getUIntBE(b, pos);
if (size < 16 || size > b.length - pos) {
break;
}
int end = pos + (int) size;
int bitRate = findBtrtAverageBitRate(b, pos + VISUAL_ENTRY_SIZE, end);
if (bitRate > 0) {
tikaMetadata.set(Video.BITRATE, bitRate);
}
pos = end;
}
}

/**
* 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(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;
}

private static String fourCc(byte[] b, int pos) {
return new String(b, pos, 4, StandardCharsets.ISO_8859_1);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.QuickTime;
import org.apache.tika.metadata.TikaCoreProperties;
import org.apache.tika.metadata.Video;
import org.apache.tika.metadata.XMP;
import org.apache.tika.metadata.XMPDM;
import org.apache.tika.parser.ParseContext;
Expand Down Expand Up @@ -142,8 +143,16 @@ public void testMP4ParsingAudio() throws Exception {
//TODO: why don't we check the output here?
}

// TODO Test a MP4 Video file
// TODO Test an old QuickTime Video File
@Test
public void testVideoBitrate() throws Exception {
// a 10 fps H.264 clip generated with ffmpeg (color source, 16x16, 1s);
// libx264 writes the average bitrate into the btrt BitRateBox
XMLResult r = getXML("testMP4Video.mp4");
assertEquals("video/mp4", r.metadata.get(Metadata.CONTENT_TYPE));
assertEquals("6536", r.metadata.get(Video.BITRATE));
}

@Test
@Timeout(30000)
public void testInfiniteLoop() throws Exception {
Expand Down
Binary file not shown.