// AacTagUtil.java
// minidlnaのCソースをJavaに置き換えたものです。
// 使い方
// File _file = new File("ファイルパス");
// AacTagUtil.SongMetaData _psong=new AacTagUtil.SongMetaData();
// AacTagUtil.get_aactags(file_,_psong,AacTagUtil.OMIT_IMAGE_DATA);
// _psongにタグ情報が得られる。OMIT_IMAGE_DATAを指定しなければ
// _psong.image_dataにイメージデータがセットされる。
// AACタグフォーマットと異なるファイルの場合titleがnullになる。
// (AACフォーマットだがタイトルが無い場合はtitleに"-"が入る)
import java.io.*;
import java.util.*;
public class AacTagUtil {
public static class SongMetaData {
String file_path;
String title;
String album;
String comment;
String grouping;
String genre;
int year;
int bpm;
int track;
int total_tracks;
int disc;
int total_discs;
boolean compilation;
String artist;
String album_artist;
String conductor;
String composer;
int image_size;
byte[] image_data;
}
public static int OMIT_IMAGE_DATA=0x01;// イメージデータを読みこまない
//
final static boolean D=false;// デバグフラグ
final static String winamp_genre[] = {
"Blues","Classic Rock","Country","Dance",//00
"Disco","Funk","Grunge","Hip-Hop",
"Jazz","Metal","New Age","Oldies",//08
"Other","Pop","R&B","Rap",
"Reggae","Rock","Techno","Industrial",//10
"Alternative","Ska","Death Metal","Pranks",
"Soundtrack","Euro-Techno","Ambient","Trip-Hop",//18
"Vocal","Jazz+Funk","Fusion","Trance",
"Classical","Instrumental","Acid","House",//20
"Game","Sound Clip","Gospel","Noise",
"AlternRock","Bass","Soul","Punk",//28
"Space","Meditative","Instrumental Pop","Instrumental Rock",
"Ethnic","Gothic","Darkwave","Techno-Industrial",//30
"Electronic","Pop-Folk","Eurodance","Dream",
"Southern Rock","Comedy","Cult","Gangsta",//38
"Top 40","Christian Rap","Pop/Funk","Jungle",
"Native American","Cabaret","New Wave","Psychedelic",//40
"Rave","Showtunes","Trailer","Lo-Fi",
"Tribal","Acid Punk","Acid Jazz","Polka",//48
"Retro","Musical","Rock & Roll","Hard Rock",
"Folk","Folk/Rock","National folk","Swing",//50
"Fast-fusion","Bebob","Latin","Revival",
"Celtic", "Bluegrass","Avantgarde","Gothic Rock",//58
"Progressive Rock","Psychedelic Rock","Symphonic Rock","Slow Rock",
"Big Band","Chorus","Easy Listening","Acoustic",//60
"Humour","Speech","Chanson","Opera",
"Chamber Music","Sonata", "Symphony","Booty Bass",//68
"Primus","Porn Groove","Satire","Slow Jam",
"Club","Tango","Samba","Folklore",//70
"Ballad","Powder Ballad","Rhythmic Soul","Freestyle",
"Duet","Punk Rock","Drum Solo","A Capella",//78
"Euro-House","Dance Hall","Goa","Drum & Bass",
"Club House","Hardcore","Terror","Indie",//80
"BritPop","NegerPunk","Polsk Punk","Beat",
"Christian Gangsta","Heavy Metal","Black Metal","Crossover",//88
"Contemporary C","Christian Rock", "Merengue","Salsa",
"Thrash Metal","Anime","JPop","SynthPop",//90
"Unknown"
};
static class LongBox{
public long value;
}
public static void skip(RandomAccessFile raf_,long len_)throws Exception{
// skiptByteの引数がintという謎の仕様のため
long _rem=len_;
while( _rem>0 ){
if( _rem<0x7FFFFFFF ){
_rem -= raf_.skipBytes((int)_rem);
}
else{
_rem -= raf_.skipBytes(0x7FFFFFFF);
}
}
}
static int toInt2(byte[] buf_,int index_){
// &0xFFはbyteが符号付きという謎の仕様のため
// 括弧は"<<"の連結が弱いという謎の仕様のため
return ((buf_[index_]&0xFF) << 8) | (buf_[index_+1]&0xFF);
}
static long toInt4(byte[] buf_){
// unsigned intがないという謎の仕様のためlong使う
return (((long)(buf_[0]&0xFF))<<24L)|((buf_[1]&0xFF)<<16)
|((buf_[2]&0xFF)<<8) |(buf_[3]&0xFF);
}
static long aac_findatom(RandomAccessFile raf_
,long max_offset_
,String which_atom_
,LongBox atom_size_
)throws Exception{
long _current_offset = 0;
byte _cbuf[]={0,0,0,0};
while(_current_offset < max_offset_){
if( raf_.read(_cbuf)!=4 ) return -1;
long _size= toInt4(_cbuf);
if(_size<8) return -1;
if( raf_.read(_cbuf)!=4 ) return -1;
String _atom= new String(_cbuf);
if( _atom.equals(which_atom_) ){
atom_size_.value= _size;
return _current_offset;
}
skip(raf_,_size-8);
_current_offset += _size;
}
return -1;
}
static long aac_lookforatom(File aac_file_
,RandomAccessFile aac_fp_
,String atom_path_
,LongBox atom_length_
)throws Exception{
String _atom_path_list[]= atom_path_.split("\\:");
long _file_size= aac_fp_.length();
aac_fp_.seek(0L);
long _atom_offset;
for(int _idx=0;_idx<_atom_path_list.length;++_idx){
String _atom_name= _atom_path_list[_idx];
_atom_offset = aac_findatom(aac_fp_,_file_size
,_atom_name,atom_length_);
if( _idx<(_atom_path_list.length-1) ){
// 次がある
switch(_atom_name){
case "meta":
aac_fp_.skipBytes(4);
break;
case "stsd":
aac_fp_.skipBytes(8);
break;
case "mp4a":
aac_fp_.skipBytes(28);
break;
}
}
}
return aac_fp_.getFilePointer()-8;
}
static long get_aactags(File file_
,SongMetaData song_
)throws Exception{
return get_aactags(file_,song_,0);
}
static long get_aactags(File file_
,SongMetaData song_
,int option_
)throws Exception{
long _current_offset = 0;
song_.file_path= file_.getAbsolutePath();
try(RandomAccessFile _raf= new RandomAccessFile(file_,"r");){
// 先頭のタグ"ftyp"のみ別途チェックする
byte _hbuf[]={0,0,0,0, 0,0,0,0};
if( _raf.read(_hbuf)!=8 ) return -1;
if( _hbuf[4]!=0x66||_hbuf[5]!=0x74||_hbuf[6]!=0x79||_hbuf[7]!=0x70 ){
if(D)System.out.println("not aac-tag format");
song_.comment="not aac-tag format";
return -1;
}
song_.title="-";// 「aac-tagとして解釈はした」ということを表す
//
_raf.seek(0L);
byte _cbuf[]={0,0,0,0};
LongBox _atom_length=new LongBox();
long _atom_offset=aac_lookforatom(file_,_raf
,"moov:udta:meta:ilst",_atom_length);
if(D)System.out.println(file_.getName()
+" "+_atom_offset+" "+_atom_length.value);
if( _atom_offset != -1 ){
while( _current_offset < _atom_length.value){
if( _raf.read(_cbuf)!=4 ) break;
long _current_size = toInt4(_cbuf);
if( _current_size <= 7 || _current_size > (1<<24) ){
break;
}
byte _current_atom_buf[]={0,0,0,0};
if( _raf.read(_current_atom_buf)!=4 ) break;
String _current_atom=new String(_current_atom_buf);
String _current_atom2=new String(_current_atom_buf,1,3);
int _len= (int)_current_size - 7;
if( _len<22 ) _len=22;
byte _data[]=new byte[_len];
long _rlen= _current_size - 8;
if( _raf.read(_data,0,(int)_rlen)!=_rlen ){
break;
}
_data[(int)((long)_current_size - 8L)] = 0;
if( (_current_atom_buf[0]&0xFF)==0xA9 ){// &FFに注意
String _text=new String(_data,16,_len-16,"utf-8");
switch(_current_atom2){
case "nam":
song_.title=_text;
break;
case "ART":
song_.artist=_text;
break;
case "alb":
song_.album =_text;
break;
case "cmt":
song_.comment =_text;
break;
case "dir":
song_.conductor=_text;
break;
case "wrt":
song_.composer=_text;
break;
case "grp":
song_.grouping=_text;
break;
case "gen":
song_.genre=_text;
break;
case "day":
song_.year=Integer.parseInt(_text.trim());
break;
}
}
else{
switch(_current_atom){
case "aART":
String _text=new String(_data,16,_len-16,"utf-8");
song_.album_artist= _text;
break;
case "tmpo":
song_.bpm = toInt2(_data,16);
break;
case "trkn":
song_.track = toInt2(_data,18);
song_.total_tracks = toInt2(_data,20);
break;
case "disk":
song_.disc = toInt2(_data,18);
song_.total_discs = toInt2(_data,20);
break;
case "gnre":
int _genre = _data[17] - 1;
if((_genre < 0) || (_genre >= winamp_genre.length)){
_genre = winamp_genre.length-1;
}
song_.genre = winamp_genre[_genre];
break;
case "cpil":
song_.compilation = _data[16]!=0;
break;
case "covr":
song_.image_size = (int)_current_size - 8 - 16;
if( (option_&OMIT_IMAGE_DATA)==0 ){
song_.image_data= Arrays.copyOfRange(_data,16
,song_.image_size);
}
break;
}
}
_current_offset += _current_size;
}
}
}
return _current_offset;
}
}