You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tika.apache.org by ta...@apache.org on 2020/04/10 21:32:10 UTC

[tika] branch master updated: TIKA-3084 -- upgrade mp4parser dependency

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

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


The following commit(s) were added to refs/heads/master by this push:
     new adf791a  TIKA-3084 -- upgrade mp4parser dependency
adf791a is described below

commit adf791ab2e928b31264c5666d7e5ba03b92860af
Author: tallison <ta...@apache.org>
AuthorDate: Fri Apr 10 17:31:49 2020 -0400

    TIKA-3084 -- upgrade mp4parser dependency
---
 tika-parsers/pom.xml                               |   4 +-
 .../tika/parser/mp4/DirectFileReadDataSource.java  | 127 -------
 .../java/org/apache/tika/parser/mp4/MP4Parser.java | 397 +++++++++++----------
 .../org/apache/tika/parser/mp4/MP4ParserTest.java  |   1 +
 4 files changed, 212 insertions(+), 317 deletions(-)

diff --git a/tika-parsers/pom.xml b/tika-parsers/pom.xml
index 35938dd..b989340 100644
--- a/tika-parsers/pom.xml
+++ b/tika-parsers/pom.xml
@@ -322,9 +322,9 @@
       <version>8.0.1</version>
     </dependency>
     <dependency>
-      <groupId>com.googlecode.mp4parser</groupId>
+      <groupId>org.tallison</groupId>
       <artifactId>isoparser</artifactId>
-      <version>1.1.22</version>
+      <version>1.9.41.2</version>
     </dependency>
     <!-- this is a fork of com.drewnoakes
       metadata extractor that shade/relocates com.adobe.internal
diff --git a/tika-parsers/src/main/java/org/apache/tika/parser/mp4/DirectFileReadDataSource.java b/tika-parsers/src/main/java/org/apache/tika/parser/mp4/DirectFileReadDataSource.java
deleted file mode 100644
index 698a106..0000000
--- a/tika-parsers/src/main/java/org/apache/tika/parser/mp4/DirectFileReadDataSource.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * 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 static com.googlecode.mp4parser.util.CastUtils.l2i;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.math.BigInteger;
-import java.nio.ByteBuffer;
-import java.nio.channels.WritableByteChannel;
-
-import com.googlecode.mp4parser.DataSource;
-
-/**
- * A {@link DataSource} implementation that relies on direct reads from a {@link RandomAccessFile}.
- * It should be slower than {@link com.googlecode.mp4parser.FileDataSourceImpl} but does not incur the implicit file locks of
- * memory mapped I/O on some JVMs. This implementation allows for a more controlled deletion of files
- * and might be preferred when working with temporary files.
- * @see <a href="http://bugs.java.com/view_bug.do?bug_id=4724038">JDK-4724038 : (fs) Add unmap method to MappedByteBuffer</a>
- * @see <a href="http://bugs.java.com/view_bug.do?bug_id=6359560">JDK-6359560 : (fs) File.deleteOnExit() doesn't work when MappedByteBuffer exists (win)</a>
- */
-public class DirectFileReadDataSource implements DataSource {
-
-    private static final int TRANSFER_SIZE = 8192;
-
-    private RandomAccessFile raf;
-
-    public DirectFileReadDataSource(File f) throws IOException {
-        this.raf = new RandomAccessFile(f, "r");
-    }
-
-    public int read(ByteBuffer byteBuffer) throws IOException {
-        int len = byteBuffer.remaining();
-        int totalRead = 0;
-        int bytesRead = 0;
-        byte[] buf = new byte[TRANSFER_SIZE];
-        while (totalRead < len) {
-            int bytesToRead = Math.min((len - totalRead), TRANSFER_SIZE);
-            bytesRead = raf.read(buf, 0, bytesToRead);
-            if (bytesRead < 0) {
-                break;
-            } else {
-                totalRead += bytesRead;
-            }
-            byteBuffer.put(buf, 0, bytesRead);
-        }
-        if (bytesRead < 0 && position() == size() && byteBuffer.hasRemaining()) {
-            throw new IOException("End of stream reached earlier than expected");
-        }
-        return ((bytesRead < 0) && (totalRead == 0)) ? -1 : totalRead;
-    }
-
-    public int readAllInOnce(ByteBuffer byteBuffer) throws IOException {
-        if (byteBuffer.remaining() > raf.length()) {
-            throw new IOException("trying to readAllInOnce past end of stream");
-        }
-        byte[] buf = new byte[byteBuffer.remaining()];
-        int read = raf.read(buf);
-        byteBuffer.put(buf, 0, read);
-        return read;
-    }
-
-    public long size() throws IOException {
-        return raf.length();
-    }
-
-    public long position() throws IOException {
-        return raf.getFilePointer();
-    }
-
-    public void position(long nuPos) throws IOException {
-        if (nuPos > raf.length()) {
-            throw new IOException("requesting seek past end of stream");
-        }
-        raf.seek(nuPos);
-    }
-
-    public long transferTo(long position, long count, WritableByteChannel target) throws IOException {
-        return target.write(map(position, count));
-    }
-
-    public ByteBuffer map(long startPosition, long size) throws IOException {
-        if (startPosition < 0 || size < 0) {
-            throw new IOException("startPosition and size must both be >= 0");
-        }
-        //make sure that start+size aren't greater than avail size
-        //in raf.
-        BigInteger end = BigInteger.valueOf(startPosition);
-        end = end.add(BigInteger.valueOf(size));
-        if (end.compareTo(BigInteger.valueOf(raf.length())) > 0) {
-            throw new IOException("requesting read past end of stream");
-        }
-
-        raf.seek(startPosition);
-        int payLoadSize = l2i(size);
-        //hack to check for potential overflow
-        if (Long.MAX_VALUE-payLoadSize < startPosition ||
-                Long.MAX_VALUE-payLoadSize > raf.length()) {
-            throw new IOException("requesting read past end of stream");
-        }
-        byte[] payload = new byte[payLoadSize];
-        raf.readFully(payload);
-        return ByteBuffer.wrap(payload);
-    }
-
-    @Override
-    public void close() throws IOException {
-        raf.close();
-    }
-
-}
\ No newline at end of file
diff --git a/tika-parsers/src/main/java/org/apache/tika/parser/mp4/MP4Parser.java b/tika-parsers/src/main/java/org/apache/tika/parser/mp4/MP4Parser.java
index 6d1959e..ea8d94d 100644
--- a/tika-parsers/src/main/java/org/apache/tika/parser/mp4/MP4Parser.java
+++ b/tika-parsers/src/main/java/org/apache/tika/parser/mp4/MP4Parser.java
@@ -16,36 +16,7 @@
  */
 package org.apache.tika.parser.mp4;
 
-
-import com.coremedia.iso.IsoFile;
-import com.coremedia.iso.boxes.Box;
-import com.coremedia.iso.boxes.Container;
-import com.coremedia.iso.boxes.FileTypeBox;
-import com.coremedia.iso.boxes.MetaBox;
-import com.coremedia.iso.boxes.MovieBox;
-import com.coremedia.iso.boxes.MovieHeaderBox;
-import com.coremedia.iso.boxes.SampleDescriptionBox;
-import com.coremedia.iso.boxes.SampleTableBox;
-import com.coremedia.iso.boxes.TrackBox;
-import com.coremedia.iso.boxes.TrackHeaderBox;
-import com.coremedia.iso.boxes.UserDataBox;
-import com.coremedia.iso.boxes.apple.AppleItemListBox;
-import com.coremedia.iso.boxes.sampleentry.AudioSampleEntry;
-import com.googlecode.mp4parser.DataSource;
-import com.googlecode.mp4parser.boxes.apple.AppleAlbumBox;
-import com.googlecode.mp4parser.boxes.apple.AppleArtist2Box;
-import com.googlecode.mp4parser.boxes.apple.AppleArtistBox;
-import com.googlecode.mp4parser.boxes.apple.AppleCommentBox;
-import com.googlecode.mp4parser.boxes.apple.AppleCompilationBox;
-import com.googlecode.mp4parser.boxes.apple.AppleDiskNumberBox;
-import com.googlecode.mp4parser.boxes.apple.AppleEncoderBox;
-import com.googlecode.mp4parser.boxes.apple.AppleGPSCoordinatesBox;
-import com.googlecode.mp4parser.boxes.apple.AppleGenreBox;
-import com.googlecode.mp4parser.boxes.apple.AppleNameBox;
-import com.googlecode.mp4parser.boxes.apple.AppleRecordingYear2Box;
-import com.googlecode.mp4parser.boxes.apple.AppleTrackAuthorBox;
-import com.googlecode.mp4parser.boxes.apple.AppleTrackNumberBox;
-import com.googlecode.mp4parser.boxes.apple.Utf8AppleDataBox;
+import org.apache.tika.config.Field;
 import org.apache.tika.exception.TikaException;
 import org.apache.tika.io.TemporaryResources;
 import org.apache.tika.io.TikaInputStream;
@@ -58,6 +29,34 @@ import org.apache.tika.mime.MediaType;
 import org.apache.tika.parser.AbstractParser;
 import org.apache.tika.parser.ParseContext;
 import org.apache.tika.sax.XHTMLContentHandler;
+import org.mp4parser.Box;
+import org.mp4parser.Container;
+import org.mp4parser.IsoFile;
+import org.mp4parser.boxes.apple.AppleAlbumBox;
+import org.mp4parser.boxes.apple.AppleArtist2Box;
+import org.mp4parser.boxes.apple.AppleArtistBox;
+import org.mp4parser.boxes.apple.AppleCommentBox;
+import org.mp4parser.boxes.apple.AppleCompilationBox;
+import org.mp4parser.boxes.apple.AppleDiskNumberBox;
+import org.mp4parser.boxes.apple.AppleEncoderBox;
+import org.mp4parser.boxes.apple.AppleGPSCoordinatesBox;
+import org.mp4parser.boxes.apple.AppleGenreBox;
+import org.mp4parser.boxes.apple.AppleItemListBox;
+import org.mp4parser.boxes.apple.AppleNameBox;
+import org.mp4parser.boxes.apple.AppleRecordingYear2Box;
+import org.mp4parser.boxes.apple.AppleTrackAuthorBox;
+import org.mp4parser.boxes.apple.AppleTrackNumberBox;
+import org.mp4parser.boxes.apple.Utf8AppleDataBox;
+import org.mp4parser.boxes.iso14496.part12.FileTypeBox;
+import org.mp4parser.boxes.iso14496.part12.MetaBox;
+import org.mp4parser.boxes.iso14496.part12.MovieBox;
+import org.mp4parser.boxes.iso14496.part12.MovieHeaderBox;
+import org.mp4parser.boxes.iso14496.part12.SampleDescriptionBox;
+import org.mp4parser.boxes.iso14496.part12.SampleTableBox;
+import org.mp4parser.boxes.iso14496.part12.TrackBox;
+import org.mp4parser.boxes.iso14496.part12.TrackHeaderBox;
+import org.mp4parser.boxes.iso14496.part12.UserDataBox;
+import org.mp4parser.boxes.sampleentry.AudioSampleEntry;
 import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
 
@@ -117,7 +116,6 @@ public class MP4Parser extends AbstractParser {
         return SUPPORTED_TYPES;
     }
 
-
     public void parse(
             InputStream stream, ContentHandler handler,
             Metadata metadata, ParseContext context)
@@ -129,186 +127,209 @@ public class MP4Parser extends AbstractParser {
         TemporaryResources tmp = new TemporaryResources();
         TikaInputStream tstream = TikaInputStream.get(stream, tmp);
 
-        try (DataSource dataSource = new DirectFileReadDataSource(tstream.getFile())) {
-            try (IsoFile isoFile = new IsoFile(dataSource)) {
-                tmp.addResource(isoFile);
-
-                // Grab the file type box
-                FileTypeBox fileType = getOrNull(isoFile, FileTypeBox.class);
-                if (fileType != null) {
-                    // Identify the type
-                    MediaType type = MediaType.application("mp4");
-                    for (Map.Entry<MediaType, List<String>> e : typesMap.entrySet()) {
-                        if (e.getValue().contains(fileType.getMajorBrand())) {
-                            type = e.getKey();
-                            break;
-                        }
+        try (IsoFile isoFile = new IsoFile(tstream.getFile())) {
+            tmp.addResource(isoFile);
+
+            // Grab the file type box
+            FileTypeBox fileType = getOrNull(isoFile, FileTypeBox.class);
+            if (fileType != null) {
+                // Identify the type
+                MediaType type = MediaType.application("mp4");
+                for (Map.Entry<MediaType, List<String>> e : typesMap.entrySet()) {
+                    if (e.getValue().contains(fileType.getMajorBrand())) {
+                        type = e.getKey();
+                        break;
                     }
-                    metadata.set(Metadata.CONTENT_TYPE, type.toString());
+                }
+                metadata.set(Metadata.CONTENT_TYPE, type.toString());
 
-                    if (type.getType().equals("audio")) {
-                        metadata.set(XMPDM.AUDIO_COMPRESSOR, fileType.getMajorBrand().trim());
-                    }
-                } else {
-                    // Some older QuickTime files lack the FileType
-                    metadata.set(Metadata.CONTENT_TYPE, "video/quicktime");
+                if (type.getType().equals("audio")) {
+                    metadata.set(XMPDM.AUDIO_COMPRESSOR, fileType.getMajorBrand().trim());
                 }
+            } else {
+                // Some older QuickTime files lack the FileType
+                metadata.set(Metadata.CONTENT_TYPE, "video/quicktime");
+            }
 
 
-                // Get the main MOOV box
-                MovieBox moov = getOrNull(isoFile, MovieBox.class);
-                if (moov == null) {
-                    // Bail out
-                    return;
-                }
+            // Get the main MOOV box
+            MovieBox moov = getOrNull(isoFile, MovieBox.class);
+            if (moov == null) {
+                // Bail out
+                return;
+            }
 
 
-                XHTMLContentHandler xhtml = new XHTMLContentHandler(handler, metadata);
-                xhtml.startDocument();
+            XHTMLContentHandler xhtml = new XHTMLContentHandler(handler, metadata);
+            xhtml.startDocument();
 
+            handleMovieHeaderBox(moov, metadata, xhtml);
+            handleTrackBoxes(moov, metadata, xhtml);
 
-            // Pull out some information from the header box
-            MovieHeaderBox mHeader = getOrNull(moov, MovieHeaderBox.class);
-            if (mHeader != null) {
-                // Get the creation and modification dates
-                metadata.set(TikaCoreProperties.CREATED, mHeader.getCreationTime());
-                metadata.set(TikaCoreProperties.MODIFIED, mHeader.getModificationTime());
+            // Get metadata from the User Data Box
+            UserDataBox userData = getOrNull(moov, UserDataBox.class);
+            if (userData != null) {
+                extractGPS(userData, metadata);
+                MetaBox metaBox = getOrNull(userData, MetaBox.class);
 
-                    // Get the duration
-                    double durationSeconds = ((double) mHeader.getDuration()) / mHeader.getTimescale();
-                    metadata.set(XMPDM.DURATION, DURATION_FORMAT.format(durationSeconds));
+                // Check for iTunes Metadata
+                // See http://atomicparsley.sourceforge.net/mpeg-4files.html and
+                //  http://code.google.com/p/mp4v2/wiki/iTunesMetadata for more on these
+                handleApple(metaBox, metadata, xhtml);
+                // TODO Check for other kinds too
+            }
 
-                    // The timescale is normally the sampling rate
-                    metadata.set(XMPDM.AUDIO_SAMPLE_RATE, (int) mHeader.getTimescale());
-                }
+            // All done
+            xhtml.endDocument();
 
+        } finally {
+            tmp.dispose();
+        }
 
-                // Get some more information from the track header
-                // TODO Decide how to handle multiple tracks
-                List<TrackBox> tb = moov.getBoxes(TrackBox.class);
-                if (tb.size() > 0) {
-                    TrackBox track = tb.get(0);
-
-                    TrackHeaderBox header = track.getTrackHeaderBox();
-                    // Get the creation and modification dates
-                    metadata.set(TikaCoreProperties.CREATED, header.getCreationTime());
-                    metadata.set(TikaCoreProperties.MODIFIED, header.getModificationTime());
-
-                    // Get the video with and height
-                    metadata.set(Metadata.IMAGE_WIDTH, (int) header.getWidth());
-                    metadata.set(Metadata.IMAGE_LENGTH, (int) header.getHeight());
-
-                    // Get the sample information
-                    SampleTableBox samples = track.getSampleTableBox();
-                    if (samples !=  null) {
-                        SampleDescriptionBox sampleDesc = samples.getSampleDescriptionBox();
-                        if (sampleDesc != null) {
-                            // Look for the first Audio Sample, if present
-                            AudioSampleEntry sample = getOrNull(sampleDesc, AudioSampleEntry.class);
-                            if (sample != null) {
-                                XMPDM.ChannelTypePropertyConverter.convertAndSet(metadata, sample.getChannelCount());
-                                //metadata.set(XMPDM.AUDIO_SAMPLE_TYPE, sample.getSampleSize());    // TODO Num -> Type mapping
-                                metadata.set(XMPDM.AUDIO_SAMPLE_RATE, (int) sample.getSampleRate());
-                                //metadata.set(XMPDM.AUDIO_, sample.getSamplesPerPacket());
-                                //metadata.set(XMPDM.AUDIO_, sample.getBytesPerSample());
-                            }
-                        }
-                    }
-                }
+    }
 
-                // Get metadata from the User Data Box
-                UserDataBox userData = getOrNull(moov, UserDataBox.class);
-                if (userData != null) {
-                    extractGPS(userData, metadata);
-                    MetaBox meta = getOrNull(userData, MetaBox.class);
-
-                    // Check for iTunes Metadata
-                    // See http://atomicparsley.sourceforge.net/mpeg-4files.html and
-                    //  http://code.google.com/p/mp4v2/wiki/iTunesMetadata for more on these
-                    AppleItemListBox apple = getOrNull(meta, AppleItemListBox.class);
-                    if (apple != null) {
-                        // Title
-                        AppleNameBox title = getOrNull(apple, AppleNameBox.class);
-                        addMetadata(TikaCoreProperties.TITLE, metadata, title);
-
-                        // Artist
-                        AppleArtistBox artist = getOrNull(apple, AppleArtistBox.class);
-                        addMetadata(TikaCoreProperties.CREATOR, metadata, artist);
-                        addMetadata(XMPDM.ARTIST, metadata, artist);
-
-                        // Album Artist
-                        AppleArtist2Box artist2 = getOrNull(apple, AppleArtist2Box.class);
-                        addMetadata(XMPDM.ALBUM_ARTIST, metadata, artist2);
-
-                        // Album
-                        AppleAlbumBox album = getOrNull(apple, AppleAlbumBox.class);
-                        addMetadata(XMPDM.ALBUM, metadata, album);
-
-                        // Composer
-                        AppleTrackAuthorBox composer = getOrNull(apple, AppleTrackAuthorBox.class);
-                        addMetadata(XMPDM.COMPOSER, metadata, composer);
-
-                        // Genre
-                        AppleGenreBox genre = getOrNull(apple, AppleGenreBox.class);
-                        addMetadata(XMPDM.GENRE, metadata, genre);
-
-                        // Year
-                        AppleRecordingYear2Box year = getOrNull(apple, AppleRecordingYear2Box.class);
-                        if (year != null) {
-                            metadata.set(XMPDM.RELEASE_DATE, year.getValue());
-                        }
-
-                        // Track number
-                        AppleTrackNumberBox trackNum = getOrNull(apple, AppleTrackNumberBox.class);
-                        if (trackNum != null) {
-                            metadata.set(XMPDM.TRACK_NUMBER, trackNum.getA());
-                            //metadata.set(XMPDM.NUMBER_OF_TRACKS, trackNum.getB()); // TODO
-                        }
-
-                        // Disc number
-                        AppleDiskNumberBox discNum = getOrNull(apple, AppleDiskNumberBox.class);
-                        if (discNum != null) {
-                            metadata.set(XMPDM.DISC_NUMBER, discNum.getA());
-                        }
-
-                        // Compilation
-                        AppleCompilationBox compilation = getOrNull(apple, AppleCompilationBox.class);
-                        if (compilation != null) {
-                            metadata.set(XMPDM.COMPILATION, (int) compilation.getValue());
-                        }
-
-                        // Comment
-                        AppleCommentBox comment = getOrNull(apple, AppleCommentBox.class);
-                        addMetadata(XMPDM.LOG_COMMENT, metadata, comment);
-
-                        // Encoder
-                        AppleEncoderBox encoder = getOrNull(apple, AppleEncoderBox.class);
-                        if (encoder != null) {
-                            metadata.set(XMP.CREATOR_TOOL, encoder.getValue());
-                        }
-
-
-                        // As text
-                        for (Box box : apple.getBoxes()) {
-                            if (box instanceof Utf8AppleDataBox) {
-                                xhtml.element("p", ((Utf8AppleDataBox) box).getValue());
-                            }
-                        }
-                    }
+    private void handleTrackBoxes(MovieBox moov, Metadata metadata, XHTMLContentHandler xhtml) {
 
-                    // TODO Check for other kinds too
+        // Get some more information from the track header
+        // TODO Decide how to handle multiple tracks
+        List<TrackBox> tb = moov.getBoxes(TrackBox.class);
+        if (tb == null || tb.size() == 0) {
+            return;
+        }
+        TrackBox track = tb.get(0);
+
+        TrackHeaderBox header = track.getTrackHeaderBox();
+        // Get the creation and modification dates
+        metadata.set(TikaCoreProperties.CREATED, header.getCreationTime());
+        metadata.set(TikaCoreProperties.MODIFIED, header.getModificationTime());
+
+        // Get the video with and height
+        metadata.set(Metadata.IMAGE_WIDTH, (int) header.getWidth());
+        metadata.set(Metadata.IMAGE_LENGTH, (int) header.getHeight());
+
+        // Get the sample information
+        SampleTableBox samples = track.getSampleTableBox();
+        if (samples != null) {
+            SampleDescriptionBox sampleDesc = samples.getSampleDescriptionBox();
+            if (sampleDesc != null) {
+                // Look for the first Audio Sample, if present
+                AudioSampleEntry sample = getOrNull(sampleDesc, AudioSampleEntry.class);
+                if (sample != null) {
+                    XMPDM.ChannelTypePropertyConverter.convertAndSet(metadata, sample.getChannelCount());
+                    //metadata.set(XMPDM.AUDIO_SAMPLE_TYPE, sample.getSampleSize());    // TODO Num -> Type mapping
+                    metadata.set(XMPDM.AUDIO_SAMPLE_RATE, (int) sample.getSampleRate());
+                    //metadata.set(XMPDM.AUDIO_, sample.getSamplesPerPacket());
+                    //metadata.set(XMPDM.AUDIO_, sample.getBytesPerSample());
                 }
+            }
+        }
+    }
+
+    private void handleMovieHeaderBox(MovieBox moov, Metadata metadata, XHTMLContentHandler xhtml) {
+        // Pull out some information from the header box
+        MovieHeaderBox mHeader = getOrNull(moov, MovieHeaderBox.class);
+        if (mHeader == null) {
+            return;
+        }
+        // Get the creation and modification dates
+        metadata.set(TikaCoreProperties.CREATED, mHeader.getCreationTime());
+        metadata.set(TikaCoreProperties.MODIFIED, mHeader.getModificationTime());
+
+        // Get the duration
+        double durationSeconds = ((double) mHeader.getDuration()) / mHeader.getTimescale();
+        metadata.set(XMPDM.DURATION, DURATION_FORMAT.format(durationSeconds));
+
+        // The timescale is normally the sampling rate
+        metadata.set(XMPDM.AUDIO_SAMPLE_RATE, (int) mHeader.getTimescale());
+    }
+
+    private void handleApple(MetaBox metaBox, Metadata metadata, XHTMLContentHandler xhtml) throws SAXException {
+        AppleItemListBox apple = getOrNull(metaBox, AppleItemListBox.class);
+        if (apple == null) {
+            return;
+        }
+        // Title
+        AppleNameBox title = getOrNull(apple, AppleNameBox.class);
+        addMetadata(TikaCoreProperties.TITLE, metadata, title);
+
+        // Artist
+        AppleArtistBox artist = getOrNull(apple, AppleArtistBox.class);
+        addMetadata(TikaCoreProperties.CREATOR, metadata, artist);
+        addMetadata(XMPDM.ARTIST, metadata, artist);
+
+        // Album Artist
+        AppleArtist2Box artist2 = getOrNull(apple, AppleArtist2Box.class);
+        addMetadata(XMPDM.ALBUM_ARTIST, metadata, artist2);
+
+        // Album
+        AppleAlbumBox album = getOrNull(apple, AppleAlbumBox.class);
+        addMetadata(XMPDM.ALBUM, metadata, album);
+
+        // Composer
+        AppleTrackAuthorBox composer = getOrNull(apple, AppleTrackAuthorBox.class);
+        addMetadata(XMPDM.COMPOSER, metadata, composer);
+
+        // Genre
+        AppleGenreBox genre = getOrNull(apple, AppleGenreBox.class);
+        addMetadata(XMPDM.GENRE, metadata, genre);
+
+        // Year
+        AppleRecordingYear2Box year = getOrNull(apple, AppleRecordingYear2Box.class);
+        if (year != null) {
+            metadata.set(XMPDM.RELEASE_DATE, year.getValue());
+        }
+
+        // Track number
+        AppleTrackNumberBox trackNum = getOrNull(apple, AppleTrackNumberBox.class);
+        if (trackNum != null) {
+            metadata.set(XMPDM.TRACK_NUMBER, trackNum.getA());
+            //metadata.set(XMPDM.NUMBER_OF_TRACKS, trackNum.getB()); // TODO
+        }
 
+        // Disc number
+        AppleDiskNumberBox discNum = getOrNull(apple, AppleDiskNumberBox.class);
+        if (discNum != null) {
+            metadata.set(XMPDM.DISC_NUMBER, discNum.getA());
+        }
+
+        // Compilation
+        AppleCompilationBox compilation = getOrNull(apple, AppleCompilationBox.class);
+        if (compilation != null) {
+            metadata.set(XMPDM.COMPILATION, (int) compilation.getValue());
+        }
+
+        // Comment
+        AppleCommentBox comment = getOrNull(apple, AppleCommentBox.class);
+        addMetadata(XMPDM.LOG_COMMENT, metadata, comment);
+
+        // Encoder
+        AppleEncoderBox encoder = getOrNull(apple, AppleEncoderBox.class);
+        if (encoder != null) {
+            metadata.set(XMP.CREATOR_TOOL, encoder.getValue());
+        }
 
-                // All done
-                xhtml.endDocument();
+
+        // As text
+        for (Box box : apple.getBoxes()) {
+            if (box instanceof Utf8AppleDataBox) {
+                xhtml.element("p", ((Utf8AppleDataBox) box).getValue());
             }
-        } finally {
-            tmp.dispose();
         }
 
     }
 
+    /**
+     * Override the maximum record size limit.  NOTE: this
+     * sets a static variable on the IsoFile and affects all files
+     * parsed in this JVM!!!
+     *
+     * @param maxRecordSize
+     */
+    @Field
+    public void setMaxRecordSize(long maxRecordSize) {
+        IsoFile.MAX_RECORD_SIZE_OVERRIDE = maxRecordSize;
+    }
+
     private void extractGPS(UserDataBox userData, Metadata metadata) {
         AppleGPSCoordinatesBox coordBox = getOrNull(userData, AppleGPSCoordinatesBox.class);
         if (coordBox == null) {
diff --git a/tika-parsers/src/test/java/org/apache/tika/parser/mp4/MP4ParserTest.java b/tika-parsers/src/test/java/org/apache/tika/parser/mp4/MP4ParserTest.java
index 4802395..4b12002 100644
--- a/tika-parsers/src/test/java/org/apache/tika/parser/mp4/MP4ParserTest.java
+++ b/tika-parsers/src/test/java/org/apache/tika/parser/mp4/MP4ParserTest.java
@@ -19,6 +19,7 @@ package org.apache.tika.parser.mp4;
 import static org.junit.Assert.assertEquals;
 
 import java.io.InputStream;
+import java.nio.file.Paths;
 
 import org.apache.tika.TikaTest;
 import org.apache.tika.io.TikaInputStream;