YOYOPlayer开发手记(三)APEv2标签读写

YOYOPlayer开发手记(三)APEv2标签读写

在写netbeans的音乐插件的时候,为了读取ID3v1标签,曾经写了一个ID3v1格式的标签读写器,但是ID3v1格式的扩展性却不太好,这个时候APEv2格式就很适合了,首先它的编码是很标准,都是统一UTF-8编码,不会出现乱码的问题,其次它的扩展性很好,并不像ID3v1一样限制128个字节.但是找了很多都没有找到APEv2的标签读写器,没办法,只能自己动手写了,在写之前必须了解APEv2格式标签的文件结构,具体的文件结构可以参见http://wiki.hydrogenaudio.org/index.php?title=APEv2_specification,刚看到这个网页的时候,看得云里雾里的,看了好久才明白它的结构.

明白了构造以后就要开始编码了,但是我不能单独编码啊,我得把这个类集成到jaudiotagger库里面去,这个才便于统一管理啊,于是又研究了jaudiotagger的源码,了解了这个库的文件组织以及结构以后,才把我新写的APEv2格式标签读写器插入到jaudiotagger的组织里去,为了管理的方便,我把jaudiotagger集成到了我的源码里面,一下子源码就多出来十几个包,一打开netbeans,再打开工程,一长列的包就看到了,要不要把它加进源码当时我也有想过,加进来会使得YOYOPlayer工程的源码很多,很长,但是为了统一管理日志,以及插入自己需要的标签读写器,还有要对源码做一些修改,最后还是把这个jaudiotagger的源码加入进来了.

 以下是APEv2格式标签的读写代码,实现了jaudiotagger里面的一些接口以便于管理.

APEv2.java代表一个标签
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 
*/
package  com.hadeslee.audiotag.tag.ape;

import  com.hadeslee.audiotag.tag.FieldDataInvalidException;
import  com.hadeslee.audiotag.tag.KeyNotFoundException;
import  com.hadeslee.audiotag.tag.Tag;
import  com.hadeslee.audiotag.tag.TagField;
import  com.hadeslee.audiotag.tag.TagFieldKey;
import  java.io.ByteArrayOutputStream;
import  java.io.File;
import  java.io.IOException;
import  java.io.RandomAccessFile;
import  java.io.UnsupportedEncodingException;
import  java.util.ArrayList;
import  java.util.HashMap;
import  java.util.Iterator;
import  java.util.List;
import  java.util.Map;
import  java.util.logging.Level;
import  java.util.logging.Logger;
import  javax.sound.sampled.UnsupportedAudioFileException;

/**
 *
 * 
@author  hadeslee
 
*/
public   class  APEv2Tag  implements  Tag {

    
private   static  Logger log  =  Logger.getLogger(APEv2Tag. class .getName());
    
private  File input;
    
private  TagHead head;
    
private  TagBody body;
    
private  String artist  =   "" ;
    
private  String album  =   "" ;
    
private  String title  =   "" ;
    
private  String year  =   "" ;
    
private  String comment  =   "" ;
    
private  String track  =   "" ;
    
private  String genre  =   "" ;
    
private   int  fieldCount;
    
private  Map < String, String >  map;

    
public  APEv2Tag(File file)  throws  IOException, UnsupportedAudioFileException {
        
this .input  =  file;
        map 
=   new  HashMap < String, String > ();
        load();
    }

    
public  APEv2Tag() {
        map 
=   new  HashMap < String, String > ();
    }

    
protected   void  load()  throws  IOException, UnsupportedAudioFileException {
        RandomAccessFile raf 
=   new  RandomAccessFile(input,  " r " );
        
// 先查看最后32个字节
         try  {
            raf.seek((
int ) (input.length()  -   32 ));
            
byte [] buffer  =   new   byte [ 32 ];
            raf.read(buffer);
            head 
=   new  TagHead(buffer);
            
if  (head.isValid()) {
                log.log(Level.INFO, 
" 读取:最后32个字节有标签! " );
                
int  size  =  head.getTagSize();
                raf.seek((
int ) (input.length()  -  size));
                buffer 
=   new   byte [size  -   32 ];
                
int  read  =   0 ;
                
while  (read  <  buffer.length) {
                    read 
+=  raf.read(buffer, read, buffer.length  -  read);
                }
                body 
=   new  TagBody(buffer);
                List
< TagItem >  list  =  body.getItems();
                
for  (TagItem item : list) {
                    log.log(Level.INFO, item.toString());
                }

            } 
else  { // 再查看128前面的32个字节
                raf.seek(( int ) (input.length()  -   32   -   128 ));
                raf.read(buffer);
                head 
=   new  TagHead(buffer);
                
if  (head.isValid()) {
                    log.log(Level.INFO, 
" 读取:ID3v1前面的字节有标签! " );
                    
int  size  =  head.getTagSize();
                    raf.seek((
int ) (input.length()  -  size  -   128 ));
                    buffer 
=   new   byte [size  -   32 ];
                    
int  read  =   0 ;
                    
while  (read  <  buffer.length) {
                        read 
+=  raf.read(buffer, read, buffer.length  -  read);
                    }
                    body 
=   new  TagBody(buffer);
                    List
< TagItem >  list  =  body.getItems();
                    
for  (TagItem item : list) {
                        log.log(Level.INFO, item.toString());
                    }

                } 
else  {
                    
throw   new  UnsupportedAudioFileException( " 读取:找不到APEv2格式的标签! " );
                }
            }
        } 
finally  {
            
try  {
                raf.close();
                readTag();
            } 
catch  (Exception exe) {
                
throw   new  UnsupportedAudioFileException( " 读取:找不到APEv2格式的标签! " );
            }
        }
    }

    
private   void  readTag() {
        
for  (TagItem item : body.getItems()) {
            map.put(item.getId(), item.getContent());
        }
        
this .album  =  map.get(APEv2FieldKey.Album.name());
        
this .artist  =  map.get(APEv2FieldKey.Artist.name());
        
this .comment  =  map.get(APEv2FieldKey.Comment.name());
        
this .genre  =  map.get(APEv2FieldKey.Genre.name());
        
this .title  =  map.get(APEv2FieldKey.Title.name());
        
this .track  =  map.get(APEv2FieldKey.Track.name());
        
this .year  =  map.get(APEv2FieldKey.Year.name());
    }

    
protected  List < TagField >  returnFieldToList(TagItem field) {
        List
< TagField >  fields  =   new  ArrayList < TagField > ();
        fields.add(field);
        
return  fields;
    }

    
/**
     * 写出APE标签到文件里面去
     * 
@param  raf 随机文件流
     * 
@param  hasID3v1 是否有ID3v1标签
     * 
@throws  java.io.IOException
     
*/
    
public   void  write(RandomAccessFile raf,  boolean  hasID3v1)  throws  IOException {
        
// 如果有ID3标签,则先把它缓存起来,总共128个字节
         byte [] temp  =   null ;
        
int  deleteLength  =   0 ;
        
if  (hasID3v1) {
            temp 
=   new   byte [ 128 ];
            raf.seek(raf.length() 
-   128 );
            raf.read(temp);
            deleteLength 
+=   128 ;
        }
        TagHead header 
=  checkTag(raf);
        
// 如果有标头,则说明有APE的标签,还要多删一些
         if  (header  !=   null ) {
            log.log(Level.INFO, 
" 原来存在APEv2标签,先删除之 " );
            
int  length  =  header.getTagSize();
            
if  (header.hasHeader()) { // 如果有标头的话,长度还要加32个字节
                length  +=   32 ;
            }
            deleteLength 
+=  length;
        } 
else  {
            log.log(Level.INFO, 
" 以前不存在APEv2标签,直接添加 " );
        }
        raf.setLength(raf.length() 
-  deleteLength);
        
// 把该截掉的都截了以后,就开始写标签了,先写APE的,再看
        
// 有没有ID3的,有就写,没有就不写了
        raf.seek(raf.length());
        
byte [] data  =  getTagBytes();
        raf.write(data);
        
if  (temp  !=   null ) {
            raf.write(temp);
        }
        log.log(Level.INFO, 
" APEv2标签写出完毕 " );
    }

    
/**
     * 得到标签所代表的字节数组
     * 
@return  标签所代表的字节数组
     
*/
    
private   byte [] getTagBytes()  throws  UnsupportedEncodingException, IOException {
        
int  itemCount  =  map.size();
        body 
=   new  TagBody();
        
for  (Map.Entry < String, String >  en : map.entrySet()) {
            body.addTagItem(
new  TagItem(en.getKey(), en.getValue()));
        }
        
byte [] bodyData  =  body.getBytes();
        log.log(Level.SEVERE, 
" BODYSIZE= "   +  bodyData.length);
        TagHead header 
=   new  TagHead();
        header.setFlag(TagHead.HEAD);
        header.setItemCount(itemCount);
        header.setTagSize(bodyData.length 
+   32 );
        header.setVersion(TagHead.V2);

        TagHead foot 
=   new  TagHead();
        foot.setFlag(TagHead.FOOT);
        foot.setItemCount(itemCount);
        foot.setTagSize(bodyData.length 
+   32 );
        foot.setVersion(TagHead.V2);

        ByteArrayOutputStream bout 
=   new  ByteArrayOutputStream();
        bout.write(header.getBytes());
        bout.write(bodyData);
        bout.write(foot.getBytes());
        bout.flush();
        
return  bout.toByteArray();
    }

    
/**
     * 检查是否已经存在APE的标签了,主要查两个地方
     * 一个是最后的字节,还有一个是最后128字节以上的字节
     * 因为最后的字节可能写入了ID3v1标签
     * 
@param  raf 文件
     * 
@return  得到标签头
     * 
@throws  java.io.IOException
     
*/
    
private  TagHead checkTag(RandomAccessFile raf)  throws  IOException {
        raf.seek((
int ) (raf.length()  -   32 ));
        
byte [] buffer  =   new   byte [ 32 ];
        raf.read(buffer);
        TagHead header 
=   new  TagHead(buffer);
        
if  (header.isValid()) {
            header.setIndex(
0 );
            
return  header;
        } 
else  {
            raf.seek((
int ) (raf.length()  -   32   -   128 ));
            raf.read(buffer);
            header 
=   new  TagHead(buffer);
            
if  (header.isValid()) {
                header.setIndex(
128 );
                
return  header;
            } 
else  {
                
return   null ;
            }
        }
    }

    
/**
     * 删除标签,如果存在ID3v1的话,就要先保存它然后删除后面部份
     * 把它写回来
     * 
@param  raf 写出文件
     * 
@param  hasID3v1 是否有ID3v1标签
     * 
@throws  java.io.IOException
     
*/
    
public   void  delete(RandomAccessFile raf,  boolean  hasID3v1)  throws  IOException {
        
// 如果有ID3标签,则先把它缓存起来,总共128个字节
         byte [] temp  =   null ;
        
int  deleteLength  =   0 ;
        
if  (hasID3v1) {
            temp 
=   new   byte [ 128 ];
            raf.seek(raf.length() 
-   128 );
            raf.read(temp);
            deleteLength 
+=   128 ;
        }
        TagHead header 
=  checkTag(raf);
        
// 如果有标头,则说明有APE的标签,还要多删一些
         if  (header  !=   null ) {
            log.log(Level.INFO, 
" 原来存在APEv2标签,先删除之 " );
            
int  length  =  header.getTagSize();
            
if  (header.hasHeader()) { // 如果有标头的话,长度还要加32个字节
                length  +=   32 ;
            }
            deleteLength 
+=  length;
        }
        raf.setLength(raf.length() 
-  deleteLength);
        log.log(Level.INFO, 
" APEv2标签删除完毕 " );
    }

    
public   void  add(TagField field)  throws  FieldDataInvalidException {
    }

    
public   void  addAlbum(String album)  throws  FieldDataInvalidException {
        setAlbum(album);
    }

    
public   void  addArtist(String artist)  throws  FieldDataInvalidException {
        setArtist(artist);
    }

    
public   void  addComment(String comment)  throws  FieldDataInvalidException {
        setComment(comment);
    }

    
public   void  addGenre(String genre)  throws  FieldDataInvalidException {
        setGenre(genre);
    }

    
public   void  addTitle(String title)  throws  FieldDataInvalidException {
        setTitle(title);
    }

    
public   void  addTrack(String track)  throws  FieldDataInvalidException {
        setTrack(track);
    }

    
public   void  addYear(String year)  throws  FieldDataInvalidException {
        setYear(year);
    }

    
public  List < TagField >  get(String id) {
        
return   null ;
    }

    
public  List < TagField >  getAlbum() {
        
if  (getFirstAlbum().length()  >   0 ) {
            TagItem field 
=   new  TagItem(APEv2FieldKey.Album.name(), getFirstAlbum());
            
return  returnFieldToList(field);
        } 
else  {
            
return   new  ArrayList < TagField > ();
        }
    }

    
public  List < TagField >  getArtist() {
        
if  (getFirstAlbum().length()  >   0 ) {
            TagItem field 
=   new  TagItem(APEv2FieldKey.Artist.name(), getFirstArtist());
            
return  returnFieldToList(field);
        } 
else  {
            
return   new  ArrayList < TagField > ();
        }
    }

    
public  List < TagField >  getComment() {
        
if  (getFirstAlbum().length()  >   0 ) {
            TagItem field 
=   new  TagItem(APEv2FieldKey.Comment.name(), getFirstComment());
            
return  returnFieldToList(field);
        } 
else  {
            
return   new  ArrayList < TagField > ();
        }
    }

    
public  List < TagField >  getGenre() {
        
if  (getFirstAlbum().length()  >   0 ) {
            TagItem field 
=   new  TagItem(APEv2FieldKey.Genre.name(), getFirstGenre());
            
return  returnFieldToList(field);
        } 
else  {
            
return   new  ArrayList < TagField > ();
        }
    }

    
public  List < TagField >  getTitle() {
        
if  (getFirstAlbum().length()  >   0 ) {
            TagItem field 
=   new  TagItem(APEv2FieldKey.Title.name(), getFirstTitle());
            
return  returnFieldToList(field);
        } 
else  {
            
return   new  ArrayList < TagField > ();
        }
    }

    
public  List < TagField >  getTrack() {
        
if  (getFirstAlbum().length()  >   0 ) {
            TagItem field 
=   new  TagItem(APEv2FieldKey.Track.name(), getFirstTrack());
            
return  returnFieldToList(field);
        } 
else  {
            
return   new  ArrayList < TagField > ();
        }
    }

    
public  List < TagField >  getYear() {
        
if  (getFirstAlbum().length()  >   0 ) {
            TagItem field 
=   new  TagItem(APEv2FieldKey.Year.name(), getFirstYear());
            
return  returnFieldToList(field);
        } 
else  {
            
return   new  ArrayList < TagField > ();
        }
    }

    
public  String getFirstAlbum() {
        
return   this .album;
    }

    
public  String getFirstArtist() {
        
return  artist;
    }

    
public  String getFirstComment() {
        
return  comment;
    }

    
public  String getFirstGenre() {
        
return  genre;
    }

    
public  String getFirstTitle() {
        
return  title;
    }

    
public  String getFirstTrack() {
        
return  track;
    }

    
public  String getFirstYear() {
        
return  year;
    }

    
public   boolean  hasCommonFields() {
        
return   true ;
    }

    
public   boolean  hasField(String id) {
        
throw   new  UnsupportedOperationException( " Not supported yet. " );
    }

    
public   boolean  isEmpty() {
        
throw   new  UnsupportedOperationException( " Not supported yet. " );
    }

    
public   void  set(TagField field)  throws  FieldDataInvalidException {
        TagFieldKey genericKey 
=  TagFieldKey.valueOf(field.getId());
        
switch  (genericKey) {
            
case  ARTIST:
                setArtist(field.toString());
                
break ;
            
case  ALBUM:
                setAlbum(field.toString());
                
break ;
            
case  TITLE:
                setTitle(field.toString());
                
break ;
            
case  GENRE:
                setGenre(field.toString());
                
break ;
            
case  YEAR:
                setYear(field.toString());
                
break ;
            
case  COMMENT:
                setComment(field.toString());
                
break ;
        }
    }

    
public   void  setAlbum(String s)  throws  FieldDataInvalidException {
        
this .album  =  s;
        map.put(APEv2FieldKey.Album.name(), album);
    }

    
public   void  setArtist(String s)  throws  FieldDataInvalidException {
        
this .artist  =  s;
        map.put(APEv2FieldKey.Artist.name(), s);
    }

    
public   void  setComment(String s)  throws  FieldDataInvalidException {
        
this .comment  =  s;
        map.put(APEv2FieldKey.Comment.name(), s);
    }

    
public   void  setGenre(String s)  throws  FieldDataInvalidException {
        
this .genre  =  s;
        map.put(APEv2FieldKey.Genre.name(), s);
    }

    
public   void  setTitle(String s)  throws  FieldDataInvalidException {
        
this .title  =  s;
        map.put(APEv2FieldKey.Title.name(), s);
    }

    
public   void  setTrack(String s)  throws  FieldDataInvalidException {
        
this .track  =  s;
        map.put(APEv2FieldKey.Track.name(), s);
    }

    
public   void  setYear(String s)  throws  FieldDataInvalidException {
        
this .year  =  s;
        map.put(APEv2FieldKey.Year.name(), s);
    }

    
public  TagField createTagField(TagFieldKey genericKey, String value)  throws  KeyNotFoundException, FieldDataInvalidException {
        
throw   new  UnsupportedOperationException( " Not supported yet. " );
    }

    
public  String getFirst(String id) {
        
throw   new  UnsupportedOperationException( " Not supported yet. " );
    }

    
public  String getFirst(TagFieldKey id)  throws  KeyNotFoundException {
        
throw   new  UnsupportedOperationException( " Not supported yet. " );
    }

    
public  TagField getFirstField(String id) {
        
throw   new  UnsupportedOperationException( " Not supported yet. " );
    }

    
public   void  deleteTagField(TagFieldKey tagFieldKey)  throws  KeyNotFoundException {
        
throw   new  UnsupportedOperationException( " Not supported yet. " );
    }

    
public  Iterator getFields() {
        
throw   new  UnsupportedOperationException( " Not supported yet. " );
    }

    
public   int  getFieldCount() {
        
throw   new  UnsupportedOperationException( " Not supported yet. " );
    }

    
public   boolean  setEncoding(String enc)  throws  FieldDataInvalidException {
        
return   false ;
    }

    
public  List < TagField >  get(TagFieldKey id)  throws  KeyNotFoundException {
        
throw   new  UnsupportedOperationException( " Not supported yet. " );
    }

    
public   static   void  main(String[] args)  throws  Exception {
        System.out.println(
0xD2 );
//         APEv2Tag tag = new APEv2Tag(new File("D:\\难道爱一个人有错吗.mp3"));
//         tag.load();
//         System.out.println("tag.album:" + tag.getFirstAlbum());
//         System.out.println("tag.title:" + tag.getFirstTitle());
//         System.out.println("tag.artist:" + tag.getFirstArtist());
    }
}

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 
*/
package  com.hadeslee.audiotag.tag.ape;

import  java.io.ByteArrayOutputStream;
import  java.io.IOException;
import  java.io.UnsupportedEncodingException;
import  java.util.ArrayList;
import  java.util.List;

/**
 * 内部私有类,它代表了一个APE标签的内容
 * 
@author  hadeslee
 
*/
public   class  TagBody {

    
private   byte [] data; // 标签的数据
     private  List < TagItem >  items; // 所有的项
     public  TagBody( byte [] data) {
        
this .data  =  data;
        items 
=   new  ArrayList < TagItem > ();
        parseData();
    }

    
public  TagBody() {
        items 
=   new  ArrayList < TagItem > ();
    }

    
public  List < TagItem >  getItems() {
        
return  items;
    }

    
public   byte [] getBytes()  throws  UnsupportedEncodingException, IOException {
        ByteArrayOutputStream bout 
=   new  ByteArrayOutputStream();
        
for (TagItem item:items){
            bout.write(item.getRawContent());
        }
        bout.flush();
        
return  bout.toByteArray();
    }

    
public   void  addTagItem(TagItem item) {
        items.add(item);
    }

    
private   void  parseData() {
        
int  count  =   0 ;
        
byte [] temp  =   new   byte [data.length];
        System.arraycopy(data, 
0 , temp,  0 , data.length);
        
while  (count  <  data.length) {
            TagItem item 
=   new  TagItem(temp, count);
            
if  (item.isValid()) {
                count 
+=  item.getSize();
                items.add(item);
            } 
else  {
                
return ;
            }
        }
    }
    }

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 
*/
package  com.hadeslee.audiotag.tag.ape;

import  com.hadeslee.yoyoplayer.util.Util;
import  java.util.Arrays;
import  java.util.logging.Level;
import  java.util.logging.Logger;

/**
 * 内部的私有类,它代表了一个APE标签的头部
 * 
@author  hadeslee
 
*/
public   class  TagHead {

    
private   static  Logger log  =  Logger.getLogger(TagHead. class .getName());
    
private   byte [] data; // 头部的数据
     private   boolean  valid; // 是否是合法的头部
     private   int  version  =   2000 ; // 版本,默认是2000
     private   int  tagSize; // 标签的长度,包括尾标签以及所有的项目,不包括头标签
     private   int  itemCount; // 项目的数量
     private   int  flag  =  FOOT; // 标签的其它标志,指示它是头部还是尾部
     public   static   final   int  HEAD  =   0xA0000000 ;
    
public   static   final   int  FOOT  =   0x80000000 ;
    
public   static   final   int  V1 = 1000 ; // 表示APE的版本号
     public   static   final   int  V2 = 2000 ;
    
private   int  index; // 这个标头的起始点的位置,相对于文件
     public  TagHead( byte [] data) {
        
this .data  =  data;
        parseData();
    }

    
public  TagHead() {
    }

    
public   int  getIndex() {
        
return  index;
    }

    
public   void  setIndex( int  index) {
        
this .index  =  index;
    }

    
public   byte [] getBytes() {
        
// 标头总共是32个字节
         byte [] head  =   new   byte [ 32 ];
        
byte [] temp  =  {( byte ' A ' ,
            (
byte ' P ' ,
            (
byte ' E ' ,
            (
byte ' T ' ,
            (
byte ' A ' ,
            (
byte ' G ' ,
            (
byte ' E ' ,
            (
byte ' X '
        };
        
// 先把头的标量填进去,8字节
        System.arraycopy(temp,  0 , head,  0 8 );
        temp 
=  Util.getBytesFromInt(version);
        
// 再把版本号写进去,4字节
        System.arraycopy(temp,  0 , head,  8 4 );
        temp 
=  Util.getBytesFromInt(tagSize);
        log.log(Level.SEVERE,
" TAGSIZE= " + tagSize);
        
// 再把标签的长度写进去,4字节
        System.arraycopy(temp,  0 , head,  12 4 );
        temp 
=  Util.getBytesFromInt(itemCount);
        
// 再把标签的数量写进去,4字节
        System.arraycopy(temp,  0 , head,  16 4 );
        temp 
=  Util.getBytesFromInt(flag);
        
// 再把标志写进去,表示是标签头部还是尾部
        System.arraycopy(temp,  0 , head,  20 4 );
        
// 把标志空的8个字节进去,因为默认就是空的,所以不用写了
        
// 头部或者尾部的数据块已经构造好了
         return  head;
    }

    
private   void  parseData() {
        
try  {
            checkHead();
            checkVersion();
            checkTagSize();
            checkItemCount();
            checkFlag();
            valid 
=   true ;
        } 
catch  (Exception exe) {
            log.log(Level.SEVERE, 
" 分析标签异常! " );
            valid 
=   false ;
        }
    }

    
/**
     * 这个标签是否有头标签,因为一般读都是从尾部读过去的
     * 
@return  是否有头标签,重写的时候有用
     
*/
    
public   boolean  hasHeader() {
        
return  (( 1   <<   31 &  flag)  !=   0 ;
    }

    
/**
     * 检查头部八个字节的的数据是否一样
     
*/
    
private   void  checkHead() {
        
byte [] temp  =   new   byte [ 8 ];
        
byte [] head  =  {( byte ' A ' ,
            (
byte ' P ' ,
            (
byte ' E ' ,
            (
byte ' T ' ,
            (
byte ' A ' ,
            (
byte ' G ' ,
            (
byte ' E ' ,
            (
byte ' X '
        };
        System.arraycopy(data, 
0 , temp,  0 8 );
        
// 比较两个头部的数据是否一样,这是第一要素
         if  ( ! Arrays.equals(head, temp)) {
            
throw   new  RuntimeException( " 头部数据不一样! " );
        }
    }

    
/**
     * 检查版本号是否合法,必须是1000或者2000
     
*/
    
private   void  checkVersion() {
        
byte [] temp  =   new   byte [ 4 ];
        System.arraycopy(data, 
8 , temp,  0 4 );
        
int  v  =  Util.getInt(temp);
        
if  (v  ==   2000   ||  v  ==   1000 ) {
            version 
=  v;
            log.log(Level.INFO, 
" 版本号是: "   +  v);
        } 
else  {
            
throw   new  RuntimeException( " 版本号不合法!! " );
        }
    }

    
private   void  checkTagSize() {
        
byte [] temp  =   new   byte [ 4 ];
        System.arraycopy(data, 
12 , temp,  0 4 );
        tagSize 
=  Util.getInt(temp);
        log.log(Level.INFO, 
" 标签大小: "   +  tagSize);
    }

    
private   void  checkItemCount() {
        
byte [] temp  =   new   byte [ 4 ];
        System.arraycopy(data, 
16 , temp,  0 4 );
        itemCount 
=  Util.getInt(temp);
        log.log(Level.INFO, 
" 标签项目数: "   +  itemCount);
    }

    
private   void  checkFlag() {
        
byte [] temp  =   new   byte [ 4 ];
        System.arraycopy(data, 
20 , temp,  0 4 );
        flag 
=  Util.getInt(temp);
        log.log(Level.INFO, 
" 标志: "   +  flag);
    }

    
public   void  setFlag( int  flag) {
        
this .flag  =  flag;
    }

    
public   void  setItemCount( int  itemCount) {
        
this .itemCount  =  itemCount;
    }

    
public   void  setTagSize( int  tagSize) {
        
this .tagSize  =  tagSize;
    }

    
public   void  setVersion( int  version) {
        
if ( ! (version == V1 || version == V2)){
            
throw   new  RuntimeException( " 非法的版本号,只能是V2或者V1. " );
        }
        
this .version  =  version;
    }

    
public   int  getFlag() {
        
return  flag;
    }

    
public   int  getItemCount() {
        
return  itemCount;
    }

    
public   int  getTagSize() {
        
return  tagSize;
    }

    
public   boolean  isValid() {
        
return  valid;
    }

    
public   int  getVersion() {
        
return  version;
    }

    
public   static   void  main(String[] args) {
    }
    }


/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 
*/
package  com.hadeslee.audiotag.tag.ape;

import  com.hadeslee.audiotag.tag.TagField;
import  com.hadeslee.audiotag.tag.TagTextField;
import  com.hadeslee.yoyoplayer.util.Util;
import  java.io.UnsupportedEncodingException;
import  java.util.logging.Logger;

/**
 *
 * 
@author  hadeslee
 
*/
public   class  TagItem  implements  TagTextField {

    
private   static  Logger log  =  Logger.getLogger(TagItem. class .getName());
    
private   boolean  common;
    
private  String id;
    
private  String content;
    
private   boolean  valid; // 是否合法
     private   int  length; // 该项的内容的长度
     private   int  flag; // 该项的标志,表明内容是什么,可能是UTF-8字符串也可能是二进制数据
     private   int  size; // 这个项用了多少个字节
     private   byte [] raw;

    
public  TagItem(String id, String content) {
        
this .id  =  id;
        
this .content  =  content;
        valid 
=   true ;
        checkCommon();
    }

    
public  TagItem( byte [] raw,  int  offset) {
        parseData(raw, offset);
    }

    
public   boolean  isValid() {
        
return  valid;
    }

    
public   int  getSize() {
        
return  size;
    }

    
private   void  parseData( byte [] data,  int  offset) {
        
try  {
            
byte [] temp  =   new   byte [ 4 ];
            System.arraycopy(data, offset, temp, 
0 4 );
            length 
=  Util.getInt(temp);
            System.arraycopy(data, offset 
+   4 , temp,  0 4 );
            flag 
=  Util.getInt(temp);

            
int  count  =   0 ;
            size 
+=   8 ;
            size 
+=  length;
            
for  ( int  i  =   8   +  offset; i  <  data.length; i ++ ) {
//                 if(data[i]>=0x20&&data[i]<=0x7E){}
                
// 只要数据不是0,就一直到后面去
                 if  (data[i]  ==   0x00 ) {
                    
break ;
                } 
else  {
                    count
++ ;
                }
            }
            id 
=   new  String(data,  8   +  offset, count,  " UTF-8 " );
            
// 加上一个空白
            count ++ ;
            size 
+=  count;
            content 
=   new  String(data,  8   +  count  +  offset, length,  " UTF-8 " );
            valid 
=   true ;
            checkCommon();
        } 
catch  (Exception ex) {
            valid 
=   false ;
        }
    }

    
private   void  checkCommon() {
        
this .common  =  id.equals(APEv2FieldKey.Title.name())  ||
                id.equals(APEv2FieldKey.Album.name()) 
||
                id.equals(APEv2FieldKey.Artist.name()) 
||
                id.equals(APEv2FieldKey.Genre.name()) 
||
                id.equals(APEv2FieldKey.Year.name()) 
||
                id.equals(APEv2FieldKey.Comment.name()) 
||
                id.equals(APEv2FieldKey.Track.name());
    }

    
public  String getContent() {
        
return   this .content;
    }

    
public  String getEncoding() {
        
return   " UTF-8 " ;
    }

    
public   void  setContent(String content) {
        
this .content  =  content;
    }

    
public   void  setEncoding(String encoding) {
    
// 什么都不做,因为APE的标签必须是UTF-8编码
    }

    
public   void  copyContent(TagField field) {
        
if  (field  instanceof  TagTextField) {
            
this .content  =  ((TagTextField) field).getContent();
        }
    }

    
public  String getId() {
        
return  id;
    }

    
public   byte [] getRawContent()  throws  UnsupportedEncodingException {
        
int  index  =   0 ;
        
byte [] idData  =  id.getBytes( " UTF-8 " );
        
byte [] contentData  =  content.getBytes( " UTF-8 " );
        raw 
=   new   byte [ 9   +  idData.length  +  contentData.length];
        
byte [] temp  =  Util.getBytesFromInt(contentData.length);
        
// 本项目数据部份的长度,4字节
        System.arraycopy(temp,  0 , raw, index,  4 );
        index 
+=   4 ;
        temp 
=   new   byte [ 4 ];
        
// 中间4个字节留空
        System.arraycopy(temp,  0 , raw, index,  4 );
        index 
+=   4 ;
        
// 项目的键值的字节数组
        System.arraycopy(idData,  0 , raw, index, idData.length);
        index 
+=  idData.length;
        
// 一个固定的空白分隔符,跳过一个字节,因为默认就是空白的
        index  +=   1 ;
        
// 项目的内容
        System.arraycopy(contentData,  0 , raw, index, contentData.length);
        
return  raw;
    }

    
public   boolean  isBinary() {
        
return   false ;
    }

    
public   void  isBinary( boolean  b) {

    }

    
public   boolean  isCommon() {
        
return  common;
    }

    
public   boolean  isEmpty() {
        
return  content  ==   null   ||  content.equals( "" );
    }

    
public  String toString() {
        
return  id  +   " : "   +  content;
    }
}

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 
*/
package  com.hadeslee.audiotag.tag.ape;

/**
 *
 * 
@author  hadeslee
 
*/
public   enum  APEv2FieldKey {

    Artist,
    Album,
    Genre,
    Title,
    Year,
    Track,
    Comment

}

以上便是 APEv2 格式标签的读写类 , 并且在 jaudiotagger 里面的 MP3File 类里面做了相应的修改 , 以期能统一管理这些标签


尽管千里冰封
依然拥有晴空

你我共同品味JAVA的浓香.

你可能感兴趣的:(YOYOPlayer开发手记(三)APEv2标签读写)