페이지

2010-02-05

TagLib을 이용해 mp3안의 메타 데이터 (ID3) 가져오기

mp3 안에는 노래 제목, 앨범, 가수, 트랙 번호 등의 정보가 들어갈 수 있는데, 가장 많이 이용되는 방식이 ID3 라는 표준이다. 이 정보를 뽑아낼 일이 있어서 TagLib 이라고 하는 C++로 만들어진 라이브러리를 사용해봤다.

(참고) ID3 사이트의 Implementations에는 C/C++ 라이브러리가 세 개 있는데, 이중에 ID3Lib 은 한글 이름의 파일을 읽어들이는데 문제가 있었다. TagLib은 한글 파일 이름도 잘 읽고, ID3 뿐만 아니라 다른 메타 데이터 포맷도 지원하고, API도 이해하기 쉽게 되어 있었다. 하지만 한가지 문제가 있었는데, wxWidgets과 함께 사용했을 때 약 20byte 정도의 메모리 누수가 있었다. (Visual C++ 2008 Express Edition으로 컴파일 시.) 큰 문제가 아니라고 생각해서 일단은 그냥 사용하기로 했지만, 약간 찜찜하긴 하다. ㅡㅡ;;

먼저 TagLib 을 다운받아 빌드해서 사용할 준비를 해야한다. 바이너리 배포도 있는데, 아무래도 직접 빌드하는 것이 마음이 놓인다. 이 과정은 기록 생략...

먼저 필요한 헤더 파일을 include.
#include "taglib/fileref.h"
#include "taglib/tag.h"

TagLib은 'TagLib' 이라는 namespace를 사용한다. using namespace TagLib; 해주든가, 아니면 매번 TagLib:: 을 붙여주어야 한다. 일단 매번 붙이기로.

mp3 안의 정보를 가져와 저장하기 위해 AudioFileInfo 라는 클래스를 만들었다.
class AudioFileInfo
{
public:
    AudioFileInfo(const wxString& file);
protected:
    wxString ToWxString(const TagLib::String& str);
    bool ExtractPicture(const wxString& filename);
protected:
    wxString m_title;
    wxString m_artist;
    wxString m_album;
    wxString m_comment;
    wxString m_genre;
    unsigned int m_year;
    unsigned int m_track;
    int m_bitrate;
    int m_sampleRate;
    int m_channels;
    int m_length;
    wxImage m_picture;
};

AudioFileInfo 의 생성자(constructor)에서 TagLib을 이용해 정보를 가져온 후, 각 멤버 변수에 저장하도록 했다. 하지만 먼저 멤버 변수 초기화부터...
AudioFileInfo::AudioFileInfo(const wxString& filename)
{
    m_year = 0;
    m_track = 0;
    m_bitrate = 0;
    m_sampleRate = 0;
    m_channels = 0;
    m_length = 0;
    

파일 타입에 대한 고민 없이 쉽게 정보를 알아오는 방법은, TagLib::FileRef 객체를 이용하는 것이다.
    TagLib::FileRef f(filename.fn_str());
    if (!f.isNull())
    {
        if (f.tag()) {
            m_title = ToWxString(f.tag()->title());
            m_artist = ToWxString(f.tag()->artist());
            m_album = ToWxString(f.tag()->album());
            m_comment = ToWxString(f.tag()->comment());
            m_genre = ToWxString(f.tag()->genre());
            m_year = f.tag()->year();
            m_track = f.tag()->track();
        }
f.tag()는 FileLib::Tag 객체인데, 여러 타입의 태그에 대한 추상 클래스이고, 실제 클래스는 TagLib::ID3v2::Tag 라든가 TagLib::MP4::Tag 같은 클래스이다. FileLib::Tag는 이 구체 태그들의 속성 중 공통적인 것만 가져올 수 있는 함수들을 제공한다. 위와같이 title, artist, album.. 등이 그것이다.

wxString의 fn_str()은 스트링을 OS의 file name encoding에 맞도록 const char* 형식으로 돌려주는 함수이다.

ToWxString(...) 함수는 TagLib::String 객체를 wxString 객체로 변환하기 위해 만든 함수이고, 내용은 다음과 같다.
wxString AudioFileInfo::ToWxString(const TagLib::String& str)
{
if (str == TagLib::String::null)
return wxEmptyString;
return wxString(str.toCString(true), wxConvUTF8);
}

TagLib::String의 toCString(bool) 함수는 스트링을 const char* 형식으로 변환해주는데, 함수 인자로 true 가 넘어가면 UTF-8로 인코딩을, false가 넘어가면 ISO8859_1 로 변환을 해준다. 한글이 제대로 나오게 하기 위해서는 위와같이 UTF-8 로 인코딩을 하게 한 후, wxString을 생성할 때 wxConvUTF8 로 다시 변환을 해주도록 하면 된다.

TagLib::FileRef 에서는 위와 같은 정보 뿐만 아니라 Audio File에 대한 속성도 가져올 수 있다.
        if (f.audioProperties()) {
            TagLib::AudioProperties *p = f.audioProperties();
            m_bitrate = p->bitrate();
            m_sampleRate = p->sampleRate();
            m_channels = p->channels();
            m_length = p->length();
        }

ID3에는 AttachedPicture가 추가될 수 있는데, 이 정보를 가져오기 위해서는 TagLib::ID3v2::Tag 클래스의 객체를 사용해야 한다. f.tag() 를 TagLib::ID3v2::Tag로 type casting해서 사용할 수도 있지만, f.tag() 가 다른 종류의 Tag일 수도 있으므로, 새로운 TagLib::ID3v2::Tag를 직접 생성하기로 했다. ExtractPicture() 함수가 그 역할을 한다.

bool AudioFileInfo::ExtractPicture(const wxString& filename)
{
    // if file is 'mp3', try ID3v2::Tag to extract picture information.
    if (filename.EndsWith(wxT(".mp3")))
    {
확장자가 mp3인 경우에만 처리하도록 했다.

TagLib::MPEG::File 객체를 만들면 곧바로 TagLib::ID3v2::Tag 객체를 가져올 수 있다.
        TagLib::MPEG::File mf(filename.fn_str());
        TagLib::ID3v2::Tag* v2 = mf.ID3v2Tag();
        if (v2) {

Tag 안에는 Frame이 들어있는데, 이 중에서 id가 'APIC' 인 것이 AttachedPicture 정보를 담고 있는 Frame이다. frameList(..) 함수를 이용해 'APIC' Frame만 가져올 수 있다.
            // 'APIC' is frame if of AttachedPictureFrame.
            // See http://id3.org/id3v2.3.0#head-70a65d30522ef0d37642224c2a40517ae35b7155
            const TagLib::ID3v2::FrameList& list = v2->frameList("APIC");
            if (!list.isEmpty()) {
                // extract just one picture
                TagLib::ID3v2::AttachedPictureFrame* pic = (TagLib::ID3v2::AttachedPictureFrame*)(*list.begin());

TagLib::ID3v2::AttachedPictureFrame 객체를 가져왔으면, 그로부터 Picture의 mime type과 실제 data를 얻을 수 있다. wxWidget이 'image/jpg'와 'image/bmp' mime type을 인식하지 못해서, 각각 'image/jpeg', 'image/x-bmp'로 보정하는 코드를 넣었다.
                wxString mime = ToWxString(pic->mimeType());
                TagLib::ByteVector data = pic->picture();
                if (data.size() > 0) {
                    // mime type correction: wxWidgets doesn't recognize 'image/jpg' and 'image/bmp'.
                    if (mime == wxT("image/jpg")) mime = wxT("image/jpeg");
                    if (mime == wxT("image/bmp")) mime = wxT("image/x-bmp");
                    m_picture = wxImage(wxMemoryInputStream(data.data(), data.size()), mime);
                    return true;
                }
            }
        }
    }
    return false;
}


이렇게 만들어진 AudioFileInfo 클래스는 다음과 같이 사용한다.

wxString filename(wxT("test.mp3"));
AudioFileInfo info(filename);

wxMessageBox(info.GetTitle());
wxMessageBox(info.GetArtist());
...

if (info.GetPicture().IsOk()) {
    // 사진을 보여주는 코드
}


댓글 없음:

댓글 쓰기