⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 abstractid3v2tag.java

📁 java+eclipse做的TTPlayer
💻 JAVA
📖 第 1 页 / 共 5 页
字号:
     * Checks to see if the file contains an ID3tag and if so return its size as reported in
     * the tag header  and return the size of the tag (including header), if no such tag exists return
     * zero.
     *
     * @param file
     * @return the end of the tag in the file or zero if no tag exists.
     */
    public static long getV2TagSizeIfExists(File file) throws IOException
    {
        FileInputStream fis = null;
        FileChannel fc = null;
        ByteBuffer bb = null;
        try
        {
            //Files
            fis = new FileInputStream(file);
            fc = fis.getChannel();

            //Read possible Tag header  Byte Buffer
            bb = ByteBuffer.allocate(TAG_HEADER_LENGTH);
            fc.read(bb);
            bb.flip();
            if (bb.limit() < (TAG_HEADER_LENGTH))
            {
                return 0;
            }
        }
        finally
        {
            if (fc != null)
            {
                fc.close();
            }

            if (fis != null)
            {
                fis.close();
            }
        }

        //ID3 identifier
        byte[] tagIdentifier = new byte[FIELD_TAGID_LENGTH];
        bb.get(tagIdentifier, 0, FIELD_TAGID_LENGTH);
        if (!(Arrays.equals(tagIdentifier, TAG_ID)))
        {
            return 0;
        }

        //Is it valid Major Version
        byte majorVersion = bb.get();
        if ((majorVersion != ID3v22Tag.MAJOR_VERSION) && (majorVersion != ID3v23Tag.MAJOR_VERSION) && (majorVersion != ID3v24Tag.MAJOR_VERSION))
        {
            return 0;
        }

        //Skip Minor Version
        bb.get();

        //Skip Flags
        bb.get();

        //Get size as recorded in frame header
        int frameSize = ID3SyncSafeInteger.bufferToValue(bb);

        //add header size to frame size
        frameSize += TAG_HEADER_LENGTH;
        return frameSize;
    }

    /**
     * Does a tag of the correct version exist in this file.
     *
     * @param byteBuffer to search through
     * @return true if tag exists.
     */
    public boolean seek(ByteBuffer byteBuffer)
    {
        byteBuffer.rewind();
        logger.info("ByteBuffer pos:" + byteBuffer.position() + ":limit" + byteBuffer.limit() + ":cap" + byteBuffer.capacity());


        byte[] tagIdentifier = new byte[FIELD_TAGID_LENGTH];
        byteBuffer.get(tagIdentifier, 0, FIELD_TAGID_LENGTH);
        if (!(Arrays.equals(tagIdentifier, TAG_ID)))
        {
            return false;
        }
        //Major Version
        if (byteBuffer.get() != getMajorVersion())
        {
            return false;
        }
        //Minor Version
        if (byteBuffer.get() != getRevision())
        {
            return false;
        }
        return true;
    }

    /**
     * This method determines the total tag size taking into account
     * where the audio file starts, the size of the tagging data and
     * user options for defining how tags should shrink or grow.
     */
    protected int calculateTagSize(int tagSize, int audioStart)
    {
        /** We can fit in the tag so no adjustments required */
        if (tagSize <= audioStart)
        {
            return audioStart;
        }
        /** There is not enough room as we need to move the audio file we might
         *  as well increase it more than neccessary for future changes
         */
        return tagSize + TAG_SIZE_INCREMENT;
    }

    /**
     * Adjust the length of the  padding at the beginning of the MP3 file, this is only called when there is currently
     * not enough space before the start of the audio to write the tag.
     * <p/>
     * A new file will be created with enough size to fit the <code>ID3v2</code> tag.
     * The old file will be deleted, and the new file renamed.
     *
     * @param paddingSize This is total size required to store tag before audio
     * @param file        The file to adjust the padding length of
     * @throws FileNotFoundException if the file exists but is a directory
     *                               rather than a regular file or cannot be opened for any other
     *                               reason
     * @throws IOException           on any I/O error
     */
    public void adjustPadding(File file, int paddingSize, long audioStart) throws FileNotFoundException, IOException
    {
        logger.finer("Need to move audio file to accomodate tag");
        FileChannel fcIn;
        FileChannel fcOut;

        // Create buffer holds the neccessary padding
        ByteBuffer paddingBuffer = ByteBuffer.wrap(new byte[paddingSize]);

        // Create Temporary File and write channel
        File paddedFile = File.createTempFile("temp", ".mp3", file.getParentFile());
        fcOut = new FileOutputStream(paddedFile).getChannel();

        //Create read channel from original file
        fcIn = new FileInputStream(file).getChannel();

        //Write padding to new file (this is where the tag will be written to later)
        long written = (long) fcOut.write(paddingBuffer);

        //Write rest of file starting from audio
        logger.finer("Copying:" + (file.length() - audioStart) + "bytes");

        //if the amount to be copied is very large we split into 10MB lumps to try and avoid
        //out of memory errors
        long audiolength = file.length() - audioStart;
        if (audiolength <= MAXIMUM_WRITABLE_CHUNK_SIZE)
        {
            long written2 = fcIn.transferTo(audioStart, audiolength, fcOut);
            logger.finer("Written padding:" + written + " Data:" + written2);
            if (written2 != audiolength)
            {
                throw new RuntimeException("Problem adjusting padding, expecting to write:" + audiolength + ":only wrote:" + written2);
            }
        }
        else
        {
            long noOfChunks = audiolength / MAXIMUM_WRITABLE_CHUNK_SIZE;
            long lastChunkSize = audiolength % MAXIMUM_WRITABLE_CHUNK_SIZE;
            long written2 = 0;
            for (int i = 0; i < noOfChunks; i++)
            {
                written2 += fcIn.transferTo(audioStart + (i * MAXIMUM_WRITABLE_CHUNK_SIZE), MAXIMUM_WRITABLE_CHUNK_SIZE, fcOut);
                //Try and recover memory as quick as possible
                Runtime.getRuntime().gc();
            }
            written2 += fcIn.transferTo(audioStart + (noOfChunks * MAXIMUM_WRITABLE_CHUNK_SIZE), lastChunkSize, fcOut);
            logger.finer("Written padding:" + written + " Data:" + written2);
            if (written2 != audiolength)
            {
                throw new RuntimeException("Problem adjusting padding in large file, expecting to write:" + audiolength + ":only wrote:" + written2);
            }
        }

        //Store original modification time
        long lastModified = file.lastModified();

        //Close Channels
        fcIn.close();
        fcOut.close();

        //Delete original File
        file.delete();

        //Rename temporary file and set modification time to original time.
        paddedFile.renameTo(file);
        paddedFile.setLastModified(lastModified);

    }

    /**
     * Add frame to HashMap used when converting between tag versions, take into account
     * occurences when two frame may both map to a single frame when converting between
     * versions
     * <p/>
     * TODO the logic here is messy and seems to be specific to date fields only when it
     * was intended to be generic.
     */
    protected void copyFrameIntoMap(String id, AbstractID3v2Frame newFrame)
    {
        /* The frame already exists this shouldnt normally happen because frames
        * that are allowed to be multiple don't call this method. Frames that
        * arent allowed to be multiple aren't added to hashmap in first place when
        * originally added.
        *
        * We only want to allow one of the frames going forward but we try and merge
        * all the information into the one frame. However there is a problem here that
        * if we then take this, modify it and try to write back the original values
        * we could lose some information although this info is probably invalid anyway.
        *
        * However converting some frames from tag of one version to another may
        * mean that two different frames both get converted to one frame, this
        * particulary applies to DateTime fields which were originally two fields
        * in v2.3 but are one field in v2.4.
        */
        if (frameMap.containsKey(newFrame.getIdentifier()))
        {
            //Retrieve the frame with the same id we have already loaded into the map
            AbstractID3v2Frame firstFrame = (AbstractID3v2Frame) frameMap.get(newFrame.getIdentifier());

            /* Two different frames both converted to TDRCFrames, now if this is the case one of them
            * may have actually have been created as a FrameUnsupportedBody because TDRC is only
            * supported in ID3v24, but is often created in v23 tags as well together with the valid TYER
            * frame
            */
            if (newFrame.getBody() instanceof FrameBodyTDRC)
            {
                if (firstFrame.getBody() instanceof FrameBodyTDRC)
                {
                    logger.finest("Modifying frame in map:" + newFrame.getIdentifier());
                    FrameBodyTDRC body = (FrameBodyTDRC) firstFrame.getBody();
                    FrameBodyTDRC newBody = (FrameBodyTDRC) newFrame.getBody();
                    //Just add the data to the frame
                    if (newBody.getOriginalID().equals(ID3v23Frames.FRAME_ID_V3_TYER))
                    {
                        body.setYear(newBody.getText());
                    }
                    else if (newBody.getOriginalID().equals(ID3v23Frames.FRAME_ID_V3_TDAT))
                    {
                        body.setDate(newBody.getText());
                    }
                    else if (newBody.getOriginalID().equals(ID3v23Frames.FRAME_ID_V3_TIME))
                    {
                        body.setTime(newBody.getText());
                    }
                    else if (newBody.getOriginalID().equals(ID3v23Frames.FRAME_ID_V3_TRDA))
                    {
                        body.setReco(newBody.getText());
                    }
                }
                /* The first frame was a TDRC frame that was not really allowed, this new frame was probably a
                * valid frame such as TYER which has been converted to TDRC, replace the firstframe with this frame
                */
                else if (firstFrame.getBody() instanceof FrameBodyUnsupported)
                {
                    frameMap.put(newFrame.getIdentifier(), newFrame);
                }
                else
                {
                    //we just lose this frame, weve already got one with the correct id.
                    //TODO may want to store this somewhere
                    logger.warning("Found duplicate TDRC frame in invalid situation,discarding:" + newFrame.getIdentifier());
                }
            }
            else
            {
                logger.warning("Found duplicate frame in invalid situation,discarding:" + newFrame.getIdentifier());
            }
        }
        else
        //Just add frame to map
        {
            logger.finest("Adding frame to map:" + newFrame.getIdentifier());
            frameMap.put(newFrame.getIdentifier(), newFrame);
        }
    }

    /**
     * Decides what to with the frame that has just be read from file.
     * If the frame is an allowable duplicate frame and is a duplicate we add all
     * frames into an ArrayList and add the Arraylist to the hashMap. if not allowed
     * to be duplicate we store bytes in the duplicateBytes variable.
     */
    protected void loadFrameIntoMap(String frameId, AbstractID3v2Frame next)
    {
        if ((ID3v24Frames.getInstanceOf().isMultipleAllowed(frameId)) || (ID3v23Frames.getInstanceOf().isMultipleAllowed(frameId)) || (ID3v22Frames.getInstanceOf().isMultipleAllowed(frameId)))
        {
            //If a frame already exists of this type
            if (frameMap.containsKey(frameId))
            {
                Object o = frameMap.get(frameId);
                if (o instanceof ArrayList)
                {
                    ArrayList multiValues = (ArrayList) o;
                    multiValues.add(next);
                    logger.finer("Adding Multi Frame(1)" + frameId);
                }
                else
                {
                    ArrayList multiValues = new ArrayList();
                    multiValues.add(o);
                    multiValues.add(next);
                    frameMap.put(frameId, multiValues);
                    logger.finer("Adding Multi Frame(2)" + frameId);
                }
            }
            else
            {
                logger.finer("Adding Multi FrameList(3)" + frameId);
                frameMap.put(frameId, next);
            }
        }
        //If duplicate frame just stores it somewhere else
        else if (frameMap.containsKey(frameId))
        {
            logger.warning("Duplicate Frame" + frameId);
            this.duplicateFrameId += (frameId + "; ");
            this.duplicateBytes += ((AbstractID3v2Frame) frameMap.get(frameId)).getSize();
        }
        else
        {
            logger.finer("Adding Frame" + frameId);
            frameMap.put(frameId, next);
        }
    }

    /**
     * Return tag size based upon the sizes of the tags rather than the physical
     * no of bytes between start of ID3Tag and start of Audio Data.Should be extended
     * by subclasses to include header.
     *
     * @return size of the tag
     */
    public int getSize()
    {
        int size = 0;
        Iterator iterator = frameMap.values().iterator();
        AbstractID3v2Frame frame;
        while (iterator.hasNext())
        {
            Object o = iterator.next();
            if (o instanceof AbstractID3v2Frame)
            {
                frame = (AbstractID3v2Frame) o;
                size += frame.getSize();
            }
            else
            {
                ArrayList multiFrames = (ArrayList) o;
                for (ListIterator li = multiFrames.listIterator(); li.hasNext();)
                {
                    frame = (AbstractID3v2Frame) li.next();
                    size += frame.getSize();
                }
            }
        }
        return size;
    }

    /**
     * Write all the frames to the byteArrayOutputStream
     * <p/>
     * <p>Currently Write all frames, defaults to the order in which they were loaded, newly
     * created frames will be at end of tag.
     *
     * @return ByteBuffer Contains all the frames written within the tag ready for writing to file
     * @throws IOException
     */
    //TODO there is a preferred tag order mentioned in spec, e.g ufid first
    protected ByteArrayOutputStream writeFramesToBuffer() throws IOException
    {
        //Increases as is required
        ByteArrayOutputStream bodyBuffer = new ByteArrayOutputStream();


        AbstractID3v2Frame frame;
        Iterator iterator;
        iterator = frameMap.values().iterator();

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -