点击数:0

最近开发时遇到了一个问题,要求为上传的MP3文件写入标签,在网上找了很久,找到一篇不错的文章,分享一下,以免遗忘。

原文链接:浅谈如何使用代码为MP3文件写入ID3Tags

源代码可能是许久未更新,实际使用会有许多问题,下面是我修改后的代码,开箱即用:

1. MusicFunction

public class MusicFunction {
    public static void StorageMusicFileWithID3V1Tag(File sourceFile, String musicFilePath,
                                                    String songName, String artistName,
                                                    String albumName) {
        try {
            sourceFile.renameTo(new File(musicFilePath));

            RandomAccessFile musicRandomAccessFile = new RandomAccessFile(musicFilePath, "rw");
            musicRandomAccessFile.seek(0); // 跳到ID3V1开始的位置

            byte[] tag = new byte[3];
            musicRandomAccessFile.read(tag);

            if (new String(tag).equals("TAG")) {
                return;
            }

            byte[] tagByteArray = new byte[128];

            musicRandomAccessFile.seek(musicRandomAccessFile.length());

            byte[] songNameByteArray = songName.getBytes("GBK");
            byte[] artistNameByteArray = artistName.getBytes("GBK");
            byte[] albumNameByteArray = albumName.getBytes("GBK");

            int songNameByteArrayLength = songNameByteArray.length;
            int artistNameByteArrayLength = artistNameByteArray.length;
            int albumNameByteArrayLength = albumNameByteArray.length;

            songNameByteArrayLength = songNameByteArrayLength > 30 ? 30 : songNameByteArrayLength;
            artistNameByteArrayLength =
                    artistNameByteArrayLength > 30 ? 30 : artistNameByteArrayLength;
            albumNameByteArrayLength =
                    albumNameByteArrayLength > 30 ? 30 : albumNameByteArrayLength;

            System.arraycopy("TAG".getBytes(), 0, tagByteArray, 0, 3);
            System.arraycopy(songNameByteArray, 0, tagByteArray, 3, songNameByteArrayLength);
            System.arraycopy(artistNameByteArray, 0, tagByteArray, 33, artistNameByteArrayLength);
            System.arraycopy(albumNameByteArray, 0, tagByteArray, 63, albumNameByteArrayLength);

            tagByteArray[127] = (byte) 0xFF; // 将流派显示为指定音乐的流派

            musicRandomAccessFile.write(tagByteArray);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //  关于mp3 tag的资料很多,大家可以自己去找,大概讲的都是大同小异,但是我觉得他们忘了很重要的一点,也是我忽略的,后来解析文件出错了,才恍然大悟。那就是tag信息的编码。
    //  大家都知道每个frame的头是由10个字节组成的,具体内容我就不仔细写了,在着10个字节后有一个字节,大家可以仔细观察一下,只有四种情况 00 ,01,02,03,这个代表什么呢?
    //  这个就是表示tag的编码方式的。00代表的就是ISO-8859-1 编码,后面直接跟的就是字符串,关于编码方式我就不做解释了,网上很多,01 代表的就是UTF-16编码,我就是被这个搞晕了,
    //  因为在10个字节的表示后有这个三个字节 01 FF FE,我是直接按照ISO-8859-1编码进行的。所以读取的字符串总是不对,FF FE表示的BOM的顺序。
    //  FF FE表示的就是little endian,FE FF 表示的就是big endian。在这两个字节后面才是真正的字符串。02 代表 UTF16BE 我没有碰到过大概的意思就是UTF16的编码顺序为big endian,
    //  但是这种编码没有FF FE这样的标识。03 表示的UTF8编码,在tag信息是不采用这种表明,但是这种编码在tag里不是错的。
    public static void StorageMusicFileWithID3V2Tag(File sourceFile, String musicFilePath,
                                                    String songName, String artistName,
                                                    String albumName) {
        try (RandomAccessFile musicRandomAccessFile = new RandomAccessFile(sourceFile.getAbsolutePath(), "rw")) {
            musicRandomAccessFile.seek(0);

            byte[] tag = new byte[3];
            musicRandomAccessFile.read(tag);

            if (new String(tag).equals("ID3")) {
                sourceFile.renameTo(new File(musicFilePath));
                return;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            byte[] encodeByte = {3}; // 03 表示的UTF8编码
            byte[] tagByteArray;
            byte[] tagHeadByteArray;
            byte[] tagFrameHeadByteArray;

            byte[] songNameByteArray = songName.getBytes("UTF-8");
            byte[] artistNameByteArray = artistName.getBytes("UTF-8");
            byte[] albumNameByteArray = albumName.getBytes("UTF-8");

            final int tagHeadLength = 10;
            final int tagFrameHeadLength = 10;
            final int tagFrameEncodeLength = 1;
            final int tagFillByteLength = 20; // 这个填充字节是我看到其他MP3文件ID3标签都会在尾端添加的数据,为了保险起见我也加上了

            int byteArrayOffset = 0;
            int songNameByteArrayLength = songNameByteArray.length;
            int artistNameByteArrayLength = artistNameByteArray.length;
            int albumNameByteArrayLength = albumNameByteArray.length;
            int songNameFrameTotalLength = songNameByteArrayLength + tagFrameEncodeLength;
            int artistNameFrameTotalLength = artistNameByteArrayLength + tagFrameEncodeLength;
            int albumNameFrameTotalLength = albumNameByteArrayLength + tagFrameEncodeLength;

            int tagTotalLength = tagHeadLength + tagFrameHeadLength + songNameByteArrayLength +
                    tagFrameHeadLength + artistNameByteArrayLength +
                    tagFrameHeadLength + albumNameByteArrayLength +
                    tagFillByteLength;
            int tagContentLength = tagTotalLength - tagHeadLength;

            tagByteArray = new byte[tagTotalLength];

            tagHeadByteArray = new byte[tagHeadLength];
            System.arraycopy("ID3".getBytes(), 0, tagHeadByteArray, 0, 3);
            tagHeadByteArray[3] = 3;
            tagHeadByteArray[4] = 0;
            tagHeadByteArray[5] = 0;
            tagHeadByteArray[6] = (byte) ((tagContentLength >> 7 >> 7 >> 7) % 128);
            tagHeadByteArray[7] = (byte) ((tagContentLength >> 7 >> 7) % 128);
            tagHeadByteArray[8] = (byte) ((tagContentLength >> 7) % 128);
            tagHeadByteArray[9] = (byte) (tagContentLength % 128);
            System.arraycopy(tagHeadByteArray, 0, tagByteArray, byteArrayOffset,
                    tagHeadLength);
            byteArrayOffset += tagHeadLength;

            tagFrameHeadByteArray = new byte[tagFrameHeadLength];
            System.arraycopy("TIT2".getBytes(), 0, tagFrameHeadByteArray, 0, 4);
            tagFrameHeadByteArray[4] = (byte) ((songNameFrameTotalLength >> 8 >> 8 >> 8) % 256);
            tagFrameHeadByteArray[5] = (byte) ((songNameFrameTotalLength >> 8 >> 8) % 256);
            tagFrameHeadByteArray[6] = (byte) ((songNameFrameTotalLength >> 8) % 256);
            tagFrameHeadByteArray[7] = (byte) (songNameFrameTotalLength % 256);
            tagFrameHeadByteArray[8] = 0;
            tagFrameHeadByteArray[9] = 0;
            System.arraycopy(tagFrameHeadByteArray, 0, tagByteArray, byteArrayOffset, tagFrameHeadLength);
            byteArrayOffset += tagFrameHeadLength;
            System.arraycopy(encodeByte, 0, tagByteArray, byteArrayOffset, tagFrameEncodeLength);
            byteArrayOffset += tagFrameEncodeLength;
            System.arraycopy(songNameByteArray, 0, tagByteArray, byteArrayOffset,
                    songNameByteArrayLength);
            byteArrayOffset += songNameByteArrayLength;

            tagFrameHeadByteArray = new byte[tagFrameHeadLength];
            System.arraycopy("TPE1".getBytes(), 0, tagFrameHeadByteArray, 0, 4);
            tagFrameHeadByteArray[4] = (byte) ((artistNameFrameTotalLength >> 8 >> 8 >> 8) % 256);
            tagFrameHeadByteArray[5] = (byte) ((artistNameFrameTotalLength >> 8 >> 8) % 256);
            tagFrameHeadByteArray[6] = (byte) ((artistNameFrameTotalLength >> 8) % 256);
            tagFrameHeadByteArray[7] = (byte) (artistNameFrameTotalLength % 256);
            tagFrameHeadByteArray[8] = 0;
            tagFrameHeadByteArray[9] = 0;
            System.arraycopy(tagFrameHeadByteArray, 0, tagByteArray, byteArrayOffset, tagFrameHeadLength);
            byteArrayOffset += tagFrameHeadLength;
            System.arraycopy(encodeByte, 0, tagByteArray, byteArrayOffset, tagFrameEncodeLength);
            byteArrayOffset += tagFrameEncodeLength;
            System.arraycopy(artistNameByteArray, 0, tagByteArray, byteArrayOffset,
                    artistNameByteArrayLength);
            byteArrayOffset += artistNameByteArrayLength;

            tagFrameHeadByteArray = new byte[tagFrameHeadLength];
            System.arraycopy("TALB".getBytes(), 0, tagFrameHeadByteArray, 0, 4);
            tagFrameHeadByteArray[4] = (byte) ((albumNameFrameTotalLength >> 8 >> 8 >> 8) % 256);
            tagFrameHeadByteArray[5] = (byte) ((albumNameFrameTotalLength >> 8 >> 8) % 256);
            tagFrameHeadByteArray[6] = (byte) ((albumNameFrameTotalLength >> 8) % 256);
            tagFrameHeadByteArray[7] = (byte) (albumNameFrameTotalLength % 256);
            tagFrameHeadByteArray[8] = 0;
            tagFrameHeadByteArray[9] = 0;
            System.arraycopy(tagFrameHeadByteArray, 0, tagByteArray, byteArrayOffset, tagFrameHeadLength);
            byteArrayOffset += tagFrameHeadLength;
            System.arraycopy(encodeByte, 0, tagByteArray, byteArrayOffset, tagFrameEncodeLength);
            byteArrayOffset += tagFrameEncodeLength;
            System.arraycopy(albumNameByteArray, 0, tagByteArray, byteArrayOffset,
                    albumNameByteArrayLength);

            byte[] dataByteBuffer = new byte[1024];

            FileInputStream fileInputStream = new FileInputStream(sourceFile);
            FileOutputStream fileOutputStream = FileFunction.GetFileOutputStreamFromFile(musicFilePath);

            fileOutputStream.write(tagByteArray);

            while (fileInputStream.read(dataByteBuffer) > 0) {
                fileOutputStream.write(dataByteBuffer);
            }

            fileOutputStream.close();
            fileInputStream.close();
            FileFunction.forceDelete(sourceFile);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2.FileFunction

public class FileFunction {

    private static void CreateDirectory(String path) {
        File dir = new File(path);

        if (!dir.exists()) {
            dir.mkdirs();
        }
    }

    public static void SaveFile(String url, String content) {
        SaveFile(url, content, true, false);
    }

    public static void SaveFile(String url, String content, boolean cover, boolean append) {
        FileOutputStream out = null;
        File file = new File(url);

        try {
            if (file.exists()) {
                if (cover) {
                    file.delete();
                    file.createNewFile();
                }
            } else {
                file.createNewFile();
            }

            out = new FileOutputStream(file, append);
            out.write(content.getBytes());
            out.close();
        } catch (Exception e) {
            e.printStackTrace();

            if (out != null) {
                try {
                    out.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
        }
    }

    public static void DeleteFile(String path) {

            File file = new File(path);

            if (file.exists()) {
                try {
                    file.delete();
                } catch (Exception e) {
                e.printStackTrace();
                }
            }
    }

    public static boolean forceDelete(File f) {
        boolean result = false;
        int tryCount = 0;
        while (!result && tryCount++ < 10) {
            System.gc();
            result = f.delete();
        }
        return result;
    }

    public static void CopyFile(String oldPath, String newPath) {
        try {
            int byteRead;

            File oldFile = new File(oldPath);
            File newFile = new File(newPath);

            if (oldFile.exists()) { //文件存在时
                if (newFile.exists()) {
                    newFile.delete();
                }

                newFile.createNewFile();

                FileInputStream inputStream = new FileInputStream(oldPath); //读入原文件
                FileOutputStream outputStream = new FileOutputStream(newPath);
                byte[] buffer = new byte[1024];

                while ((byteRead = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, byteRead);
                }

                inputStream.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static FileInputStream GetFileInputStreamFromFile(String fileUrl) {
        FileInputStream fileInputStream = null;

        try {
            File file = new File(fileUrl);

            fileInputStream = new FileInputStream(file);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return fileInputStream;
    }

    public static FileOutputStream GetFileOutputStreamFromFile(String fileUrl) {
        FileOutputStream bufferedOutputStream = null;

        try {
            File file = new File(fileUrl);

            if (file.exists()) {
                file.delete();
            }

            file.createNewFile();

            bufferedOutputStream = new FileOutputStream(file);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return bufferedOutputStream;
    }

    public static BufferedOutputStream GetBufferedOutputStreamFromFile(String fileUrl) {
        BufferedOutputStream bufferedOutputStream = null;

        try {
            File file = new File(fileUrl);

            if (file.exists()) {
                file.delete();
            }

            file.createNewFile();

            bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(file));
        } catch (Exception e) {
            e.printStackTrace();
        }

        return bufferedOutputStream;
    }
}

3.实际使用

    String ID3V1TagTempFilepath = "id3v1temp.mp3";
    String ID3V2TagTempFilepath = "id3v2temp.mp3";
    String ID3V1TagFilepath = "id3v1.mp3";
    String ID3V2TagFilepath = "id3v2.mp3";

    MusicFunction.StorageMusicFileWithID3V1Tag(new File(ID3V1TagTempFilepath), ID3V1TagFilepath,
                                               "歌名 songName id3v1", "歌手名 artistName id3v1", "专辑名 albumName id3v1");
    MusicFunction.StorageMusicFileWithID3V2Tag(new File(ID3V2TagTempFilepath), ID3V2TagFilepath,
                                               "歌名 songName id3v2", "歌手名 artistName id3v2", "专辑名 albumName id3v2");


心之所向,素履前往 ;生之逆旅,一苇以航 .