Skip to content

Commit 90a33d4

Browse files
committed
TIKA-4838: Expose the MP4 audio track codec (audio:format)
TikaMp4SoundHandler already reads the sound sample entry's format fourcc (it uses it to detect drms/enca protected streams); expose it as a new audio:format property (e.g. mp4a, alac, ac-3). For protected streams the original codec is recovered from the nested frma box, with audio:has-drm still set. Follows TIKA-4779, TIKA-4800 and TIKA-4802.
1 parent 26381aa commit 90a33d4

5 files changed

Lines changed: 57 additions & 10 deletions

File tree

tika-core/src/main/java/org/apache/tika/metadata/Audio.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,15 @@ public interface Audio {
8484
*/
8585
Property BITS_PER_SAMPLE = Property.internalInteger("audio:bits-per-sample");
8686

87+
/**
88+
* The audio track's four-character codec identifier from the MP4/QuickTime
89+
* sample description (e.g. "mp4a" for AAC, "alac", "ac-3"). For protected
90+
* streams this is the original codec from the 'frma' box, with {@link #HAS_DRM}
91+
* also set. Distinct from {@link XMPDM#AUDIO_COMPRESSOR}, the human-readable
92+
* codec name.
93+
*/
94+
Property FORMAT = Property.internalText("audio:format");
95+
8796
/**
8897
* The raw javax.sound encoding name (e.g. "PCM_SIGNED"), as reported by {@code
8998
* AudioFormat#getEncoding()} (AudioParser). Distinct from {@link XMPDM#AUDIO_SAMPLE_TYPE}

tika-metadata-schema/src/main/resources/org/apache/tika/metadata/metadata-key-fields.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
{"class":"org.apache.tika.metadata.Audio","field":"CHANNELS","key":"audio:channels"},
1414
{"class":"org.apache.tika.metadata.Audio","field":"DISC_COUNT","key":"audio:disc-count"},
1515
{"class":"org.apache.tika.metadata.Audio","field":"ENCODING","key":"audio:encoding"},
16+
{"class":"org.apache.tika.metadata.Audio","field":"FORMAT","key":"audio:format"},
1617
{"class":"org.apache.tika.metadata.Audio","field":"HAS_DRM","key":"audio:has-drm"},
1718
{"class":"org.apache.tika.metadata.Audio","field":"IS_VARIABLE_BITRATE","key":"audio:is-variable-bitrate"},
1819
{"class":"org.apache.tika.metadata.Audio","field":"RAW_DISC_NUMBER","key":"audio:raw-disc-number"},

tika-metadata-schema/src/main/resources/org/apache/tika/metadata/metadata-keys.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
{"key":"audio:channels","namespace":"audio","valueType":"INTEGER","cardinality":"SIMPLE","module":"tika-core"},
7878
{"key":"audio:disc-count","namespace":"audio","valueType":"INTEGER","cardinality":"SIMPLE","module":"tika-core"},
7979
{"key":"audio:encoding","namespace":"audio","valueType":"TEXT","cardinality":"SIMPLE","module":"tika-core"},
80+
{"key":"audio:format","namespace":"audio","valueType":"TEXT","cardinality":"SIMPLE","module":"tika-core"},
8081
{"key":"audio:has-drm","namespace":"audio","valueType":"BOOLEAN","cardinality":"SIMPLE","module":"tika-core"},
8182
{"key":"audio:is-variable-bitrate","namespace":"audio","valueType":"BOOLEAN","cardinality":"SIMPLE","module":"tika-core"},
8283
{"key":"audio:raw-disc-number","namespace":"audio","valueType":"TEXT","cardinality":"SIMPLE","module":"tika-core"},

tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp4/TikaMp4SoundHandler.java

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -70,20 +70,25 @@ private void extractFromSampleDescriptions(byte[] b) {
7070
}
7171
int end = pos + (int) size;
7272
String format = fourCc(b, pos + 4);
73-
//protected streams replace the codec fourcc with a protected
74-
//entry format: 'drms' (FairPlay) or 'enca' (ISO common encryption)
73+
//sample entry: 8 byte header, 6 reserved, 2 data ref index, then
74+
//version-dependent fixed sound fields before the child boxes
75+
int childStart = pos + 18 <= end
76+
? pos + soundEntrySize(EndianUtils.getUShortBE(b, pos + 16)) : end;
77+
//protected streams replace the codec fourcc with a protected entry
78+
//format: 'drms' (FairPlay) or 'enca' (ISO common encryption); the
79+
//original codec is carried in a nested 'frma' box.
7580
if ("drms".equals(format) || "enca".equals(format)) {
7681
tikaMetadata.set(Audio.HAS_DRM, true);
77-
}
78-
if (pos + 18 <= end) {
79-
//sample entry: 8 byte header, 6 reserved, 2 data ref index,
80-
//then version-dependent fixed sound fields before child boxes
81-
int version = EndianUtils.getUShortBE(b, pos + 16);
82-
int bitRate = findEsdsAverageBitRate(b, pos + soundEntrySize(version), end, 0);
83-
if (bitRate > 0) {
84-
tikaMetadata.set(Audio.BITRATE, bitRate);
82+
String original = findOriginalFormat(b, childStart, end, 0);
83+
if (original != null) {
84+
format = original;
8585
}
8686
}
87+
tikaMetadata.set(Audio.FORMAT, format);
88+
int bitRate = findEsdsAverageBitRate(b, childStart, end, 0);
89+
if (bitRate > 0) {
90+
tikaMetadata.set(Audio.BITRATE, bitRate);
91+
}
8792
pos = end;
8893
}
8994
}
@@ -196,6 +201,36 @@ private static int skipDescriptorLength(byte[] b, int pos) {
196201
return pos + 1;
197202
}
198203

204+
/**
205+
* Scans the child boxes of a protected ('drms'/'enca') sound sample entry for
206+
* the 'frma' (original format) box nested in the 'sinf' protection scheme
207+
* information box, and returns its codec fourcc, or null if there is none.
208+
*/
209+
private static String findOriginalFormat(byte[] b, int pos, int end, int depth) {
210+
if (depth > MAX_BOX_DEPTH) {
211+
return null;
212+
}
213+
while (pos + 8 <= end) {
214+
long size = EndianUtils.getUIntBE(b, pos);
215+
if (size < 8 || size > end - pos) {
216+
return null;
217+
}
218+
int boxEnd = pos + (int) size;
219+
String type = fourCc(b, pos + 4);
220+
if ("frma".equals(type) && pos + 12 <= boxEnd) {
221+
return fourCc(b, pos + 8);
222+
}
223+
if ("sinf".equals(type)) {
224+
String original = findOriginalFormat(b, pos + 8, boxEnd, depth + 1);
225+
if (original != null) {
226+
return original;
227+
}
228+
}
229+
pos = boxEnd;
230+
}
231+
return null;
232+
}
233+
199234
private static String fourCc(byte[] b, int pos) {
200235
return new String(b, pos, 4, StandardCharsets.ISO_8859_1);
201236
}

tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/mp4/MP4ParserTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ public void testMP4ParsingAudio() throws Exception {
130130
assertEquals("Stereo", metadata.get(XMPDM.AUDIO_CHANNEL_TYPE));
131131
assertEquals("2", metadata.get(Audio.CHANNELS));
132132
assertEquals("16", metadata.get(Audio.BITS_PER_SAMPLE));
133+
assertEquals("mp4a", metadata.get(Audio.FORMAT));
133134
assertEquals("M4A", metadata.get(XMPDM.AUDIO_COMPRESSOR));
134135
assertEquals("0.07", metadata.get(XMPDM.DURATION));
135136

0 commit comments

Comments
 (0)