|
JAVA写的MP3标签读写器
|
|
来源:conanpaul 作者: |
|
前几天写了一个NB的音乐插件,自己用了一下,还是挺方便的,后来想想,如果能把歌词也显示出来那就更好了。呵呵,怎么办呢,只有自己写了,在显示歌词之前,必须要知道目前正在播放的MP3是什么内容啊,一点可以从文件名得到一些信息,还有一点就是从MP3文件里面得到这个MP3的信息,我这里实现的ID3V1的格式标签,APEV2也想实现,无奈找不到相关的资料,不知道APEV2的数据结构是怎么样的,所以也无从分析。目前已经写完了 ID3V1格式标签的读取和写入。并且NB的音乐插件也实现了本地歌词的搜索,先把ID3V1的文件结构的类文件帖一下,大家一起分享。 MP3的ID3V1的信息结构是很有规律的,它一般是出现在MP3文件的最后128个字节上,并且是以“TAG”开头。我这里把它封装成一个类了。 截图如下:
代码如下: /* * To change this template, choose Tools | Templates * and open the template in the editor. */ package com.hadeslee.music;
/** * 一个歌曲信息的类的结构表示 * 这个歌曲是使用ID3V1的信息存储结构的 * @author hadeslee */ public class SongInfo {
private final String TAG = "TAG"; //文件头1-3 private String songName; //歌曲名4-33 private String artist; //歌手名34-63 private String album; //专辑名61-93 private String year; //年94-97 private String comment; //备注98-125 private byte r1, r2, r3; //三个保留位126,127,128 private boolean valid; //是否合法 public transient String fileName; //此歌曲对应的文件名,没有封装 public SongInfo(byte[] data) { if (data.length != 128) { throw new RuntimeException("数据长度不合法:" + data.length); } String tag = new String(data, 0, 3); //只有前三个字节是TAG才处理后面的字节 if (tag.equalsIgnoreCase("TAG")) { valid = true; songName = new String(data, 3, 30).trim(); artist = new String(data, 33, 30).trim(); album = new String(data, 63, 30).trim(); year = new String(data, 93, 4).trim(); comment = new String(data, 97, 28).trim(); r1 = data[125]; r2 = data[126]; r3 = data[127]; } else { valid = false; } }
public SongInfo() { }
/** * 返回是否合法 * @return 是否 */ public boolean isValid() { return valid; }
/** * 得到此对象的128个字节的表示形式 * @return */ public byte[] getBytes() { byte[] data = new byte[128]; System.arraycopy(TAG.getBytes(), 0, data, 0, 3); byte[] temp = songName.getBytes(); System.arraycopy(temp, 0, data, 3, temp.length > 30 ? 30 : temp.length); temp = artist.getBytes(); System.arraycopy(temp, 0, data, 33, temp.length > 30 ? 30 : temp.length); temp = album.getBytes(); System.arraycopy(temp, 0, data, 63, temp.length > 30 ? 30 : temp.length); temp = year.getBytes(); System.arraycopy(temp, 0, data, 93, temp.length > 4 ? 4 : temp.length); temp = comment.getBytes(); System.arraycopy(temp, 0, data, 97, temp.length > 28 ? 28 : temp.length); data[125] = r1; data[126] = r2; data[127] = r3; return data; }
public String getArtist() { return artist; }
public void setArtist(String authorName) { this.artist = authorName; }
public String getComment() { return comment; }
public void setComment(String comment) { this.comment = comment; }
public byte getR1() { return r1; }
public void setR1(byte r1) { this.r1 = r1; }
public byte getR2() { return r2; }
public void setR2(byte r2) { this.r2 = r2; }
public byte getR3() { return r3; }
public void setR3(byte r3) { this.r3 = r3; }
public String getSongName() { return songName; }
public void setSongName(String songName) { if(songName==null){ throw new NullPointerException("歌名不能是null!"); } valid=true; this.songName = songName; }
public String getAlbum() { return album; }
public void setAlbum(String specialName) { this.album = specialName; }
public String getYear() { return year; }
public void setYear(String year) { this.year = year; }
}
编辑对话框的代码: /* * SongInfoDialog.java * * Created on 2007年11月26日, 下午4:12 */ package com.hadeslee.music;
import java.awt.Dialog; import java.awt.Frame; import java.io.File; import java.io.RandomAccessFile; import java.util.logging.Level; import java.util.logging.Logger;
/** * * @author hadeslee */ public class SongInfoDialog extends javax.swing.JDialog {
private SongInfo info; //歌曲信息对象 private File file; //文件对象 private boolean valid; //表示原来这个文件是不是合法的,如果不是,就要重新写入128个字节 /** Creates new form SongInfoDialog */ public SongInfoDialog(java.awt.Frame parent, boolean modal) { super(parent, modal); initComponents(); }
public SongInfoDialog(Dialog parent, boolean modal) { super(parent, modal); initComponents(); }
public SongInfoDialog(Frame parent, boolean modal, File file) { this(parent, modal); this.file = file; init(); }
public SongInfoDialog(Dialog parent, boolean modal, File file) { this(parent, modal); this.file = file; init(); }
/** * 初始化 */ private void init() { try { fileName.setText(file.toString()); RandomAccessFile ra = new RandomAccessFile(file, "r"); byte[] buffer = new byte[128]; ra.seek(ra.length() - 128); ra.read(buffer); info = new SongInfo(buffer); valid = info.isValid(); title.setText(info.getSongName()); artist.setText(info.getArtist()); album.setText(info.getAlbum()); year.setText(info.getYear()); comment.setText(info.getComment()); r2.setText("" + info.getR2()); r3.setText("" + info.getR3()); ra.close(); } catch (Exception ex) { Logger.getLogger(SongInfoDialog.class.getName()).log(Level.SEVERE, null, ex); } }
/** This method is called from within the constructor to * initialize the form. * WARNING: Do NOT modify this code. The content of this method is * always regenerated by the Form Editor. */ // private void initComponents() {
jLabel5 = new javax.swing.JLabel(); fileName = new javax.swing.JTextField(); jPanel1 = new javax.swing.JPanel(); jLabel1 = new javax.swing.JLabel(); title = new javax.swing.JTextField(); jLabel2 = new javax.swing.JLabel(); artist = new javax.swing.JTextField(); jLabel3 = new javax.swing.JLabel(); jLabel4 = new javax.swing.JLabel(); album = new javax.swing.JTextField(); jLabel6 = new javax.swing.JLabel(); r2 = new javax.swing.JTextField(); year = new javax.swing.JTextField(); jLabel7 = new javax.swing.JLabel(); r3 = new javax.swing.JTextField(); jLabel8 = new javax.swing.JLabel(); jScrollPane1 = new javax.swing.JScrollPane(); comment = new javax.swing.JTextArea(); jButton1 = new javax.swing.JButton(); jButton2 = new javax.swing.JButton(); jButton3 = new javax.swing.JButton(); jLabel9 = new javax.swing.JLabel();
setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
jLabel5.setText("文件名:");
fileName.setEditable(false);
jPanel1.setBorder(javax.swing.BorderFactory.createTitledBorder("标签"));
jLabel1.setText("标 题:");
jLabel2.setText("艺术家:");
jLabel3.setText("专 辑:");
jLabel4.setText("年份:");
jLabel6.setText("音轨:");
r2.setEditable(false);
jLabel7.setText("流 派:");
r3.setEditable(false);
jLabel8.setText("备 注:");
comment.setColumns(20); comment.setRows(5); jScrollPane1.setViewportView(comment);
javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); jPanel1.setLayout(jPanel1Layout); jPanel1Layout.setHorizontalGroup( jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jPanel1Layout.createSequentialGroup() .addContainerGap() .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jPanel1Layout.createSequentialGroup() .addComponent(jLabel1) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(title, javax.swing.GroupLayout.PREFERRED_SIZE, 183, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGroup(jPanel1Layout.createSequentialGroup() .addComponent(jLabel2) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(artist, javax.swing.GroupLayout.DEFAULT_SIZE, 185, Short.MAX_VALUE) .addContainerGap()) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jPanel1Layout.createSequentialGroup() .addComponent(jLabel3) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(album, javax.swing.GroupLayout.DEFAULT_SIZE, 110, Short.MAX_VALUE)) .addGroup(jPanel1Layout.createSequentialGroup() .addComponent(jLabel7) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(r3, javax.swing.GroupLayout.DEFAULT_SIZE, 110, Short.MAX_VALUE))) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jPanel1Layout.createSequentialGroup() .addComponent(jLabel4) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(year, javax.swing.GroupLayout.PREFERRED_SIZE, 31, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGroup(jPanel1Layout.createSequentialGroup() .addComponent(jLabel6) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(r2, javax.swing.GroupLayout.PREFERRED_SIZE, 30, javax.swing.GroupLayout.PREFERRED_SIZE))) .addContainerGap()) .addGroup(jPanel1Layout.createSequentialGroup() .addComponent(jLabel8) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 185, Short.MAX_VALUE) .addContainerGap()))) ); jPanel1Layout.setVerticalGroup( jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jPanel1Layout.createSequentialGroup() .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jLabel1) .addComponent(title, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jLabel2) .addComponent(artist, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jLabel3) .addComponent(r2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(jLabel6) .addComponent(album, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jLabel4) .addComponent(year, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(jLabel7) .addComponent(r3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jPanel1Layout.createSequentialGroup() .addComponent(jLabel8) .addContainerGap(42, Short.MAX_VALUE)) .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 57, Short.MAX_VALUE))) );
jButton1.setText("重新读取文件"); jButton1.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { jButton1ActionPerformed(evt); } });
jButton2.setText("保存到文件"); jButton2.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { jButton2ActionPerformed(evt); } });
jButton3.setText("取消"); jButton3.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { jButton3ActionPerformed(evt); } });
jLabel9.setForeground(new java.awt.Color(255, 0, 102)); jLabel9.setText("注:目前只支持ID3v1格式标签的存取");
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addComponent(jLabel5) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(fileName, javax.swing.GroupLayout.DEFAULT_SIZE, 221, Short.MAX_VALUE)) .addGroup(layout.createSequentialGroup() .addComponent(jButton1) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jButton2) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jButton3)) .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(jLabel9)) .addContainerGap()) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jLabel5) .addComponent(fileName, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jButton1) .addComponent(jButton2) .addComponent(jButton3)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(jLabel9) .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) );
pack(); }// private void jButton3ActionPerformed(java.awt.event.ActionEvent evt) { // TODO add your handling code here: this.dispose(); }
private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) { // TODO add your handling code here: doSave(); }
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) { // TODO add your handling code here: init(); }
private void doSave() { try { info.setAlbum(album.getText()); info.setArtist(artist.getText()); info.setComment(comment.getText()); info.setR1((byte) 0); try { info.setR2(Byte.parseByte(r2.getText())); info.setR3(Byte.parseByte(r3.getText())); } catch (Exception exe) { exe.printStackTrace(); } info.setYear(year.getText()); info.setSongName(title.getText()); RandomAccessFile raf = new RandomAccessFile(file, "rw"); //如果这个文件原来就是合法的,那么就不用新起128个字节了 if (valid) { raf.seek(raf.length() - 128); } else {//否则就在末层加上128个字节 raf.seek(raf.length()); } raf.write(info.getBytes()); raf.close(); this.dispose(); } catch (Exception ex) { Logger.getLogger(SongInfoDialog.class.getName()).log(Level.SEVERE, null, ex); } }
/** * @param args the command line arguments */ public static void main(String args[]) { java.awt.EventQueue.invokeLater(new Runnable() {
public void run() { SongInfoDialog dialog = new SongInfoDialog(new javax.swing.JFrame(), true); dialog.addWindowListener(new java.awt.event.WindowAdapter() {
public void windowClosing(java.awt.event.WindowEvent e) { System.exit(0); } }); dialog.setVisible(true); } }); } // Variables declaration - do not modify private javax.swing.JTextField album; private javax.swing.JTextField artist; private javax.swing.JTextArea comment; private javax.swing.JTextField fileName; private javax.swing.JButton jButton1; private javax.swing.JButton jButton2; private javax.swing.JButton jButton3; private javax.swing.JLabel jLabel1; private javax.swing.JLabel jLabel2; private javax.swing.JLabel jLabel3; private javax.swing.JLabel jLabel4; private javax.swing.JLabel jLabel5; private javax.swing.JLabel jLabel6; private javax.swing.JLabel jLabel7; private javax.swing.JLabel jLabel8; private javax.swing.JLabel jLabel9; private javax.swing.JPanel jPanel1; private javax.swing.JScrollPane jScrollPane1; private javax.swing.JTextField r2; private javax.swing.JTextField r3; private javax.swing.JTextField title; private javax.swing.JTextField year; // End of variables declaration }
有歌词的插件,由于还不是很完善,所以过一两天再发布,呵呵。:)
|
| 上一篇:JavaScript中如何定义类 下一篇:如何使用Static的变量和方法 |
|
[ 收藏]
[ 推荐]
[ 评论(0条)]
[返回顶部] [打印本页]
[关闭窗口] |
|
|
| |
|
|
 |
|
相关信息 |
|
|
|
|
|