Package org.jaudiotagger.audio.mp4

Source Code of org.jaudiotagger.audio.mp4.Mp4TagReader

/*
* Entagged Audio Tag library
* Copyright (c) 2003-2005 Raphaël Slinckx <raphael@slinckx.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/
package org.jaudiotagger.audio.mp4;

import org.jaudiotagger.audio.exceptions.CannotReadException;
import org.jaudiotagger.audio.generic.Utils;
import org.jaudiotagger.audio.mp4.atom.Mp4BoxHeader;
import org.jaudiotagger.audio.mp4.atom.Mp4MetaBox;
import org.jaudiotagger.logging.ErrorMessage;
import org.jaudiotagger.tag.TagField;
import org.jaudiotagger.tag.mp4.Mp4FieldKey;
import org.jaudiotagger.tag.mp4.Mp4NonStandardFieldKey;
import org.jaudiotagger.tag.mp4.Mp4Tag;
import org.jaudiotagger.tag.mp4.atom.Mp4DataBox;
import org.jaudiotagger.tag.mp4.field.*;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.logging.Logger;

/**
* Reads metadata from mp4,
* <p/>
* <p>The metadata tags are usually held under the ilst atom as shown below<p/>
* <p>Valid Exceptions to the rule:</p>
* <p>Can be no udta atom with meta rooted immediately under moov instead<p/>
* <p>Can be no udta/meta atom at all<p/>
* <p/>
* <pre>
* |--- ftyp
* |--- moov
* |......|
* |......|----- mvdh
* |......|----- trak
* |......|----- udta
* |..............|
* |..............|-- meta
* |....................|
* |....................|-- hdlr
* |....................|-- ilst
* |.........................|
* |.........................|---- @nam (Optional for each metadatafield)
* |.........................|.......|-- data
* |.........................|....... ecetera
* |.........................|---- ---- (Optional for reverse dns field)
* |.................................|-- mean
* |.................................|-- name
* |.................................|-- data
* |.................................... ecetere
* |
* |--- mdat
* </pre
*/
public class Mp4TagReader {

    // Logger Object
    public static Logger logger = Logger.getLogger("org.jaudiotagger.tag.mp4");

    /*
     * The metadata is stored in the box under the hierachy moov.udta.meta.ilst
     *
     * There are gaps between these boxes

     */
    public Mp4Tag read(RandomAccessFile raf) throws CannotReadException, IOException {
        Mp4Tag tag = new Mp4Tag();

        //Get to the facts everything we are interested in is within the moov box, so just load data from file
        //once so no more file I/O needed
        Mp4BoxHeader moovHeader = Mp4BoxHeader.seekWithinLevel(raf, Mp4NotMetaFieldKey.MOOV.getFieldName());
        if (moovHeader == null) {
            throw new CannotReadException(ErrorMessage.MP4_FILE_NOT_CONTAINER.getMsg());
        }
        ByteBuffer moovBuffer = ByteBuffer.allocate(moovHeader.getLength() - Mp4BoxHeader.HEADER_LENGTH);
        raf.getChannel().read(moovBuffer);
        moovBuffer.rewind();

        //Level 2-Searching for "udta" within "moov"
        Mp4BoxHeader boxHeader = Mp4BoxHeader.seekWithinLevel(moovBuffer, Mp4NotMetaFieldKey.UDTA.getFieldName());
        if (boxHeader != null) {
            //Level 3-Searching for "meta" within udta
            boxHeader = Mp4BoxHeader.seekWithinLevel(moovBuffer, Mp4NotMetaFieldKey.META.getFieldName());
            if (boxHeader == null) {
                //logger.warning(ErrorMessage.MP4_FILE_HAS_NO_METADATA.getMsg());
                return tag;
            }
            Mp4MetaBox meta = new Mp4MetaBox(boxHeader, moovBuffer);
            meta.processData();

            //Level 4- Search for "ilst" within meta
            boxHeader = Mp4BoxHeader.seekWithinLevel(moovBuffer, Mp4NotMetaFieldKey.ILST.getFieldName());
            //This file does not actually contain a tag
            if (boxHeader == null) {
                //logger.warning(ErrorMessage.MP4_FILE_HAS_NO_METADATA.getMsg());
                return tag;
            }
        } else {
            //Level 2-Searching for "meta" not within udta
            boxHeader = Mp4BoxHeader.seekWithinLevel(moovBuffer, Mp4NotMetaFieldKey.META.getFieldName());
            if (boxHeader == null) {
                //logger.warning(ErrorMessage.MP4_FILE_HAS_NO_METADATA.getMsg());
                return tag;
            }
            Mp4MetaBox meta = new Mp4MetaBox(boxHeader, moovBuffer);
            meta.processData();


            //Level 3- Search for "ilst" within meta
            boxHeader = Mp4BoxHeader.seekWithinLevel(moovBuffer, Mp4NotMetaFieldKey.ILST.getFieldName());
            //This file does not actually contain a tag
            if (boxHeader == null) {
                //logger.warning(ErrorMessage.MP4_FILE_HAS_NO_METADATA.getMsg());
                return tag;
            }
        }

        //Size of metadata (exclude the size of the ilst parentHeader), take a slice starting at
        //metadata children to make things safer
        int length = boxHeader.getLength() - Mp4BoxHeader.HEADER_LENGTH;
        ByteBuffer metadataBuffer = moovBuffer.slice();
        //Datalength is longer are there boxes after ilst at this level?
        //logger.info("headerlengthsays:" + length + "datalength:" + metadataBuffer.limit());
        int read = 0;
        //logger.info("Started to read metadata fields at position is in metadata buffer:" + metadataBuffer.position());
        while (read < length) {
            //Read the boxHeader
            boxHeader.update(metadataBuffer);

            //Create the corresponding datafield from the id, and slice the buffer so position of main buffer
            //wont get affected
            //logger.info("Next position is at:" + metadataBuffer.position());
            createMp4Field(tag, boxHeader, metadataBuffer.slice());

            //Move position in buffer to the start of the next parentHeader
            metadataBuffer.position(metadataBuffer.position() + boxHeader.getDataLength());
            read += boxHeader.getLength();
        }
        return tag;
    }

    /**
     * Process the field and add to the tag
     * <p/>
     * Note:In the case of coverart MP4 holds all the coverart within individual dataitems all within
     * a single covr atom, we will add separate mp4field for each image.
     *
     * @param tag
     * @param header
     * @param raw
     * @return
     * @throws UnsupportedEncodingException
     */
    private void createMp4Field(Mp4Tag tag, Mp4BoxHeader header, ByteBuffer raw) throws UnsupportedEncodingException {
        //Reverse Dns Atom
        if (header.getId().equals(Mp4TagReverseDnsField.IDENTIFIER)) {
            //
            try {
                TagField field = new Mp4TagReverseDnsField(header, raw);
                tag.addField(field);
            } catch (Exception e) {
                //logger.warning(ErrorMessage.MP4_UNABLE_READ_REVERSE_DNS_FIELD.getMsg(e.getMessage()));
                TagField field = new Mp4TagRawBinaryField(header, raw);
                tag.addField(field);
            }
        }
        //Normal Parent with Data atom
        else {
            int currentPos = raw.position();
            boolean isDataIdentifier = Utils.getString(raw, Mp4BoxHeader.IDENTIFIER_POS, Mp4BoxHeader.IDENTIFIER_LENGTH, "ISO-8859-1").equals(Mp4DataBox.IDENTIFIER);
            raw.position(currentPos);
            if (isDataIdentifier) {
                //Need this to decide what type of Field to create
                int type = Utils.getIntBE(raw, Mp4DataBox.TYPE_POS_INCLUDING_HEADER, Mp4DataBox.TYPE_POS_INCLUDING_HEADER + Mp4DataBox.TYPE_LENGTH - 1);
                Mp4FieldType fieldType = Mp4FieldType.getFieldType(type);
                //logger.info("Box Type id:" + header.getId() + ":type:" + fieldType);

                //Special handling for some specific identifiers otherwise just base on class id
                if (header.getId().equals(Mp4FieldKey.TRACK.getFieldName())) {
                    TagField field = new Mp4TrackField(header.getId(), raw);
                    tag.addField(field);
                } else if (header.getId().equals(Mp4FieldKey.DISCNUMBER.getFieldName())) {
                    TagField field = new Mp4DiscNoField(header.getId(), raw);
                    tag.addField(field);
                } else if (header.getId().equals(Mp4FieldKey.GENRE.getFieldName())) {
                    TagField field = new Mp4GenreField(header.getId(), raw);
                    tag.addField(field);
                } else if (header.getId().equals(Mp4FieldKey.ARTWORK.getFieldName()) || Mp4FieldType.isCoverArtType(fieldType)) {
                    int processedDataSize = 0;
                    int imageCount = 0;
                    //The loop should run for each image (each data atom)
                    while (processedDataSize < header.getDataLength()) {
                        //There maybe a mixture of PNG and JPEG images so have to check type
                        //for each subimage (if there are more than one image)
                        if (imageCount > 0) {
                            type = Utils.getIntBE(raw, processedDataSize + Mp4DataBox.TYPE_POS_INCLUDING_HEADER,
                                    processedDataSize + Mp4DataBox.TYPE_POS_INCLUDING_HEADER + Mp4DataBox.TYPE_LENGTH - 1);
                            fieldType = Mp4FieldType.getFieldType(type);
                        }
                        Mp4TagCoverField field = new Mp4TagCoverField(raw, fieldType);
                        tag.addField(field);
                        processedDataSize += field.getDataAndHeaderSize();
                        imageCount++;
                    }
                } else if (fieldType == Mp4FieldType.TEXT) {
                    TagField field = new Mp4TagTextField(header.getId(), raw);
                    tag.addField(field);
                } else if (fieldType == Mp4FieldType.IMPLICIT) {
                    TagField field = new Mp4TagTextNumberField(header.getId(), raw);
                    tag.addField(field);
                } else if (fieldType == Mp4FieldType.INTEGER) {
                    TagField field = new Mp4TagByteField(header.getId(), raw);
                    tag.addField(field);
                } else {
                    boolean existingId = false;
                    for (Mp4FieldKey key : Mp4FieldKey.values()) {
                        if (key.getFieldName().equals(header.getId())) {
                            //The parentHeader is a known id but its field type is not one of the expected types so
                            //this field is invalid. i.e I received a file with the TMPO set to 15 (Oxf) when it should
                            //be 21 (ox15) so looks like somebody got their decimal and hex numbering confused
                            //So in this case best to ignore this field and just write a warning
                            existingId = true;
                            //logger.warning("Known Field:" + header.getId() + " with invalid field type of:" + type + " is ignored");
                            break;
                        }
                    }

                    //Unknown field id with unknown type so just create as binary
                    if (!existingId) {
                        //logger.warning("UnKnown Field:" + header.getId() + " with invalid field type of:" + type + " created as binary");
                        TagField field = new Mp4TagBinaryField(header.getId(), raw);
                        tag.addField(field);
                    }
                }
            }
            //Special Cases
            else {
                //MediaMonkey 3 CoverArt Attributes field, does not have data items so just
                //copy parent and child as is without modification
                if (header.getId().equals(Mp4NonStandardFieldKey.AAPR.getFieldName())) {
                    TagField field = new Mp4TagRawBinaryField(header, raw);
                    tag.addField(field);
                }
                //Default case
                else {
                    TagField field = new Mp4TagRawBinaryField(header, raw);
                    tag.addField(field);
                }
            }
        }

    }
}
TOP

Related Classes of org.jaudiotagger.audio.mp4.Mp4TagReader

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.