This is an automated email from the ASF dual-hosted git repository.

tballison pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tika.git


The following commit(s) were added to refs/heads/main by this push:
     new c3655a9694 TIKA-4800: Expose the MP4 frame rate, audio channels and 
sample size (#2982)
c3655a9694 is described below

commit c3655a96947facd71f7fdc17478fd074c8b23a61
Author: Dominik Schmidt <[email protected]>
AuthorDate: Thu Aug 6 02:24:45 2026 +0200

    TIKA-4800: Expose the MP4 frame rate, audio channels and sample size (#2982)
    
    * TIKA-4800: Add video:frame-rate from the MP4 video track
    
    * TIKA-4800: Add audio:channels from the MP4 sound track
    
    * TIKA-4800: Add audio:bits-per-sample from the MP4 sound track
---
 .../main/java/org/apache/tika/metadata/Audio.java  |  12 ++++++++
 .../main/java/org/apache/tika/metadata/Video.java  |  33 +++++++++++++++++++++
 .../java/org/apache/tika/parser/mp4/MP4Parser.java |  11 +++++++
 .../org/apache/tika/parser/mp4/MP4ParserTest.java  |  12 +++++++-
 .../test/resources/test-documents/testMP4Video.mp4 | Bin 0 -> 1810 bytes
 5 files changed, 67 insertions(+), 1 deletion(-)

diff --git a/tika-core/src/main/java/org/apache/tika/metadata/Audio.java 
b/tika-core/src/main/java/org/apache/tika/metadata/Audio.java
index 9b84b0eb3f..90232d45da 100644
--- a/tika-core/src/main/java/org/apache/tika/metadata/Audio.java
+++ b/tika-core/src/main/java/org/apache/tika/metadata/Audio.java
@@ -71,4 +71,16 @@ public interface Audio {
      * protected audio track sets it. Only set when protection is detected.
      */
     Property HAS_DRM = Property.internalBoolean("audio:has-drm");
+
+    /**
+     * Number of audio channels (e.g. 2 for stereo). {@link 
XMPDM#AUDIO_CHANNEL_TYPE}
+     * only distinguishes Mono from Stereo and cannot represent more than two
+     * channels. A per-stream value, see {@link #BITRATE}.
+     */
+    Property CHANNELS = Property.internalInteger("audio:channels");
+
+    /**
+     * Audio sample size in bits (e.g. 16). A per-stream value, see {@link 
#BITRATE}.
+     */
+    Property BITS_PER_SAMPLE = 
Property.internalInteger("audio:bits-per-sample");
 }
diff --git a/tika-core/src/main/java/org/apache/tika/metadata/Video.java 
b/tika-core/src/main/java/org/apache/tika/metadata/Video.java
new file mode 100644
index 0000000000..6dc90345b5
--- /dev/null
+++ b/tika-core/src/main/java/org/apache/tika/metadata/Video.java
@@ -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-4800.
+ *
+ * @since Apache Tika 4.0.0
+ */
+public interface Video {
+
+    /**
+     * Frame rate in frames per second. {@link XMPDM#VIDEO_FRAME_RATE} is a
+     * closed-choice text field (24, NTSC, PAL) and cannot carry an arbitrary
+     * measured rate.
+     */
+    Property FRAME_RATE = Property.internalReal("video:frame-rate");
+}
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp4/MP4Parser.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp4/MP4Parser.java
index e450c1675b..b146a8bab4 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp4/MP4Parser.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp4/MP4Parser.java
@@ -45,9 +45,11 @@ import org.apache.tika.annotation.TikaComponent;
 import org.apache.tika.exception.RuntimeSAXException;
 import org.apache.tika.exception.TikaException;
 import org.apache.tika.io.TikaInputStream;
+import org.apache.tika.metadata.Audio;
 import org.apache.tika.metadata.Metadata;
 import org.apache.tika.metadata.Property;
 import org.apache.tika.metadata.TikaCoreProperties;
+import org.apache.tika.metadata.Video;
 import org.apache.tika.metadata.XMPDM;
 import org.apache.tika.mime.MediaType;
 import org.apache.tika.parser.ParseContext;
@@ -158,6 +160,12 @@ public class MP4Parser implements Parser {
             String compressor = 
mp4Directory.getString(Mp4VideoDirectory.TAG_COMPRESSOR_NAME);
             metadata.set(XMPDM.VIDEO_COMPRESSOR, compressor);
         }
+        Float frameRate = 
mp4Directory.getFloatObject(Mp4VideoDirectory.TAG_FRAME_RATE);
+        if (frameRate != null) {
+            // set as the float's own string: set(Property, double) would 
widen it
+            // and print the double-rounding artefact (e.g. 29.969999...).
+            metadata.set(Video.FRAME_RATE, frameRate.toString());
+        }
     }
 
     /**
@@ -188,9 +196,12 @@ public class MP4Parser implements Parser {
                                         Metadata metadata) {
         addInt(mp4SoundDirectory, metadata, 
Mp4SoundDirectory.TAG_AUDIO_SAMPLE_RATE,
                 XMPDM.AUDIO_SAMPLE_RATE);
+        addInt(mp4SoundDirectory, metadata, 
Mp4SoundDirectory.TAG_AUDIO_SAMPLE_SIZE,
+                Audio.BITS_PER_SAMPLE);
 
         try {
             int numChannels = 
mp4SoundDirectory.getInt(Mp4SoundDirectory.TAG_NUMBER_OF_CHANNELS);
+            metadata.set(Audio.CHANNELS, numChannels);
 
             if (numChannels == 1) {
                 metadata.set(XMPDM.AUDIO_CHANNEL_TYPE, "Mono");
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/mp4/MP4ParserTest.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/mp4/MP4ParserTest.java
index 2a8f9fcecb..0e3cbe1e3a 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/mp4/MP4ParserTest.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/mp4/MP4ParserTest.java
@@ -46,6 +46,7 @@ import org.apache.tika.metadata.Audio;
 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;
@@ -121,6 +122,8 @@ public class MP4ParserTest extends TikaTest {
 
         assertEquals("44100", metadata.get(XMPDM.AUDIO_SAMPLE_RATE));
         assertEquals("Stereo", metadata.get(XMPDM.AUDIO_CHANNEL_TYPE));
+        assertEquals("2", metadata.get(Audio.CHANNELS));
+        assertEquals("16", metadata.get(Audio.BITS_PER_SAMPLE));
         assertEquals("M4A", metadata.get(XMPDM.AUDIO_COMPRESSOR));
         assertEquals("0.07", metadata.get(XMPDM.DURATION));
 
@@ -179,8 +182,15 @@ public class MP4ParserTest extends TikaTest {
                 back.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE));
     }
 
-    // TODO Test a MP4 Video file
     // TODO Test an old QuickTime Video File
+    @Test
+    public void testVideoFrameRate() throws Exception {
+        // a 10 fps H.264 clip generated with ffmpeg (color source, 16x16, 1s)
+        XMLResult r = getXML("testMP4Video.mp4");
+        assertEquals("video/mp4", r.metadata.get(Metadata.CONTENT_TYPE));
+        assertEquals("10.0", r.metadata.get(Video.FRAME_RATE));
+    }
+
     @Test
     @Timeout(30000)
     public void testInfiniteLoop() throws Exception {
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testMP4Video.mp4
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testMP4Video.mp4
new file mode 100644
index 0000000000..1daaa0daf4
Binary files /dev/null and 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testMP4Video.mp4
 differ

Reply via email to