Search in sources :

Example 1 with DefaultMutableTreeNode

use of org.jaudiotagger.utils.tree.DefaultMutableTreeNode in project MusicDNA by harjot-oberai.

the class Mp4AtomTree method buildTree.

/**
 * Build a tree of the atoms in the file
 *
 * @param raf
 * @param closeExit false to keep randomfileacces open, only used when randomaccessfile already being used
 * @return
 * @throws java.io.IOException
 * @throws org.jaudiotagger.audio.exceptions.CannotReadException
 */
public DefaultTreeModel buildTree(RandomAccessFile raf, boolean closeExit) throws IOException, CannotReadException {
    FileChannel fc = null;
    try {
        fc = raf.getChannel();
        // make sure at start of file
        fc.position(0);
        // Build up map of nodes
        rootNode = new DefaultMutableTreeNode();
        dataTree = new DefaultTreeModel(rootNode);
        // Iterate though all the top level Nodes
        ByteBuffer headerBuffer = ByteBuffer.allocate(Mp4BoxHeader.HEADER_LENGTH);
        while (fc.position() < fc.size()) {
            Mp4BoxHeader boxHeader = new Mp4BoxHeader();
            headerBuffer.clear();
            fc.read(headerBuffer);
            headerBuffer.rewind();
            try {
                boxHeader.update(headerBuffer);
            } catch (NullBoxIdException ne) {
                // If we only get this error after all the expected data has been found we allow it
                if (moovNode != null & mdatNode != null) {
                    NullPadding np = new NullPadding(fc.position() - Mp4BoxHeader.HEADER_LENGTH, fc.size());
                    DefaultMutableTreeNode trailingPaddingNode = new DefaultMutableTreeNode(np);
                    rootNode.add(trailingPaddingNode);
                    logger.warning(ErrorMessage.NULL_PADDING_FOUND_AT_END_OF_MP4.getMsg(np.getFilePos()));
                    break;
                } else {
                    // File appears invalid
                    throw ne;
                }
            }
            boxHeader.setFilePos(fc.position() - Mp4BoxHeader.HEADER_LENGTH);
            DefaultMutableTreeNode newAtom = new DefaultMutableTreeNode(boxHeader);
            // Go down moov
            if (boxHeader.getId().equals(Mp4AtomIdentifier.MOOV.getFieldName())) {
                // and finish
                if (moovNode != null & mdatNode != null) {
                    logger.warning(ErrorMessage.ADDITIONAL_MOOV_ATOM_AT_END_OF_MP4.getMsg(fc.position() - Mp4BoxHeader.HEADER_LENGTH));
                    break;
                }
                moovNode = newAtom;
                moovHeader = boxHeader;
                long filePosStart = fc.position();
                moovBuffer = ByteBuffer.allocate(boxHeader.getDataLength());
                int bytesRead = fc.read(moovBuffer);
                // If Moov atom is incomplete we are not going to be able to read this file properly
                if (bytesRead < boxHeader.getDataLength()) {
                    String msg = ErrorMessage.ATOM_LENGTH_LARGER_THAN_DATA.getMsg(boxHeader.getId(), boxHeader.getDataLength(), bytesRead);
                    throw new CannotReadException(msg);
                }
                moovBuffer.rewind();
                buildChildrenOfNode(moovBuffer, newAtom);
                fc.position(filePosStart);
            } else if (boxHeader.getId().equals(Mp4AtomIdentifier.FREE.getFieldName())) {
                // Might be multiple in different locations
                freeNodes.add(newAtom);
            } else if (boxHeader.getId().equals(Mp4AtomIdentifier.MDAT.getFieldName())) {
                // mdatNode always points to the last mDatNode, normally there is just one mdatnode but do have
                // a valid example of multiple mdatnode
                // if(mdatNode!=null)
                // {
                // throw new CannotReadException(ErrorMessage.MP4_FILE_CONTAINS_MULTIPLE_DATA_ATOMS.getMsg());
                // }
                mdatNode = newAtom;
                mdatNodes.add(newAtom);
            }
            rootNode.add(newAtom);
            fc.position(fc.position() + boxHeader.getDataLength());
        }
        return dataTree;
    } finally {
        // now rather than later when try and write to it.
        if (mdatNode == null) {
            throw new CannotReadException(ErrorMessage.MP4_CANNOT_FIND_AUDIO.getMsg());
        }
        if (closeExit) {
            fc.close();
        }
    }
}
Also used : Mp4BoxHeader(org.jaudiotagger.audio.mp4.atom.Mp4BoxHeader) DefaultMutableTreeNode(org.jaudiotagger.utils.tree.DefaultMutableTreeNode) CannotReadException(org.jaudiotagger.audio.exceptions.CannotReadException) FileChannel(java.nio.channels.FileChannel) DefaultTreeModel(org.jaudiotagger.utils.tree.DefaultTreeModel) NullBoxIdException(org.jaudiotagger.audio.exceptions.NullBoxIdException) ByteBuffer(java.nio.ByteBuffer) NullPadding(org.jaudiotagger.audio.mp4.atom.NullPadding)

Example 2 with DefaultMutableTreeNode

use of org.jaudiotagger.utils.tree.DefaultMutableTreeNode in project MusicDNA by harjot-oberai.

the class Mp4AtomTree method printAtomTree.

/**
 * Display atom tree
 */
@SuppressWarnings("unchecked")
public void printAtomTree() {
    Enumeration<DefaultMutableTreeNode> e = rootNode.preorderEnumeration();
    DefaultMutableTreeNode nextNode;
    while (e.hasMoreElements()) {
        nextNode = e.nextElement();
        Mp4BoxHeader header = (Mp4BoxHeader) nextNode.getUserObject();
        if (header != null) {
            String tabbing = "";
            for (int i = 1; i < nextNode.getLevel(); i++) {
                tabbing += "\t";
            }
            if (header instanceof NullPadding) {
                System.out.println(tabbing + "Null pad " + " @ " + header.getFilePos() + " of size:" + header.getLength() + " ,ends @ " + (header.getFilePos() + header.getLength()));
            } else {
                System.out.println(tabbing + "Atom " + header.getId() + " @ " + header.getFilePos() + " of size:" + header.getLength() + " ,ends @ " + (header.getFilePos() + header.getLength()));
            }
        }
    }
}
Also used : Mp4BoxHeader(org.jaudiotagger.audio.mp4.atom.Mp4BoxHeader) DefaultMutableTreeNode(org.jaudiotagger.utils.tree.DefaultMutableTreeNode) NullPadding(org.jaudiotagger.audio.mp4.atom.NullPadding)

Example 3 with DefaultMutableTreeNode

use of org.jaudiotagger.utils.tree.DefaultMutableTreeNode in project MusicDNA by harjot-oberai.

the class Mp4AtomTree method buildChildrenOfNode.

/**
 * @param moovBuffer
 * @param parentNode
 * @throws IOException
 * @throws CannotReadException
 */
public void buildChildrenOfNode(ByteBuffer moovBuffer, DefaultMutableTreeNode parentNode) throws IOException, CannotReadException {
    Mp4BoxHeader boxHeader;
    // Preprocessing for nodes that contain data before their children atoms
    Mp4BoxHeader parentBoxHeader = (Mp4BoxHeader) parentNode.getUserObject();
    // We set the buffers position back to this after processing the children
    int justAfterHeaderPos = moovBuffer.position();
    // Preprocessing for meta that normally contains 4 data bytes, but doesn'timer where found under track or tags atom
    if (parentBoxHeader.getId().equals(Mp4AtomIdentifier.META.getFieldName())) {
        Mp4MetaBox meta = new Mp4MetaBox(parentBoxHeader, moovBuffer);
        meta.processData();
        try {
            boxHeader = new Mp4BoxHeader(moovBuffer);
        } catch (NullBoxIdException nbe) {
            // It might be that the meta box didn'timer actually have any additional data after it so we adjust the buffer
            // to be immediately after metabox and code can retry
            moovBuffer.position(moovBuffer.position() - Mp4MetaBox.FLAGS_LENGTH);
        } finally {
            // Skip back last header cos this was only a test
            moovBuffer.position(moovBuffer.position() - Mp4BoxHeader.HEADER_LENGTH);
        }
    }
    // Defines where to start looking for the first child node
    int startPos = moovBuffer.position();
    while (moovBuffer.position() < ((startPos + parentBoxHeader.getDataLength()) - Mp4BoxHeader.HEADER_LENGTH)) {
        boxHeader = new Mp4BoxHeader(moovBuffer);
        if (boxHeader != null) {
            boxHeader.setFilePos(moovHeader.getFilePos() + moovBuffer.position());
            logger.finest("Atom " + boxHeader.getId() + " @ " + boxHeader.getFilePos() + " of size:" + boxHeader.getLength() + " ,ends @ " + (boxHeader.getFilePos() + boxHeader.getLength()));
            DefaultMutableTreeNode newAtom = new DefaultMutableTreeNode(boxHeader);
            parentNode.add(newAtom);
            if (boxHeader.getId().equals(Mp4AtomIdentifier.UDTA.getFieldName())) {
                udtaNode = newAtom;
            } else // only interested in metaNode that is child of udta node
            if (boxHeader.getId().equals(Mp4AtomIdentifier.META.getFieldName()) && parentBoxHeader.getId().equals(Mp4AtomIdentifier.UDTA.getFieldName())) {
                metaNode = newAtom;
            } else if (boxHeader.getId().equals(Mp4AtomIdentifier.HDLR.getFieldName()) && parentBoxHeader.getId().equals(Mp4AtomIdentifier.META.getFieldName())) {
                hdlrWithinMetaNode = newAtom;
            } else if (boxHeader.getId().equals(Mp4AtomIdentifier.HDLR.getFieldName())) {
                hdlrWithinMdiaNode = newAtom;
            } else if (boxHeader.getId().equals(Mp4AtomIdentifier.TAGS.getFieldName())) {
                tagsNode = newAtom;
            } else if (boxHeader.getId().equals(Mp4AtomIdentifier.STCO.getFieldName())) {
                if (stco == null) {
                    stco = new Mp4StcoBox(boxHeader, moovBuffer);
                    stcoNode = newAtom;
                }
            } else if (boxHeader.getId().equals(Mp4AtomIdentifier.ILST.getFieldName())) {
                DefaultMutableTreeNode parent = (DefaultMutableTreeNode) parentNode.getParent();
                if (parent != null) {
                    Mp4BoxHeader parentsParent = (Mp4BoxHeader) (parent).getUserObject();
                    if (parentsParent != null) {
                        if (parentBoxHeader.getId().equals(Mp4AtomIdentifier.META.getFieldName()) && parentsParent.getId().equals(Mp4AtomIdentifier.UDTA.getFieldName())) {
                            ilstNode = newAtom;
                        }
                    }
                }
            } else if (boxHeader.getId().equals(Mp4AtomIdentifier.FREE.getFieldName())) {
                // Might be multiple in different locations
                freeNodes.add(newAtom);
            } else if (boxHeader.getId().equals(Mp4AtomIdentifier.TRAK.getFieldName())) {
                // Might be multiple in different locations, although only one should be audio track
                trakNodes.add(newAtom);
            }
            // For these atoms iterate down to build their children
            if ((boxHeader.getId().equals(Mp4AtomIdentifier.TRAK.getFieldName())) || (boxHeader.getId().equals(Mp4AtomIdentifier.MDIA.getFieldName())) || (boxHeader.getId().equals(Mp4AtomIdentifier.MINF.getFieldName())) || (boxHeader.getId().equals(Mp4AtomIdentifier.STBL.getFieldName())) || (boxHeader.getId().equals(Mp4AtomIdentifier.UDTA.getFieldName())) || (boxHeader.getId().equals(Mp4AtomIdentifier.META.getFieldName())) || (boxHeader.getId().equals(Mp4AtomIdentifier.ILST.getFieldName()))) {
                buildChildrenOfNode(moovBuffer, newAtom);
            }
            // Now  adjust buffer for the next atom header at this level
            moovBuffer.position(moovBuffer.position() + boxHeader.getDataLength());
        }
    }
    moovBuffer.position(justAfterHeaderPos);
}
Also used : Mp4BoxHeader(org.jaudiotagger.audio.mp4.atom.Mp4BoxHeader) DefaultMutableTreeNode(org.jaudiotagger.utils.tree.DefaultMutableTreeNode) Mp4MetaBox(org.jaudiotagger.audio.mp4.atom.Mp4MetaBox) NullBoxIdException(org.jaudiotagger.audio.exceptions.NullBoxIdException) Mp4StcoBox(org.jaudiotagger.audio.mp4.atom.Mp4StcoBox)

Example 4 with DefaultMutableTreeNode

use of org.jaudiotagger.utils.tree.DefaultMutableTreeNode in project MusicDNA by harjot-oberai.

the class Mp4TagWriter method getMetaLevelFreeAtomSize.

/**
 * Determine the size of the free atom immediately after ilst atom at the same level (if any), we can use this if
 * ilst needs to grow or shrink because of more less metadata
 *
 * @param atomTree
 * @return
 */
private int getMetaLevelFreeAtomSize(Mp4AtomTree atomTree) {
    // Level 4 - Free
    int oldMetaLevelFreeAtomSize;
    oldMetaLevelFreeAtomSize = 0;
    for (DefaultMutableTreeNode freeNode : atomTree.getFreeNodes()) {
        DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) freeNode.getParent();
        DefaultMutableTreeNode brotherNode = freeNode.getPreviousSibling();
        if (!parentNode.isRoot()) {
            Mp4BoxHeader parentHeader = ((Mp4BoxHeader) parentNode.getUserObject());
            Mp4BoxHeader freeHeader = ((Mp4BoxHeader) freeNode.getUserObject());
            // We are only interested in free atoms at this level if they come after the ilst node
            if (brotherNode != null) {
                Mp4BoxHeader brotherHeader = ((Mp4BoxHeader) brotherNode.getUserObject());
                if (parentHeader.getId().equals(Mp4AtomIdentifier.META.getFieldName()) && brotherHeader.getId().equals(Mp4AtomIdentifier.ILST.getFieldName())) {
                    oldMetaLevelFreeAtomSize = freeHeader.getLength();
                    break;
                }
            }
        }
    }
    return oldMetaLevelFreeAtomSize;
}
Also used : DefaultMutableTreeNode(org.jaudiotagger.utils.tree.DefaultMutableTreeNode)

Example 5 with DefaultMutableTreeNode

use of org.jaudiotagger.utils.tree.DefaultMutableTreeNode in project MusicDNA by harjot-oberai.

the class Mp4TagWriter method write.

/**
 * Write tag to rafTemp file
 *
 * @param tag     tag data
 * @param raf     current file
 * @param rafTemp temporary file for writing
 * @throws CannotWriteException
 * @throws IOException
 */
public void write(Tag tag, RandomAccessFile raf, RandomAccessFile rafTemp) throws CannotWriteException, IOException {
    logger.config("Started writing tag data");
    // Read Channel for reading from old file
    FileChannel fileReadChannel = raf.getChannel();
    // Write channel for writing to new file
    FileChannel fileWriteChannel = rafTemp.getChannel();
    // TODO we shouldn'timer need all these variables, and some are very badly named - used by new and old methods
    int oldIlstSize = 0;
    int relativeIlstposition;
    int startIlstWithinFile;
    int newIlstSize;
    int oldMetaLevelFreeAtomSize;
    int topLevelFreePosition;
    int topLevelFreeSize;
    long endOfMoov = 0;
    // Found top level free atom that comes after moov and before mdat, (also true if no free atom ?)
    boolean topLevelFreeAtomComesBeforeMdatAtomAndAfterMetadata;
    // Found top level free atom that comes between ftyp and moov
    boolean topLevelFreeAtomComesBeforeMdatAndMetadata;
    Mp4BoxHeader topLevelFreeHeader;
    Mp4AtomTree atomTree;
    // Build AtomTree
    try {
        atomTree = new Mp4AtomTree(raf, false);
    } catch (CannotReadException cre) {
        throw new CannotWriteException(cre.getMessage());
    }
    Mp4BoxHeader mdatHeader = atomTree.getBoxHeader(atomTree.getMdatNode());
    // Unable to find audio so no chance of saving any changes
    if (mdatHeader == null) {
        throw new CannotWriteException(ErrorMessage.MP4_CHANGES_TO_FILE_FAILED_CANNOT_FIND_AUDIO.getMsg());
    }
    // Go through every field constructing the data that will appear starting from ilst box
    ByteBuffer rawIlstData = tc.convert(tag);
    rawIlstData.rewind();
    newIlstSize = rawIlstData.limit();
    // Moov Box header
    Mp4BoxHeader moovHeader = atomTree.getBoxHeader(atomTree.getMoovNode());
    long positionWithinFileAfterFindingMoovHeader = moovHeader.getFilePos() + Mp4BoxHeader.HEADER_LENGTH;
    endOfMoov = moovHeader.getFilePos() + moovHeader.getLength();
    Mp4StcoBox stco = atomTree.getStco();
    Mp4BoxHeader ilstHeader = atomTree.getBoxHeader(atomTree.getIlstNode());
    Mp4BoxHeader udtaHeader = atomTree.getBoxHeader(atomTree.getUdtaNode());
    Mp4BoxHeader metaHeader = atomTree.getBoxHeader(atomTree.getMetaNode());
    Mp4BoxHeader hdlrMetaHeader = atomTree.getBoxHeader(atomTree.getHdlrWithinMetaNode());
    Mp4BoxHeader tagsHeader = atomTree.getBoxHeader(atomTree.getTagsNode());
    Mp4BoxHeader trakHeader = atomTree.getBoxHeader(atomTree.getTrakNodes().get(0));
    ByteBuffer moovBuffer = atomTree.getMoovBuffer();
    // Udta
    if (udtaHeader != null) {
        // Meta
        if (metaHeader != null) {
            // ilst - record where ilst is,and where it ends
            if (ilstHeader != null) {
                oldIlstSize = ilstHeader.getLength();
                // Relative means relative to moov buffer after moov header
                startIlstWithinFile = (int) ilstHeader.getFilePos();
                relativeIlstposition = (int) (startIlstWithinFile - (moovHeader.getFilePos() + Mp4BoxHeader.HEADER_LENGTH));
            } else {
                // Place ilst immediately after existing hdlr atom
                if (hdlrMetaHeader != null) {
                    startIlstWithinFile = (int) hdlrMetaHeader.getFilePos() + hdlrMetaHeader.getLength();
                    relativeIlstposition = (int) (startIlstWithinFile - (moovHeader.getFilePos() + Mp4BoxHeader.HEADER_LENGTH));
                } else // Place ilst after data fields in meta atom
                // TODO Should we create a hdlr atom
                {
                    startIlstWithinFile = (int) metaHeader.getFilePos() + Mp4BoxHeader.HEADER_LENGTH + Mp4MetaBox.FLAGS_LENGTH;
                    relativeIlstposition = (int) ((startIlstWithinFile) - (moovHeader.getFilePos() + Mp4BoxHeader.HEADER_LENGTH));
                }
            }
        } else {
            // There no ilst or meta header so we set to position where it would be if it existed
            relativeIlstposition = moovHeader.getLength() - Mp4BoxHeader.HEADER_LENGTH;
            startIlstWithinFile = (int) (moovHeader.getFilePos() + moovHeader.getLength());
        }
    } else // There no udta header so we are going to create a new structure, but we have to be aware that there might be
    // an existing meta box structure in which case we preserve it but with our new structure before it.
    {
        // Create new structure just after the end of the trak atom
        if (metaHeader != null) {
            startIlstWithinFile = (int) trakHeader.getFilePos() + trakHeader.getLength();
            relativeIlstposition = (int) (startIlstWithinFile - (moovHeader.getFilePos() + Mp4BoxHeader.HEADER_LENGTH));
        } else {
            // There no udta,ilst or meta header so we set to position where it would be if it existed
            relativeIlstposition = moovHeader.getLength() - Mp4BoxHeader.HEADER_LENGTH;
            startIlstWithinFile = (int) (moovHeader.getFilePos() + moovHeader.getLength());
        }
    }
    // Find size of Level-4 Free atom (if any) immediately after ilst atom
    oldMetaLevelFreeAtomSize = getMetaLevelFreeAtomSize(atomTree);
    // Level-1 free atom
    topLevelFreePosition = 0;
    topLevelFreeSize = 0;
    topLevelFreeAtomComesBeforeMdatAtomAndAfterMetadata = true;
    topLevelFreeAtomComesBeforeMdatAndMetadata = false;
    for (DefaultMutableTreeNode freeNode : atomTree.getFreeNodes()) {
        DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) freeNode.getParent();
        if (parentNode.isRoot()) {
            topLevelFreeHeader = ((Mp4BoxHeader) freeNode.getUserObject());
            topLevelFreeSize = topLevelFreeHeader.getLength();
            topLevelFreePosition = (int) topLevelFreeHeader.getFilePos();
            break;
        }
    }
    if (topLevelFreeSize > 0) {
        if (topLevelFreePosition > mdatHeader.getFilePos()) {
            topLevelFreeAtomComesBeforeMdatAtomAndAfterMetadata = false;
        } else if (topLevelFreePosition < moovHeader.getFilePos()) {
            topLevelFreeAtomComesBeforeMdatAtomAndAfterMetadata = false;
            topLevelFreeAtomComesBeforeMdatAndMetadata = true;
        }
    } else {
        topLevelFreePosition = (int) mdatHeader.getFilePos();
    }
    logger.config("Read header successfully ready for writing");
    // create a new file identical to first file but with replaced metadata
    if (oldIlstSize == newIlstSize) {
        logger.config("Writing:Option 1:Same Size");
        writeMetadataSameSize(rawIlstData, oldIlstSize, startIlstWithinFile, fileReadChannel, fileWriteChannel, tagsHeader);
    } else // no other changes necessary and total file size remains the same
    if (oldIlstSize > newIlstSize) {
        // after ilst as a child of meta
        if (oldMetaLevelFreeAtomSize > 0) {
            logger.config("Writing:Option 2:Smaller Size have free atom:" + oldIlstSize + ":" + newIlstSize);
            writeDataUptoIncludingIlst(fileReadChannel, fileWriteChannel, oldIlstSize, startIlstWithinFile, rawIlstData);
            // Write the modified free atom that comes after ilst
            int newFreeSize = oldMetaLevelFreeAtomSize + (oldIlstSize - newIlstSize);
            Mp4FreeBox newFreeBox = new Mp4FreeBox(newFreeSize - Mp4BoxHeader.HEADER_LENGTH);
            fileWriteChannel.write(newFreeBox.getHeader().getHeaderData());
            fileWriteChannel.write(newFreeBox.getData());
            // Skip over the read channel old free atom
            fileReadChannel.position(fileReadChannel.position() + oldMetaLevelFreeAtomSize);
            writeDataAfterIlst(fileReadChannel, fileWriteChannel, tagsHeader);
        } else // No free atom we need to create a new one or adjust top level free atom
        {
            int newFreeSize = (oldIlstSize - newIlstSize) - Mp4BoxHeader.HEADER_LENGTH;
            // into account size of new header in calculating size of box
            if (newFreeSize > 0) {
                logger.config("Writing:Option 3:Smaller Size can create free atom");
                writeDataUptoIncludingIlst(fileReadChannel, fileWriteChannel, oldIlstSize, startIlstWithinFile, rawIlstData);
                // Create new free box
                Mp4FreeBox newFreeBox = new Mp4FreeBox(newFreeSize);
                fileWriteChannel.write(newFreeBox.getHeader().getHeaderData());
                fileWriteChannel.write(newFreeBox.getData());
                writeDataAfterIlst(fileReadChannel, fileWriteChannel, tagsHeader);
            } else // Ok everything in this bit of tree has to be recalculated because eight or less bytes smaller
            {
                logger.config("Writing:Option 4:Smaller Size <=8 cannot create free atoms");
                // Size will be this amount smaller
                int sizeReducedBy = oldIlstSize - newIlstSize;
                // Write stuff before Moov (ftyp)
                fileReadChannel.position(0);
                fileWriteChannel.transferFrom(fileReadChannel, 0, moovHeader.getFilePos());
                fileWriteChannel.position(moovHeader.getFilePos());
                // unless mdat is at start of file
                if (mdatHeader.getFilePos() > moovHeader.getFilePos()) {
                    stco.adjustOffsets(-sizeReducedBy);
                }
                // Edit and rewrite the Moov,Udta and Meta header in moov buffer
                adjustSizeOfMoovHeader(moovHeader, moovBuffer, -sizeReducedBy, udtaHeader, metaHeader);
                fileWriteChannel.write(moovHeader.getHeaderData());
                moovBuffer.rewind();
                moovBuffer.limit(relativeIlstposition);
                fileWriteChannel.write(moovBuffer);
                // Now write ilst data
                fileWriteChannel.write(rawIlstData);
                fileReadChannel.position(startIlstWithinFile + oldIlstSize);
                writeDataAfterIlst(fileReadChannel, fileWriteChannel, tagsHeader);
            }
        }
    } else // Size of metadata has increased, the most complex situation, more atoms affected
    {
        int additionalSpaceRequiredForMetadata = newIlstSize - oldIlstSize;
        // solution where can fit in data, but cant fit in free atom header
        if (additionalSpaceRequiredForMetadata <= (oldMetaLevelFreeAtomSize - Mp4BoxHeader.HEADER_LENGTH)) {
            int newFreeSize = oldMetaLevelFreeAtomSize - (additionalSpaceRequiredForMetadata);
            logger.config("Writing:Option 5;Larger Size can use meta free atom need extra:" + newFreeSize + "bytes");
            writeDataUptoIncludingIlst(fileReadChannel, fileWriteChannel, oldIlstSize, startIlstWithinFile, rawIlstData);
            // Create an amended smaller freeBaos atom and write it to file
            Mp4FreeBox newFreeBox = new Mp4FreeBox(newFreeSize - Mp4BoxHeader.HEADER_LENGTH);
            fileWriteChannel.write(newFreeBox.getHeader().getHeaderData());
            fileWriteChannel.write(newFreeBox.getData());
            // Skip over the read channel old free atom
            fileReadChannel.position(fileReadChannel.position() + oldMetaLevelFreeAtomSize);
            writeDataAfterIlst(fileReadChannel, fileWriteChannel, tagsHeader);
        } else // There is not enough padding in the metadata free atom anyway
        // Size meta needs to be increased by (if not writing a free atom)
        // Special Case this could actually be negative (upto -8)if is actually enough space but would
        // not be able to write free atom properly, it doesn'timer matter the parent atoms would still
        // need their sizes adjusted.
        {
            int additionalMetaSizeThatWontFitWithinMetaAtom = additionalSpaceRequiredForMetadata - (oldMetaLevelFreeAtomSize);
            // Write stuff before Moov (ftyp)
            fileReadChannel.position(0);
            fileWriteChannel.transferFrom(fileReadChannel, 0, positionWithinFileAfterFindingMoovHeader - Mp4BoxHeader.HEADER_LENGTH);
            fileWriteChannel.position(positionWithinFileAfterFindingMoovHeader - Mp4BoxHeader.HEADER_LENGTH);
            if (udtaHeader == null) {
                logger.config("Writing:Option 5.1;No udta atom");
                Mp4HdlrBox hdlrBox = Mp4HdlrBox.createiTunesStyleHdlrBox();
                Mp4MetaBox metaBox = Mp4MetaBox.createiTunesStyleMetaBox(hdlrBox.getHeader().getLength() + rawIlstData.limit());
                udtaHeader = new Mp4BoxHeader(Mp4AtomIdentifier.UDTA.getFieldName());
                udtaHeader.setLength(Mp4BoxHeader.HEADER_LENGTH + metaBox.getHeader().getLength());
                additionalMetaSizeThatWontFitWithinMetaAtom = additionalMetaSizeThatWontFitWithinMetaAtom + (udtaHeader.getLength() - rawIlstData.limit());
                // or special case of matching exactly the free atom plus header)
                if ((!topLevelFreeAtomComesBeforeMdatAtomAndAfterMetadata) || ((topLevelFreeSize - Mp4BoxHeader.HEADER_LENGTH < additionalMetaSizeThatWontFitWithinMetaAtom) && (topLevelFreeSize != additionalMetaSizeThatWontFitWithinMetaAtom))) {
                    // by the amount mdat is going to be shifted
                    if (mdatHeader.getFilePos() > moovHeader.getFilePos()) {
                        logger.config("Adjusting Offsets");
                        stco.adjustOffsets(additionalMetaSizeThatWontFitWithinMetaAtom);
                    }
                }
                // Edit and rewrite the Moov header
                moovHeader.setLength(moovHeader.getLength() + additionalMetaSizeThatWontFitWithinMetaAtom);
                fileWriteChannel.write(moovHeader.getHeaderData());
                moovBuffer.rewind();
                moovBuffer.limit(relativeIlstposition);
                fileWriteChannel.write(moovBuffer);
                // Write new atoms required for holding metadata in itunes format
                fileWriteChannel.write(udtaHeader.getHeaderData());
                fileWriteChannel.write(metaBox.getHeader().getHeaderData());
                fileWriteChannel.write(metaBox.getData());
                fileWriteChannel.write(hdlrBox.getHeader().getHeaderData());
                fileWriteChannel.write(hdlrBox.getData());
            } else if (metaHeader == null) {
                // #291:In this case we throwaway editing udta header and create a new one, would be beter if we coul
                // keep the info but rather difficult.
                logger.config("Writing:Option 5.2;No meta atom");
                int oldUdtaHeaderLength = udtaHeader.getLength();
                Mp4HdlrBox hdlrBox = Mp4HdlrBox.createiTunesStyleHdlrBox();
                Mp4MetaBox metaBox = Mp4MetaBox.createiTunesStyleMetaBox(hdlrBox.getHeader().getLength() + rawIlstData.limit());
                udtaHeader = new Mp4BoxHeader(Mp4AtomIdentifier.UDTA.getFieldName());
                udtaHeader.setLength(Mp4BoxHeader.HEADER_LENGTH + metaBox.getHeader().getLength());
                additionalMetaSizeThatWontFitWithinMetaAtom = additionalMetaSizeThatWontFitWithinMetaAtom + (udtaHeader.getLength() - rawIlstData.limit());
                // or special case of matching exactly the free atom plus header)
                if ((!topLevelFreeAtomComesBeforeMdatAtomAndAfterMetadata) || ((topLevelFreeSize - Mp4BoxHeader.HEADER_LENGTH < additionalMetaSizeThatWontFitWithinMetaAtom) && (topLevelFreeSize != additionalMetaSizeThatWontFitWithinMetaAtom))) {
                    // by the amount mdat is going to be shifted
                    if (mdatHeader.getFilePos() > moovHeader.getFilePos()) {
                        logger.config("Adjusting Offsets");
                        stco.adjustOffsets(additionalMetaSizeThatWontFitWithinMetaAtom);
                    }
                }
                // Edit and rewrite the Moov header
                moovHeader.setLength(moovHeader.getLength() - oldUdtaHeaderLength + additionalMetaSizeThatWontFitWithinMetaAtom);
                fileWriteChannel.write(moovHeader.getHeaderData());
                moovBuffer.rewind();
                moovBuffer.limit(relativeIlstposition - oldUdtaHeaderLength);
                fileWriteChannel.write(moovBuffer);
                // Write new atoms required for holding metadata in itunes format
                fileWriteChannel.write(udtaHeader.getHeaderData());
                fileWriteChannel.write(metaBox.getHeader().getHeaderData());
                fileWriteChannel.write(metaBox.getData());
                fileWriteChannel.write(hdlrBox.getHeader().getHeaderData());
                fileWriteChannel.write(hdlrBox.getData());
            } else {
                logger.config("Writing:Option 5.3;udta atom exists");
                // or special case of matching exactly the free atom plus header)
                if ((!topLevelFreeAtomComesBeforeMdatAtomAndAfterMetadata) || ((topLevelFreeSize - Mp4BoxHeader.HEADER_LENGTH < additionalMetaSizeThatWontFitWithinMetaAtom) && (topLevelFreeSize != additionalMetaSizeThatWontFitWithinMetaAtom))) {
                    // by the amount mdat is going to be shifted
                    if (mdatHeader.getFilePos() > moovHeader.getFilePos()) {
                        stco.adjustOffsets(additionalMetaSizeThatWontFitWithinMetaAtom);
                    }
                }
                // Edit and rewrite the Moov header
                adjustSizeOfMoovHeader(moovHeader, moovBuffer, additionalMetaSizeThatWontFitWithinMetaAtom, udtaHeader, metaHeader);
                fileWriteChannel.write(moovHeader.getHeaderData());
                // Now write from this edited buffer up until ilst atom
                moovBuffer.rewind();
                moovBuffer.limit(relativeIlstposition);
                fileWriteChannel.write(moovBuffer);
            }
            // Now write ilst data
            fileWriteChannel.write(rawIlstData);
            // Skip over the read channel old meta level free atom because now used up
            fileReadChannel.position(startIlstWithinFile + oldIlstSize);
            fileReadChannel.position(fileReadChannel.position() + oldMetaLevelFreeAtomSize);
            if (tagsHeader != null) {
                // Write from after ilst upto tags atom
                long writeBetweenIlstAndTags = tagsHeader.getFilePos() - fileReadChannel.position();
                fileWriteChannel.transferFrom(fileReadChannel, fileWriteChannel.position(), writeBetweenIlstAndTags);
                fileWriteChannel.position(fileWriteChannel.position() + writeBetweenIlstAndTags);
                convertandWriteTagsAtomToFreeAtom(fileWriteChannel, tagsHeader);
                // Write after tags atom upto end of moov
                fileReadChannel.position(tagsHeader.getFilePos() + tagsHeader.getLength());
                long extraData = endOfMoov - fileReadChannel.position();
                fileWriteChannel.transferFrom(fileReadChannel, fileWriteChannel.position(), extraData);
            } else {
                // Now write the rest of children under moov which wont have changed
                long extraData = endOfMoov - fileReadChannel.position();
                fileWriteChannel.transferFrom(fileReadChannel, fileWriteChannel.position(), extraData);
                fileWriteChannel.position(fileWriteChannel.position() + extraData);
            }
            // the free atom actually come after the the metadata
            if (topLevelFreeAtomComesBeforeMdatAtomAndAfterMetadata && (topLevelFreePosition >= startIlstWithinFile)) {
                // ok
                if (topLevelFreeSize - Mp4BoxHeader.HEADER_LENGTH >= additionalMetaSizeThatWontFitWithinMetaAtom) {
                    logger.config("Writing:Option 6;Larger Size can use top free atom");
                    Mp4FreeBox freeBox = new Mp4FreeBox((topLevelFreeSize - Mp4BoxHeader.HEADER_LENGTH) - additionalMetaSizeThatWontFitWithinMetaAtom);
                    fileWriteChannel.write(freeBox.getHeader().getHeaderData());
                    fileWriteChannel.write(freeBox.getData());
                    // Skip over the read channel old free atom
                    fileReadChannel.position(fileReadChannel.position() + topLevelFreeSize);
                    // Write Mdat
                    writeDataInChunks(fileReadChannel, fileWriteChannel);
                } else // we could just remove the header
                if (topLevelFreeSize == additionalMetaSizeThatWontFitWithinMetaAtom) {
                    logger.config("Writing:Option 7;Larger Size uses top free atom including header");
                    // Skip over the read channel old free atom
                    fileReadChannel.position(fileReadChannel.position() + topLevelFreeSize);
                    // Write Mdat
                    writeDataInChunks(fileReadChannel, fileWriteChannel);
                } else // Mdat is going to have to move anyway, so keep free atom as is and write it and mdat
                // (have already updated stco above)
                {
                    logger.config("Writing:Option 8;Larger Size cannot use top free atom");
                    fileWriteChannel.transferFrom(fileReadChannel, fileWriteChannel.position(), fileReadChannel.size() - fileReadChannel.position());
                    writeDataInChunks(fileReadChannel, fileWriteChannel);
                }
            } else {
                logger.config("Writing:Option 9;Top Level Free comes after Mdat or before Metadata so cant use it");
                writeDataInChunks(fileReadChannel, fileWriteChannel);
            }
        }
    }
    // Close all channels to original file
    fileReadChannel.close();
    raf.close();
    checkFileWrittenCorrectly(rafTemp, mdatHeader, fileWriteChannel, stco);
}
Also used : CannotWriteException(org.jaudiotagger.audio.exceptions.CannotWriteException) DefaultMutableTreeNode(org.jaudiotagger.utils.tree.DefaultMutableTreeNode) CannotReadException(org.jaudiotagger.audio.exceptions.CannotReadException) FileChannel(java.nio.channels.FileChannel) ByteBuffer(java.nio.ByteBuffer)

Aggregations

DefaultMutableTreeNode (org.jaudiotagger.utils.tree.DefaultMutableTreeNode)5 Mp4BoxHeader (org.jaudiotagger.audio.mp4.atom.Mp4BoxHeader)3 ByteBuffer (java.nio.ByteBuffer)2 FileChannel (java.nio.channels.FileChannel)2 CannotReadException (org.jaudiotagger.audio.exceptions.CannotReadException)2 NullBoxIdException (org.jaudiotagger.audio.exceptions.NullBoxIdException)2 NullPadding (org.jaudiotagger.audio.mp4.atom.NullPadding)2 CannotWriteException (org.jaudiotagger.audio.exceptions.CannotWriteException)1 Mp4MetaBox (org.jaudiotagger.audio.mp4.atom.Mp4MetaBox)1 Mp4StcoBox (org.jaudiotagger.audio.mp4.atom.Mp4StcoBox)1 DefaultTreeModel (org.jaudiotagger.utils.tree.DefaultTreeModel)1