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/03 19:52:24 UTC

[tika] 01/02: TIKA-2792 -- upgrade dependency coordinates for MP4Parser

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 3239f24863d5980395a6951e02e42fc9defcb37e
Author: TALLISON <ta...@apache.org>
AuthorDate: Mon Dec 3 14:43:24 2018 -0500

    TIKA-2792 -- upgrade dependency coordinates for MP4Parser
---
 CHANGES.txt                                        |   3 +
 tika-parsers/pom.xml                               |   8 +-
 .../tika/parser/mp4/DirectFileReadDataSource.java  | 128 --------
 .../java/org/apache/tika/parser/mp4/MP4Parser.java | 361 ++++++++++-----------
 4 files changed, 189 insertions(+), 311 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index e33f0f5..1b1958b 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -7,6 +7,9 @@ Release 2.0.0 - ???
 
 Release 1.20 - ???
 
+   * Upgrade MP4Parser to newer dependency coordinates:
+     org.mp4parser:isoparser (TIKA-2792).
+
    * Upgrade to POI 4.0.1 (TIKA-2751).
 
    * Integrate/parameterize new angles handling in
diff --git a/tika-parsers/pom.xml b/tika-parsers/pom.xml
index b6bed3d..dace1b9 100644
--- a/tika-parsers/pom.xml
+++ b/tika-parsers/pom.xml
@@ -287,10 +287,14 @@
       <version>7.0</version>
     </dependency>
     <dependency>
-      <groupId>com.googlecode.mp4parser</groupId>
+      <!-- 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>
       <artifactId>isoparser</artifactId>
-      <version>1.1.22</version>
+      <version>1.9.36</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
deleted file mode 100644
index cde2548..0000000
--- a/tika-parsers/src/main/java/org/apache/tika/parser/mp4/DirectFileReadDataSource.java
+++ /dev/null
@@ -1,128 +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();
-    }
-
-
-}
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 7baae65..6fa3203 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,46 +16,7 @@
  */
 package org.apache.tika.parser.mp4;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.text.DecimalFormat;
-import java.text.NumberFormat;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
 
-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.tika.exception.TikaException;
 import org.apache.tika.io.TemporaryResources;
 import org.apache.tika.io.TikaInputStream;
@@ -68,9 +29,48 @@ 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;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
 /**
  * Parser for the MP4 media container format, as well as the older
  *  QuickTime format that MP4 is based on.
@@ -126,176 +126,175 @@ 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();
 
 
-                // 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());
+            // 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(Metadata.CREATION_DATE, 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));
+                // 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());
-                }
+                // 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
                 }
 
-                // All done
-                xhtml.endDocument();
+                // TODO Check for other kinds too
             }
+
+            // All done
+            xhtml.endDocument();
+
         } finally {
             tmp.dispose();
         }