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 d5786e3564 TIKA-4779: Add audio:track-count and audio:disc-count, 
normalize n/total values (#2947)
d5786e3564 is described below

commit d5786e3564968dd8624669d2c3c00c99597bdd3a
Author: Dominik Schmidt <[email protected]>
AuthorDate: Mon Jul 13 22:01:27 2026 +0200

    TIKA-4779: Add audio:track-count and audio:disc-count, normalize n/total 
values (#2947)
    
    * [TIKA-4779] Add audio:track-count and audio:disc-count, normalize n/total 
values
    
    The total number of tracks and discs is present in all common audio
    containers but was dropped: the MP4 parser read the second value of the
    trkn/disk atoms and discarded it, ID3 TRCK/TPOS "n/total" values were
    passed through unsplit, and the Vorbis TRACKTOTAL/DISCTOTAL comments were
    not mapped. XMPDM has no property for the totals and cannot be extended,
    so this adds a Tika-owned org.apache.tika.metadata.Audio interface
    (audio:track-count, audio:disc-count), following the pattern of
    Geographic and the Google properties from TIKA-4775.
    
    xmpDM:trackNumber and xmpDM:discNumber are declared as Integer
    properties but previously received the raw tag value, including the
    combined "n/total" form and non-numeric forms like vinyl "A1". They now
    only receive clean positive integers, parsed by a small NumberAndTotal
    value class (same shape as the recently added ISO6709 parser). Nothing
    is lost: the value exactly as tagged stays available under the new
    audio:raw-track-number and audio:raw-disc-number properties.
    
    The Ogg path also gains the previously unmapped DISCNUMBER comment, and
    explicit TRACKTOTAL/TOTALTRACKS/DISCTOTAL/TOTALDISCS comments win over
    the combined form.
    
    The existing testMP4.m4a fixture already carries the totals (track 1 of
    42, disc 6 of 12); the ID3v2.4 fixture gains a TRCK "3/12" frame.
    
    * [TIKA-4779] Consume the declared length of the disk atom
    
    The disk branch read a fixed 6 bytes with no length guard, unlike the
    adjacent trkn branch. Some encoders pad the disk atom to 8 bytes like
    trkn, in which case totalRead diverged from the bytes actually consumed
    and every following ilst record was read misaligned. Read the 6 known
    bytes when at least that much is declared, then skip the remainder, so
    the walk always consumes exactly the declared length.
    
    The udta fixture gains an iTunes-style meta/mdir/ilst with a padded
    8-byte disk atom followed by a title entry; without the guard the
    misalignment derails the remaining parse.
---
 .../main/java/org/apache/tika/metadata/Audio.java  |  52 ++++++++++++++++
 .../apache/tika/parser/audio/NumberAndTotal.java   |  67 +++++++++++++++++++++
 .../java/org/apache/tika/parser/mp3/Mp3Parser.java |  25 +++++++-
 .../tika/parser/mp4/boxes/TikaUserDataBox.java     |  22 ++++++-
 .../org/apache/tika/parser/ogg/OggAudioParser.java |  56 ++++++++++++++++-
 .../tika/parser/audio/NumberAndTotalTest.java      |  67 +++++++++++++++++++++
 .../org/apache/tika/parser/mp3/Mp3ParserTest.java  |  11 +++-
 .../org/apache/tika/parser/mp4/MP4ParserTest.java  |  10 +++
 .../apache/tika/parser/ogg/OggAudioParserTest.java |  37 ++++++++++++
 .../resources/test-documents/testMP3id3v24.mp3     | Bin 39496 -> 39511 bytes
 .../test-documents/testMP4_udtaLocation.mp4        | Bin 74 -> 200 bytes
 11 files changed, 339 insertions(+), 8 deletions(-)

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
new file mode 100644
index 0000000000..f20463bfd3
--- /dev/null
+++ b/tika-core/src/main/java/org/apache/tika/metadata/Audio.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+/**
+ * Audio metadata properties that have no XMPDM equivalent. XMPDM defines
+ * {@link XMPDM#TRACK_NUMBER} and {@link XMPDM#DISC_NUMBER} but no properties
+ * for the totals, although the common audio containers all carry them.
+ * See TIKA-4779.
+ *
+ * @since Apache Tika 4.0.0
+ */
+public interface Audio {
+
+    /**
+     * Total number of tracks on the album / in the set
+     * (MP4 'trkn' second value, ID3 TRCK "n/total", Vorbis TRACKTOTAL).
+     */
+    Property TRACK_COUNT = Property.internalInteger("audio:track-count");
+
+    /**
+     * Total number of discs in the set
+     * (MP4 'disk' second value, ID3 TPOS "n/total", Vorbis DISCTOTAL).
+     */
+    Property DISC_COUNT = Property.internalInteger("audio:disc-count");
+
+    /**
+     * The track value exactly as tagged (e.g. "3/12" or a non-numeric form
+     * like vinyl "A1"). {@link XMPDM#TRACK_NUMBER} only receives clean
+     * integers, so nothing is lost.
+     */
+    Property RAW_TRACK_NUMBER = 
Property.internalText("audio:raw-track-number");
+
+    /**
+     * The disc value exactly as tagged, see {@link #RAW_TRACK_NUMBER}.
+     */
+    Property RAW_DISC_NUMBER = Property.internalText("audio:raw-disc-number");
+}
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/audio/NumberAndTotal.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/audio/NumberAndTotal.java
new file mode 100644
index 0000000000..b5929e90f3
--- /dev/null
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/audio/NumberAndTotal.java
@@ -0,0 +1,67 @@
+/*
+ * 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.audio;
+
+/**
+ * The combined "n/total" form used by ID3 TRCK/TPOS frames and Vorbis
+ * track/disc comments, e.g. {@code "3/12"}. See TIKA-4779.
+ */
+public final class NumberAndTotal {
+
+    //null when the part is absent or not a positive integer; non-numeric
+    //forms (vinyl "A1") are preserved by the raw properties instead
+    public final Integer number;
+    public final Integer total;
+
+    NumberAndTotal(Integer number, Integer total) {
+        this.number = number;
+        this.total = total;
+    }
+
+    /**
+     * @param s a track or disc value, plain ("3") or combined ("3/12"), or 
null
+     * @return the parsed value, or null if neither part is a positive integer
+     */
+    public static NumberAndTotal parse(String s) {
+        if (s == null) {
+            return null;
+        }
+        int slash = s.indexOf('/');
+        Integer number;
+        Integer total;
+        if (slash < 0) {
+            number = positiveInteger(s);
+            total = null;
+        } else {
+            number = positiveInteger(s.substring(0, slash));
+            total = positiveInteger(s.substring(slash + 1));
+        }
+        if (number == null && total == null) {
+            return null;
+        }
+        return new NumberAndTotal(number, total);
+    }
+
+    private static Integer positiveInteger(String s) {
+        try {
+            int parsed = Integer.parseInt(s.trim());
+            return parsed > 0 ? parsed : null;
+        } catch (NumberFormatException e) {
+            return null;
+        }
+    }
+}
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/Mp3Parser.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/Mp3Parser.java
index 81b3ad7375..2f51477eb9 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/Mp3Parser.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/Mp3Parser.java
@@ -30,12 +30,14 @@ import org.apache.tika.annotation.TikaComponent;
 import org.apache.tika.exception.TikaException;
 import org.apache.tika.io.TailStream;
 import org.apache.tika.io.TikaInputStream;
+import org.apache.tika.metadata.Audio;
 import org.apache.tika.metadata.Metadata;
 import org.apache.tika.metadata.TikaCoreProperties;
 import org.apache.tika.metadata.XMPDM;
 import org.apache.tika.mime.MediaType;
 import org.apache.tika.parser.ParseContext;
 import org.apache.tika.parser.Parser;
+import org.apache.tika.parser.audio.NumberAndTotal;
 import org.apache.tika.parser.mp3.ID3Tags.ID3Comment;
 import org.apache.tika.sax.XHTMLContentHandler;
 
@@ -216,11 +218,29 @@ public class Mp3Parser implements Parser {
             sb.append(tag.getAlbum());
             if (tag.getTrackNumber() != null) {
                 sb.append(", track ").append(tag.getTrackNumber());
-                metadata.set(XMPDM.TRACK_NUMBER, tag.getTrackNumber());
+                metadata.set(Audio.RAW_TRACK_NUMBER, tag.getTrackNumber());
+                NumberAndTotal trackNumberAndTotal = 
NumberAndTotal.parse(tag.getTrackNumber());
+                if (trackNumberAndTotal != null) {
+                    if (trackNumberAndTotal.number != null) {
+                        metadata.set(XMPDM.TRACK_NUMBER, 
trackNumberAndTotal.number);
+                    }
+                    if (trackNumberAndTotal.total != null) {
+                        metadata.set(Audio.TRACK_COUNT, 
trackNumberAndTotal.total);
+                    }
+                }
             }
             if (tag.getDisc() != null) {
                 sb.append(", disc ").append(tag.getDisc());
-                metadata.set(XMPDM.DISC_NUMBER, tag.getDisc());
+                metadata.set(Audio.RAW_DISC_NUMBER, tag.getDisc());
+                NumberAndTotal discNumberAndTotal = 
NumberAndTotal.parse(tag.getDisc());
+                if (discNumberAndTotal != null) {
+                    if (discNumberAndTotal.number != null) {
+                        metadata.set(XMPDM.DISC_NUMBER, 
discNumberAndTotal.number);
+                    }
+                    if (discNumberAndTotal.total != null) {
+                        metadata.set(Audio.DISC_COUNT, 
discNumberAndTotal.total);
+                    }
+                }
             }
 
             xhtml.element("h1", tag.getTitle());
@@ -268,4 +288,5 @@ public class Mp3Parser implements Parser {
             return duration / 1000;
         }
     }
+
 }
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp4/boxes/TikaUserDataBox.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp4/boxes/TikaUserDataBox.java
index fecac20f7c..9d6e293073 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp4/boxes/TikaUserDataBox.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp4/boxes/TikaUserDataBox.java
@@ -27,6 +27,7 @@ import com.drew.metadata.mp4.Mp4Directory;
 import org.xml.sax.SAXException;
 
 import org.apache.tika.exception.RuntimeSAXException;
+import org.apache.tika.metadata.Audio;
 import org.apache.tika.metadata.Metadata;
 import org.apache.tika.metadata.TikaCoreProperties;
 import org.apache.tika.metadata.XMP;
@@ -157,14 +158,29 @@ public class TikaUserDataBox {
                         long numA = reader.getUInt32();
                         long numB = reader.getUInt32();
                         metadata.set(XMPDM.TRACK_NUMBER, (int)numA);
+                        //2 bytes track total, 2 bytes reserved
+                        int trackCount = (int) (numB >>> 16);
+                        if (trackCount > 0) {
+                            metadata.set(Audio.TRACK_COUNT, trackCount);
+                        }
                     } else {
                         //log
                         reader.skip(toRead);
                     }
                 } else if ("disk".equals(fieldName)) {
-                    int a = reader.getInt32();
-                    short b = reader.getInt16();
-                    metadata.set(XMPDM.DISC_NUMBER, a);
+                    //2 bytes reserved, 2 bytes disc, 2 bytes total; some 
encoders
+                    //pad to 8 bytes like trkn, so consume exactly toRead 
either way
+                    if (toRead >= 6) {
+                        int a = reader.getInt32();
+                        short b = reader.getInt16();
+                        metadata.set(XMPDM.DISC_NUMBER, a);
+                        if (b > 0) {
+                            metadata.set(Audio.DISC_COUNT, b);
+                        }
+                        reader.skip(toRead - 6);
+                    } else {
+                        reader.skip(toRead);
+                    }
                 } else {
                     String val = reader.getString(toRead, 
StandardCharsets.UTF_8);
                     try {
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/OggAudioParser.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/OggAudioParser.java
index defb63361c..a10238c933 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/OggAudioParser.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/OggAudioParser.java
@@ -32,11 +32,13 @@ import org.gagravarr.vorbis.VorbisStyleComments;
 import org.xml.sax.SAXException;
 
 import org.apache.tika.exception.TikaException;
+import org.apache.tika.metadata.Audio;
 import org.apache.tika.metadata.Metadata;
 import org.apache.tika.metadata.TikaCoreProperties;
 import org.apache.tika.metadata.XMP;
 import org.apache.tika.metadata.XMPDM;
 import org.apache.tika.parser.AbstractParser;
+import org.apache.tika.parser.audio.NumberAndTotal;
 import org.apache.tika.sax.XHTMLContentHandler;
 
 /**
@@ -46,6 +48,27 @@ import org.apache.tika.sax.XHTMLContentHandler;
 public abstract class OggAudioParser extends AbstractParser {
     private static final long serialVersionUID = 5168743829615945633L;
 
+
+    /**
+     * Returns the first positive integer found under the given comment keys,
+     * or null if there is none.
+     */
+    private static Integer firstPositiveInteger(VorbisStyleComments comments, 
String... keys) {
+        for (String key : keys) {
+            for (String value : comments.getComments(key)) {
+                try {
+                    int parsed = Integer.parseInt(value.trim());
+                    if (parsed > 0) {
+                        return parsed;
+                    }
+                } catch (NumberFormatException e) {
+                    //skip unparseable values
+                }
+            }
+        }
+        return null;
+    }
+
     protected static void extractChannelInfo(Metadata metadata, 
OggAudioInfoHeader info) {
         extractChannelInfo(metadata, info.getNumChannels());
     }
@@ -107,10 +130,40 @@ public abstract class OggAudioParser extends 
AbstractParser {
         // Album and Track number
         if (comments.getTrackNumber() != null) {
             xhtml.element("p", comments.getAlbum() + ", track " + 
comments.getTrackNumber());
-            metadata.set(XMPDM.TRACK_NUMBER, comments.getTrackNumber());
+            metadata.set(Audio.RAW_TRACK_NUMBER, comments.getTrackNumber());
+            NumberAndTotal trackNumberAndTotal = 
NumberAndTotal.parse(comments.getTrackNumber());
+            if (trackNumberAndTotal != null) {
+                if (trackNumberAndTotal.number != null) {
+                    metadata.set(XMPDM.TRACK_NUMBER, 
trackNumberAndTotal.number);
+                }
+                if (trackNumberAndTotal.total != null) {
+                    metadata.set(Audio.TRACK_COUNT, trackNumberAndTotal.total);
+                }
+            }
         } else {
             xhtml.element("p", comments.getAlbum());
         }
+        for (String discValue : comments.getComments("discnumber")) {
+            metadata.set(Audio.RAW_DISC_NUMBER, discValue);
+            NumberAndTotal discNumberAndTotal = 
NumberAndTotal.parse(discValue);
+            if (discNumberAndTotal != null) {
+                if (discNumberAndTotal.number != null) {
+                    metadata.set(XMPDM.DISC_NUMBER, discNumberAndTotal.number);
+                }
+                if (discNumberAndTotal.total != null) {
+                    metadata.set(Audio.DISC_COUNT, discNumberAndTotal.total);
+                }
+            }
+        }
+        //explicit totals win over the combined "n/total" form
+        Integer trackTotal = firstPositiveInteger(comments, "tracktotal", 
"totaltracks");
+        if (trackTotal != null) {
+            metadata.set(Audio.TRACK_COUNT, trackTotal);
+        }
+        Integer discTotal = firstPositiveInteger(comments, "disctotal", 
"totaldiscs");
+        if (discTotal != null) {
+            metadata.set(Audio.DISC_COUNT, discTotal);
+        }
 
         // A few other bits
         xhtml.element("p", comments.getDate());
@@ -161,4 +214,5 @@ public abstract class OggAudioParser extends AbstractParser 
{
             return String.format(Locale.ROOT, "%d:%02d", minutes, seconds);
         }
     }
+
 }
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/audio/NumberAndTotalTest.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/audio/NumberAndTotalTest.java
new file mode 100644
index 0000000000..bd6d7edb94
--- /dev/null
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/audio/NumberAndTotalTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.audio;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import org.junit.jupiter.api.Test;
+
+public class NumberAndTotalTest {
+
+    @Test
+    public void testPlainNumber() {
+        NumberAndTotal value = NumberAndTotal.parse("3");
+        assertEquals(3, value.number);
+        assertNull(value.total);
+    }
+
+    @Test
+    public void testCombinedForm() {
+        NumberAndTotal value = NumberAndTotal.parse("3/12");
+        assertEquals(3, value.number);
+        assertEquals(12, value.total);
+    }
+
+    @Test
+    public void testWhitespace() {
+        NumberAndTotal value = NumberAndTotal.parse(" 1 / 2 ");
+        assertEquals(1, value.number);
+        assertEquals(2, value.total);
+    }
+
+    @Test
+    public void testDegenerateForms() {
+        assertNull(NumberAndTotal.parse(null));
+        assertNull(NumberAndTotal.parse("   "));
+        //non-numeric forms parse to nothing; the raw properties keep them
+        assertNull(NumberAndTotal.parse("A1"));
+        assertNull(NumberAndTotal.parse("3a/of twelve"));
+
+        NumberAndTotal totalOnly = NumberAndTotal.parse("/12");
+        assertNull(totalOnly.number);
+        assertEquals(12, totalOnly.total);
+
+        NumberAndTotal nonNumericTotal = NumberAndTotal.parse("3/of twelve");
+        assertEquals(3, nonNumericTotal.number);
+        assertNull(nonNumericTotal.total);
+
+        NumberAndTotal zeroTotal = NumberAndTotal.parse("3/0");
+        assertEquals(3, zeroTotal.number);
+        assertNull(zeroTotal.total);
+    }
+}
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/mp3/Mp3ParserTest.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/mp3/Mp3ParserTest.java
index bbe9936e2e..0bf7053c72 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/mp3/Mp3ParserTest.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/mp3/Mp3ParserTest.java
@@ -26,6 +26,7 @@ import org.junit.jupiter.api.Test;
 
 import org.apache.tika.TikaTest;
 import org.apache.tika.io.TikaInputStream;
+import org.apache.tika.metadata.Audio;
 import org.apache.tika.metadata.Metadata;
 import org.apache.tika.metadata.TikaCoreProperties;
 import org.apache.tika.metadata.XMPDM;
@@ -115,7 +116,10 @@ public class Mp3ParserTest extends TikaTest {
         assertEquals("XXX - ID3v1 Comment\nTest Comment",
                 metadata.get(XMPDM.LOG_COMMENT.getName()));
         assertEquals("1", metadata.get(XMPDM.TRACK_NUMBER));
-        assertEquals("1/1", metadata.get(XMPDM.DISC_NUMBER));
+        //TPOS "1/1" is normalized into number and total; the raw form survives
+        assertEquals("1", metadata.get(XMPDM.DISC_NUMBER));
+        assertEquals("1", metadata.get(Audio.DISC_COUNT));
+        assertEquals("1/1", metadata.get(Audio.RAW_DISC_NUMBER));
         assertEquals("1", metadata.get(XMPDM.COMPILATION));
 
         assertEquals("44100", metadata.get(XMPDM.AUDIO_SAMPLE_RATE));
@@ -198,7 +202,10 @@ public class Mp3ParserTest extends TikaTest {
         assertEquals("Rock", metadata.get(XMPDM.GENRE));
         assertEquals("1", metadata.get(XMPDM.COMPILATION));
 
-        assertEquals(null, metadata.get(XMPDM.TRACK_NUMBER));
+        //TRCK "3/12" is normalized into number and total; the raw form 
survives
+        assertEquals("3", metadata.get(XMPDM.TRACK_NUMBER));
+        assertEquals("12", metadata.get(Audio.TRACK_COUNT));
+        assertEquals("3/12", metadata.get(Audio.RAW_TRACK_NUMBER));
         assertEquals("1", metadata.get(XMPDM.DISC_NUMBER));
     }
 
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 f0e5b75300..afa8d97619 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
@@ -37,6 +37,7 @@ import org.xml.sax.ContentHandler;
 
 import org.apache.tika.TikaTest;
 import org.apache.tika.io.TikaInputStream;
+import org.apache.tika.metadata.Audio;
 import org.apache.tika.metadata.Metadata;
 import org.apache.tika.metadata.TikaCoreProperties;
 import org.apache.tika.metadata.XMP;
@@ -101,8 +102,11 @@ public class MP4ParserTest extends TikaTest {
         assertEquals("Test Genre", metadata.get(XMPDM.GENRE));
         assertEquals("Test Comments", 
metadata.get(XMPDM.LOG_COMMENT.getName()));
         assertEquals("1", metadata.get(XMPDM.TRACK_NUMBER));
+        //the totals from the trkn/disk atoms were previously read and 
discarded
+        assertEquals("42", metadata.get(Audio.TRACK_COUNT));
         assertEquals("Test Album Artist", metadata.get(XMPDM.ALBUM_ARTIST));
         assertEquals("6", metadata.get(XMPDM.DISC_NUMBER));
+        assertEquals("12", metadata.get(Audio.DISC_COUNT));
         assertEquals("0", metadata.get(XMPDM.COMPILATION));
 
 
@@ -318,5 +322,11 @@ public class MP4ParserTest extends TikaTest {
         assertEquals(12.3456, 
Double.parseDouble(metadata.get(TikaCoreProperties.LATITUDE)), 0.00001);
         assertEquals(-98.7654, 
Double.parseDouble(metadata.get(TikaCoreProperties.LONGITUDE)), 0.00001);
         assertEquals(10.5, 
Double.parseDouble(metadata.get(TikaCoreProperties.ALTITUDE)), 0.00001);
+
+        //the fixture's disk atom uses the padded 8-byte form; the title after
+        //it proves the ilst walk consumes exactly the declared length
+        assertEquals("6", metadata.get(XMPDM.DISC_NUMBER));
+        assertEquals("12", metadata.get(Audio.DISC_COUNT));
+        assertEquals("Test Title", metadata.get(TikaCoreProperties.TITLE));
     }
 }
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/ogg/OggAudioParserTest.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/ogg/OggAudioParserTest.java
index 549885a076..a0fd0d7e03 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/ogg/OggAudioParserTest.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/ogg/OggAudioParserTest.java
@@ -18,11 +18,13 @@ package org.apache.tika.parser.ogg;
 
 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
 
 import org.gagravarr.vorbis.VorbisComments;
 import org.junit.jupiter.api.Test;
 import org.xml.sax.helpers.DefaultHandler;
 
+import org.apache.tika.metadata.Audio;
 import org.apache.tika.metadata.Metadata;
 import org.apache.tika.metadata.XMPDM;
 import org.apache.tika.sax.XHTMLContentHandler;
@@ -57,6 +59,41 @@ public class OggAudioParserTest {
                 metadata.getValues("vorbis:copyright"));
     }
 
+    @Test
+    public void testTrackAndDiscTotals() throws Exception {
+        Metadata metadata = extractComments("tracknumber", "3/12",
+                "discnumber", "1", "disctotal", "2");
+
+        assertEquals("3", metadata.get(XMPDM.TRACK_NUMBER));
+        assertEquals("12", metadata.get(Audio.TRACK_COUNT));
+        assertEquals("3/12", metadata.get(Audio.RAW_TRACK_NUMBER));
+        assertEquals("1", metadata.get(XMPDM.DISC_NUMBER));
+        assertEquals("2", metadata.get(Audio.DISC_COUNT));
+    }
+
+    @Test
+    public void testDegenerateTrackValues() throws Exception {
+        Metadata metadata = extractComments("tracknumber", "3/of twelve",
+                "discnumber", "/2");
+
+        //non-numeric parts stay out of the typed properties but survive raw
+        assertEquals("3", metadata.get(XMPDM.TRACK_NUMBER));
+        assertNull(metadata.get(Audio.TRACK_COUNT));
+        assertEquals("3/of twelve", metadata.get(Audio.RAW_TRACK_NUMBER));
+        assertNull(metadata.get(XMPDM.DISC_NUMBER));
+        assertEquals("2", metadata.get(Audio.DISC_COUNT));
+        assertEquals("/2", metadata.get(Audio.RAW_DISC_NUMBER));
+    }
+
+    @Test
+    public void testExplicitTotalWinsOverCombinedForm() throws Exception {
+        Metadata metadata = extractComments("tracknumber", "3/12",
+                "totaltracks", "14");
+
+        assertEquals("3", metadata.get(XMPDM.TRACK_NUMBER));
+        assertEquals("14", metadata.get(Audio.TRACK_COUNT));
+    }
+
     /**
      * Runs the given key/value comment pairs through the shared comment
      * extraction and returns the resulting metadata.
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testMP3id3v24.mp3
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testMP3id3v24.mp3
index 368360b6a5..c48df959bd 100644
Binary files 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testMP3id3v24.mp3
 and 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testMP3id3v24.mp3
 differ
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testMP4_udtaLocation.mp4
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testMP4_udtaLocation.mp4
index c01bac1209..507b2a75b8 100644
Binary files 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testMP4_udtaLocation.mp4
 and 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/resources/test-documents/testMP4_udtaLocation.mp4
 differ

Reply via email to