2 안드로이드뮤직플레이어 Intent 를활용한 ID3 태그에디터구현안드로이드용 ID3 태그에디터개발 스마트폰에서뮤직플레이어를사용하면서노래제목과가수이름의글이깨져서보인경험이있는가? 필자는 MP3 의메타정보인 ID3 태그를편집할수있는에디터를만들어사용자의불편을개선하는애플리케이션을만들어배포하고있다. 이번호에서는 ID3 태그에디터를만들어보면서안드로이드에서음악정보를수정할수있는방법을알아보자. 연재순서 1 회 2011. 1 안드로이드뮤직플레이어분석 2 회 2011. 2 안드로이드용 ID3 태그에디터개발 진성주 moleskine7@gmail.com, http://softwaregeeks.org 호기심왕성하고생활속자동화를실천하려는개발자다. 검색엔진, 대용량분산처리, 성능튜닝에관심이많다. 현재는삼성소프트웨어멤버십 20기, SW 마에스트로 1기회원으로활동하고있다. 일상생활에서 MP3 파일을많이접한다. 음악을재생하려고윈도우탐색기를열면쉽게음악정보를확인할수있다. 윈도우탐색기에표시되는제목, 참여음악가, 앨범등의정보를음악태그라고한다. ID3 태그의이해 ID3 를위키피디아에서찾아보면 MP3 파일에서사용하는메타데이터포맷으로, 음악의제목, 음악가이름등의음악파일에관련된정보를담는다 고정의돼있다. < 화면 1> 윈도우탐색기에서음악정보 많은애플리케이션이음악정보들을활용한다. 안드로이드뮤직플레이어에서음악이재생되는동안나타나는제목과앨범이미지가대표적이다. 이러한음악정보들은어디에있는것일까? 파일내부에기록돼있다. 즉, MP3 파일내부에가수명, 앨범명, 앨범이미지가존재하는것이다. ID3라고것은음악파일내부에 어떤정보가들어있으며어느위치에존재하는지를정의해놓은것이다. ID3v1은 1996년에고안됐으며사용자의요구사항에맞춰 ID3v1, ID3v1.1, ID3v2.2, ID3v2.3, ID3v2.4 형태로발전돼왔다. ID3의각버전마다지원하는정보의양과처리방법이다르다. 우리가기억해야할것은음악정보가파일에기록돼있고 ID3 각버전마다기록방법이조금씩다르다는것이다. ID3의상세한내용을알고싶다면 http://www.id3.org를방문하면도움이될것이다. ID3 태그라이브러리 ID3 태그정보는파일에기록돼있고버전마다기록되는방식이조금씩다르다. 다양한파일의음악정보를수정하려면모든버전에대해음악정보기록방식을이해해야한다. 이프로젝트의목표는태그라이브러리를만드는것이아닌사용자에게유용한애플리케이션을제공하는것이므로라이브러리를찾는것이현명하다. 수많은 ID3 태그라이브러리가존재하는데, 이프로젝트에서선택할라이브러리의기준을정해보자. 1) ID3 버전지원범위는어떻게되는가? 2) 안드로이드애플리케이션과통합하는데유용한언어인가? ( 자바또는 C/C++) 3) 라이브러리가배포된일자는언제인가? 206 m a s o
안드로이드애플리케이션과통합하는데는자바가편하므로대표적인두가지라이브러리를비교해보자. 자바 ID3 태그라이브러리 jaudiotagger ID3v1, ID3v1.1, Lyrics3v1, Mp3, Mp4 (Mp4 오디오, M4a 지원범위 Lyrics3v2, ID3v2.2, ID3v2.3, and M4p 오디오 ) Ogg, wma and ID3v2.4 tags 개발언어 자바 자바 최근버전 2006. 3. 12 2010. 12. 14 < 표 1> 라이브러리비교 안드로이드에서테스트하기위한환경구축이클립스의퍼스펙티브바에서 DDMS를선택하면오른쪽상단에 File Browser를볼수있다. 오른쪽상단에서휴대폰모양의아이콘을선택하면 Push a file onto the device 라는메뉴를볼수있는데, 비활성화되어있다면에뮬레이터가구동돼있는지확인하자. 라이브러리를선택할때최근활동을파악해계속적인지원을받을수있는지확인해야한다. Jaudiotagger 쪽이계속적으로활동하고있고다양한형식을지원하므로라이브러리를 jaudiotagger로선택하자. Jaudiotagger는자바로만들어진 ID3 태그라이브러리다. Mp3, Mp4(Mp4 오디오, M4a and M4p 오디오 ), Ogg, wma까지지원되며, 라이선스는 LGPL(The GNU Lesser General Public License) 이다. 간단한 API를사용해음악파일의정보변경이가능하다. 배포사이트인 www.jthink.net/jaudiotagger에방문하면더많은정보를확인할수있다. 사용될라이브러리를다운받아몇가지예제를만들어보며라이브러리의사용법을익히자. < 화면 3> DDMS 해당아이콘을클릭한후원하는파일을선택하면파일이옮겨지는것을볼수있다. 여기에서는 /sdcard/slowmotion.mp3 파일로복사했다. < 화면 4> DDMS FileBrowser 파일을옮기면 < 화면 4> 와같은화면을볼수있다. 이로써에뮬레이터에서테스트하기위한환경이구축됐다. < 화면 2> jaudiotagger 배포사이트 프로젝트생성안드로이드프로젝트를생성하자. < 화면 5> 는이해를돕기위해정보를기입한화면이다. 1) 사이트접속 http://www.jthink.net/jaudiotagger/ 2) Downloads Releases 선택 3) 2.0.4-SNAPSHOT 폴더이동 4) jaudiotagger-2.0.4-snapshot.jar 파일다운로드 Documentation Code Examples Read Metadata 메뉴를참고해 MP3 파일의음악정보를읽고쓰는예제를작성해보자. 실제안드로이드용단말기에서테스트하면좋겠지만단말기가없는사람들을위해에뮬레이터에서테스트를진행할것이다. 먼저테스트할 MP3 파일을에뮬레이터에옮겨야테스트할수있다. < 화면 5> 프로젝트생성 jaudiotagger 라이브러리사용해음악정보읽기앞서다운로드한 jaudiotagger-2.0.4-snapshot.jar 파일을 classpath로지정한다음예제를실행한다. m a s o 207
실전강의실 안드로이드용 ID3 태그에디터개발 < 리스트 1> jaudiotagger 라이브러리사용해음악정보읽기 (1) src/org/softwaregeeks/tagreadactivity.java import java.io.*; import org.jaudiotagger.audio.*; import org.jaudiotagger.audio.exceptions.*; import org.jaudiotagger.tag.*; import android.app.activity; import android.os.bundle; import android.util.log; import android.widget.textview; public class TagReadActivity extends Activity private static final String TAG = "TagEditor"; private TextView titletextview; private TextView artisttextview; private TextView albumtextview; public void oncreate(bundle savedinstancestate) super.oncreate(savedinstancestate); setcontentview(r.layout.tag_read); titletextview = (TextView) findviewbyid(r.id.titletextview); artisttextview = (TextView) findviewbyid(r.id.artisttextview); albumtextview = (TextView) findviewbyid(r.id.albumtextview); String path = "/sdcard/slowmotion.mp3"; read(path); public void read(string path) try Filefile=newFile(path); AudioFile audiofile = AudioFileIO.read(file); Tag tag = audiofile.gettag(); catch (TagException e) catch (ReadOnlyFileException e) catch (InvalidAudioFrameException e) < 리스트 2> jaudiotagger 라이브러리사용해음악정보읽기 (2) res/layout/tag_read.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com /apk/res/android" android:orientation="vertical" android:layout_width= "fill_parent" android:layout_height="fill_parent"> <TextView android:id="@+id/titletextview" <TextView android:id="@+id/artisttextview" <TextView android:id="@+id/albumtextview" </LinearLayout> < 리스트 1> 은간단하게 TextView 3개를배치해음악정보를출력하는액티비티다. 여기에서는곡명, 가수, 앨범명을출력해봤다. 핵심코드는굵게표시된부분인데, AudioFileIO.read 메소드를사용해간단히음악정보를가져온후원하는정보만사용할수있다. 액티비티를실행하면음악정보가출력된것을확인할수있다. String title = tag.getfirst(fieldkey.title); String artist = tag.getfirst(fieldkey.artist); String album = tag.getfirst(fieldkey.album); titletextview.settext(title); artisttextview.settext(artist); albumtextview.settext(album); Log.i(TAG,"title : " + title); Log.i(TAG,"artist : " + artist); Log.i(TAG,"album : " + album); catch (CannotReadException e) catch (IOException e) < 화면 6> 실행화면 208 m a s o
jaudiotagger 라이브러리사용해음악정보쓰기 < 리스트 3> jaudiotagger 라이브러리사용해음악정보쓰기 (1) src/org/softwaregeeks/tagwriteactivity.java import java.io.*; import org.jaudiotagger.audio.*; import org.jaudiotagger.audio.exceptions.*; import org.jaudiotagger.tag.*; import android.app.activity; import android.os.bundle; import android.widget.textview; public class TagWriteActivity extends Activity private TextView beforetitletextview; private TextView beforeartisttextview; private TextView beforealbumtextview; private TextView aftertitletextview; private TextView afterartisttextview; private TextView afteralbumtextview; public void oncreate(bundle savedinstancestate) super.oncreate(savedinstancestate); setcontentview(r.layout.tag_write); beforetitletextview = (TextView) findviewbyid(r.id.beforetitletextview); beforeartisttextview = (TextView) findviewbyid(r.id.beforeartisttextview); beforealbumtextview = (TextView) findviewbyid(r.id.beforealbumtextview); aftertitletextview = (TextView) findviewbyid(r.id.aftertitletextview); afterartisttextview = (TextView) findviewbyid(r.id.afterartisttextview); afteralbumtextview = (TextView) findviewbyid(r.id.afteralbumtextview); String path = "/sdcard/slowmotion.mp3"; write(path); public void write(string path) try File file = new File(path); AudioFile audiofile = AudioFileIO.read(file); Tag tag = audiofile.gettag(); String title = tag.getfirst(fieldkey.title); String artist = tag.getfirst(fieldkey.artist); String album = tag.getfirst(fieldkey.album); beforetitletextview.settext(title); beforeartisttextview.settext(artist); beforealbumtextview.settext(album); tag.setfield(fieldkey.album,"album"); AudioFileIO.write(audioFile); album = tag.getfirst(fieldkey.album); aftertitletextview.settext(title); afterartisttextview.settext(artist); afteralbumtextview.settext(album); catch (CannotReadException e) catch (IOException e) catch (TagException e) catch (ReadOnlyFileException e) catch (InvalidAudioFrameException e) catch (CannotWriteException e) < 리스트 4> jaudiotagger 라이브러리사용해음악정보쓰기 (2) res/layout/tag_write.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com /apk/res/android" android:orientation="vertical" android:layout_width= "fill_parent" android:layout_height="fill_parent"> <TextView android:id="@+id/beforetitletextview" <TextView android:id="@+id/beforeartisttextview" <TextView android:id="@+id/beforealbumtextview" <TextView android:id="@+id/aftertitletextview" <TextView android:id="@+id/afterartisttextview" <TextView android:id="@+id/afteralbumtextview" </LinearLayout> TagReadActivity에음악정보를수정하는코드만추가했다. 수정전후의정보를보며확인할수있는 TextView를추가했다. 이예제에서는외부저장장치 (sdcard) 에접근해파일을사용하 m a s o 209
실전강의실 안드로이드용 ID3 태그에디터개발 는데, 안드로이드에서는권한을설정해야정상적으로동작한다는것에유의하자. < 리스트 5> 외부저장장치권한설정 AndroidManifest.xml 중략 <uses-permission android:name="android.permission. WRITE_EXTERNAL_STORAGE"></uses-permission> 중략 Tip 간단한태그의읽기와수정은해당코드를사용하면안드로이드애플리케이션에도정상적으로동작한다. 하지만 Logcat 창을살펴보면오류가난것을볼수있다. 태그정보에간단히정보를설정한후 AudioFileIO.write 메소드를사용하면의도한대로음악정보가변경된다. 실행해실제로변경되는지확인해보자. < 화면 8> Logcat 오류화면 Jaudiotagger에서는음악정보중앨범아트를처리하기위해자바 Swing 패키지와 Awt 패키지를사용했는데안드로이드에는두패키지가없으므로오류메시지가출력된다. Jaudiotagger를사용해앨범아트를변경하고싶다면직접라이브러리의소스코드를받아두패키지의의존성을제거해야한다. 예를들어 org.jaudiotagger.tag.asf.abstractasftagimagefield.java를살펴보면 BufferedImage를 Bitmap 으로변경하는작업이필요하다. 원본 public BufferedImage getimage() throws IOException return ImageIO.read(new ByteArrayInputStream (getrawimagedata())); < 화면 7> 실행화면 지금까지 MP3 파일의음악정보를읽고쓰는간단한예제를살펴봤다. jaudiotagger 라이브러리를사용하면정말쉽게음악파일의정보를수정할수있다. ContentProvider 이해콘텐트프로바이더는특정애플리케이션에상관없이사용하는공통의데이터 ( 연락처, 사진, 음악, 동영상, 북마크목록, 통화목록등 ) 를안드로이드플랫폼에서조회 입력 수정 삭제할수있게만든것이다. 실제단말장치에는수백, 수천곡까지있을수있고사용자들은많은곡중원하는곡을선택해정보를수정해야한다. 수천곡의정보를불러온후리스트로나열하는경우를생각해보자. 앞서살펴본 jaudiotagger 라이브러리를사용하면정상적으로동작하겠지만너무느려사용자들이불평을할것이다. 콘텐트프로바이더는내외장저장장치의미디어파일에서미리정보를읽어공용데이터베이스에기록한다. 많은곡을한번에조회하려면콘텐트프로바이더를이용하는것이파일을개별로읽어서처리하는것보다훨씬빠르다. 콘텐트프로바이더를이용해음악목록을리스트로출력해보자. 수정 public Bitmap getimage() throws IOException ByteArrayInputStream in = new ByteArrayInputStream (getrawimagedata()); Bitmap bitmap = BitmapFactory.decodeStream(in); return bitmap; 꽤많은소스변경이필요하기때문에미리변경해둔 http://co de.google.com/p/needletagger/ 소스를참고하면도움이될것이다. 음악정보를담을데이터객체만들기 < 리스트 6> 음악정보를담을데이터객체 src/org/softwaregeeks/music.java public class Music private Long id; private Long albumid; private String album; private String track; 210 m a s o
private String artist; public void setartist(string artist) this.artist = artist; public String getartist() return artist; public void settrack(string track) this.track = track; public String gettrack() return track; public void setalbum(string album) this.album = album; public String getalbum() return album; public void setid(long id) this.id = id; public Long getid() return id; public void setalbumid(long albumid) this.albumid = albumid; public Long getalbumid() return albumid; 콘텐트프로바이더를사용해음악의정보를가져온후저장할데이터객체를만들어보자. 콘텐트프로바이더는 RDB인 sqlite 를사용해데이터를관리하는데내부적으로데이터 ID 컬럼을만들어관리한다. 그값을위한 id, 앨범 ID, 앨범명, 곡명, 가수명을저장하고있다. 리스트액티비티만들기 < 리스트 7> 리스트액티비티 src/org/softwaregeeks/musiclistactivity.java import java.util.arraylist; import android.app.activity; import android.content.contentresolver; import android.content.context; import android.database.cursor; import android.net.uri; import android.os.bundle; import android.provider.mediastore; import android.widget.listview; public class MusicListActivity extends Activity private ArrayList<Music> musiclist; private MusicListAdapter listadapter; private ListView listview; static String[] columns = new String[] "audio._id AS _id", MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.ALBUM, MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DATA, MediaStore.Audio.Media.MIME_TYPE, MediaStore.Audio.Media.ALBUM_ID ; public void oncreate(bundle savedinstancestate) super.oncreate(savedinstancestate); setcontentview(r.layout.list); musiclist = getall(this,"");...(1) listview = (ListView) findviewbyid(r.id.listview); listadapter = new MusicListAdapter(this,R.layout. list_row, musiclist); listview.setadapter(listadapter); public static ArrayList<Music> getall(context context,string where) ArrayList<Music> list = new ArrayList<Music>(); Cursor c = query(context, MediaStore.Audio. Media.EXTERNAL_CONTENT_URI, columns, where, null, null); try if (c == null c.getcount() == 0) return list; c.movetofirst(); for (int i = 0; i < c.getcount(); i++) Music music = new Music(); music.setid(c.getlong(0)); music.setartist(c.getstring(1)); music.setalbum(c.getstring(2)); music.settrack(c.getstring(3)); music.setalbumid(c.getlong(6)); list.add(music); c.movetonext(); finally if (c!= null) c.close(); m a s o 211
실전강의실 안드로이드용 ID3 태그에디터개발 return list; public static Cursor query(context context,uri uri,string[] projection, String selection, String[] selectionargs, String sortorder, int limit) try ContentResolver resolver = context.getcontentresolver(); if (resolver == null) return null; if (limit > 0) uri = uri.buildupon().appendqueryparameter ("limit", "" + limit).build(); return resolver.query(uri, projection, selection, selectionargs, sortorder); catch (UnsupportedOperationException ex) return null; public static Cursor query(context context, Uri uri, String[] projection, String selection, String[] selectionargs, String sortorder) return query(context, uri, projection, selection, selectionargs, sortorder, 0); 소스가복잡하게보일수도있다. 콘텐트프로바이더에서원하는데이터를가져와리스트뷰로출력하는소스다. 핵심코드는굵게표시했는데 ContentResolver의 query 메소드를사용해원하는컬럼과조건을파라미터로넘겨데이터를가져온다. (1) 의코드를실행하면서부터이러한과정이진행된다. 리스트어댑터만들기 < 리스트 8> 리스트어댑터 src/org/softwaregeeks/musiclistadapter.java import java.util.arraylist; import android.content.context; import android.graphics.typeface; import android.view.layoutinflater; import android.view.view; import android.view.viewgroup; import android.widget.baseadapter; import android.widget.imageview; import android.widget.textview; public class MusicListAdapter extends BaseAdapter Typeface typeface; ArrayList<Music> list; LayoutInflater inflater; Context context; int layout; public MusicListAdapter(Context context, int layout, ArrayList<Music> items) this.context = context; this.inflater = (LayoutInflater)context.getSystemService(Context. LAYOUT_INFLATER_SERVICE); this.list = items; this.layout = layout; public int getcount() return list.size(); public Object getitem(int position) return list.get(position).getid(); public long getitemid(int position) return position; static class ViewHolder ImageView nowplayimageview; TextView tracktextview; TextView artisttextview; public View getview(int position, View convertview, ViewGroup parent) View v = convertview; ViewHolder holder; if(convertview == null) holder = new ViewHolder(); v = inflater.inflate(layout, parent, false); holder.tracktextview = (TextView) 212 m a s o
v.findviewbyid(r.id.track); holder.artisttextview = (TextView) v.findviewbyid(r.id.artist); v.settag(holder); else holder = (ViewHolder)v.getTag(); Musicmusic=list.get(position); holder.tracktextview.settext(music.gettrack()); holder.artisttextview.settext(music.getartist()); return v; intent 를배포하는것이다. < 리스트 9> MediaSanner 실행 context.sendbroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + Environment.getExternalStorage Directory()))); 정보를수정한후 < 리스트 9> 를추가하면콘텐트프로바이더도동일하게변경될것이다. NeedleTagger 오픈소스프로젝트소개 1. 자동으로태그정보를가져옴. 2. 재생중쉽게이동 리스트뷰에어떻게배치하고출력할지를결정하는리스트어댑터다. < 리스트 8> 을바탕으로에뮬레이터를동작하면음악정보가출력되는것을확인할수있다. 두가지사항으로사용자편의성을높일것이라고했다. 연재의 1회와 2회를통해 intent를활용한태그에디터구현의기초를알아봤다. 기초만알아봤기때문에사용자가쓸만한애플리케이션을개발하려면많은노력이필요하다. 여기서이미마켓에서배포되고있는 NeedleTagger 오픈소스프로젝트를소개하고자한다. < 화면 9> 콘텐트프로바이더를사용해음악정보를리스트뷰로출력한화면 ContentProvider 정보갱신을위한 MediaSanner 작성앞서살펴본내용으로콘텐트프로바이더에서음악의리스트를보여주고사용자가선택해음악정보를수정할수있다. 하지만태그라이브러리로수정한정보는 MP3 파일에만반영될뿐콘텐트프로바이더정보에는반영되지않는다. 콘텐트프로바이더정보를수정하기위해서는다음과같은두가지방법이있다. 1. 데이터에직접접근해업데이트 2. MediaSanner를작동해변경된내역을찾게함. 여기에서는두번째방법을사용해파일의변경내역을자동으로찾도록하자. 방법은간단하다. 미디어가마운트됨을알리는 < 화면 10> NeedleTagger 1. 태그자동찾기 : 태그의정보를자동으로설정 2. 앨범이미지찾기 : 태그의제목과가수를바탕으로앨범커버검색 3. 음악재생중자동링크 : HTC 디자이어, 넥서스원의기본음악플레이어사용시상태바로바로가기세가지기능을구현한프로젝트다. http://code.google.com /p/needletagger/ 에서소스코드를볼수있으니관심있는분은방문하면도움이될것이다. 2회의짧은연재로안드로이드에서 intent를활용해다양한확장이가능하다는것과안드로이드오픈소스를활용해흥미로운애플리케이션을만들수있음을알았다. 독자들에게도움이됐길바란다. 이달의디스켓 : ID3 태그에디터-2.zip m a s o 213