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 2018/12/13 15:31:14 UTC

[tika] 02/02: TIKA-2792 -- revert 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

commit 8187f21aecf17d6796c36f2ef2f3d39ca6364ab0
Author: TALLISON <ta...@apache.org>
AuthorDate: Thu Dec 13 10:24:47 2018 -0500

    TIKA-2792 -- revert mp4parser dependency
---
 tika-parsers/pom.xml                               |   8 +-
 .../tika/parser/mp4/DirectFileReadDataSource.java  | 127 ++++++++
 .../java/org/apache/tika/parser/mp4/MP4Parser.java | 337 +++++++++++----------
 3 files changed, 300 insertions(+), 172 deletions(-)

diff --git a/tika-parsers/pom.xml b/tika-parsers/pom.xml
index 230ff1b..248aea9 100644
--- a/tika-parsers/pom.xml
+++ b/tika-parsers/pom.xml
@@ -292,14 +292,10 @@
       <version>7.0</version>
     </dependency>
     <dependency>
-      <!-- 1.9.37 was built with jdk9 and
-           causes: Method rewind()Ljava/nio/ByteBuffer;
-           does not exist in class java.nio.ByteBuffer -->
-      <groupId>org.mp4parser</groupId>
+      <groupId>com.googlecode.mp4parser</groupId>
       <artifactId>isoparser</artifactId>
-      <version>1.9.36</version>
+      <version>1.1.22</version>
     </dependency>
-
     <dependency>
       <groupId>com.drewnoakes</groupId>
       <artifactId>metadata-extractor</artifactId>
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
new file mode 100644
index 0000000..698a106
--- /dev/null
+++ b/tika-parsers/src/main/java/org/apache/tika/parser/mp4/DirectFileReadDataSource.java
@@ -0,0 +1,127 @@
+/*
+ * 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 c483433..afa63b8 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,6 +16,36 @@
  */
 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.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.poi.ss.formula.functions.T;
 import org.apache.tika.exception.TikaException;
 import org.apache.tika.io.TemporaryResources;
 import org.apache.tika.io.TikaInputStream;
@@ -28,33 +58,6 @@ 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.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;
 
@@ -73,7 +76,7 @@ import java.util.Set;
 /**
  * Parser for the MP4 media container format, as well as the older
  *  QuickTime format that MP4 is based on.
- *
+ * 
  * This uses the MP4Parser project from http://code.google.com/p/mp4parser/
  *  to do the underlying parsing
  */
@@ -81,12 +84,12 @@ public class MP4Parser extends AbstractParser {
     /** Serial version UID */
     private static final long serialVersionUID = 84011216792285L;
     /** TODO Replace this with a 2dp Duration Property Converter */
-    private static final DecimalFormat DURATION_FORMAT =
-            (DecimalFormat)NumberFormat.getNumberInstance(Locale.ROOT);
+    private static final DecimalFormat DURATION_FORMAT = 
+            (DecimalFormat)NumberFormat.getNumberInstance(Locale.ROOT); 
     static {
         DURATION_FORMAT.applyPattern("0.0#");
     }
-
+    
     // Ensure this stays in Sync with the entries in tika-mimetypes.xml
     private static final Map<MediaType,List<String>> typesMap = new HashMap<MediaType, List<String>>();
     static {
@@ -125,41 +128,42 @@ public class MP4Parser extends AbstractParser {
         TemporaryResources tmp = new TemporaryResources();
         TikaInputStream tstream = TikaInputStream.get(stream, tmp);
 
-        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;
+        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;
+                        }
                     }
-                }
-                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());
+                    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");
                 }
-            } 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();
 
 
             // Pull out some information from the header box
@@ -169,131 +173,132 @@ public class MP4Parser extends AbstractParser {
                 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());
-            }
+                    // 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());
+                }
 
-            // 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();
-                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 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();
+                    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) {
-                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());
-                    }
+                // Get metadata from the User Data Box
+                UserDataBox userData = getOrNull(moov, UserDataBox.class);
+                if (userData != null) {
+                    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
-                    }
+                        // 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());
-                    }
+                        // 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());
-                    }
+                        // 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);
+                        // 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());
-                    }
+                        // 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());
+                        // As text
+                        for (Box box : apple.getBoxes()) {
+                            if (box instanceof Utf8AppleDataBox) {
+                                xhtml.element("p", ((Utf8AppleDataBox) box).getValue());
+                            }
                         }
                     }
-                }
 
-                // TODO Check for other kinds too
-            }
+                    // TODO Check for other kinds too
+                }
 
-            // All done
-            xhtml.endDocument();
 
+                // All done
+                xhtml.endDocument();
+            }
         } finally {
             tmp.dispose();
         }