/*---------------------------------------------------------------------------- libtunepimp -- The MusicBrainz tagging library. Let a thousand taggers bloom! Copyright (C) Robert Kaye 2003 This file is part of libtunepimp. libtunepimp is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. libtunepimp is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with libtunepimp; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA $Id: vorbis_meta.cpp 7216 2006-04-14 23:10:49Z robert $ ----------------------------------------------------------------------------*/ //--------------------------------------------------------------------------- // This code is based on vorbis.cpp and vorbis.cpp from FreeAmp. EMusic.com // has released this code into the Public Domain. // (Thanks goes to Brett Thomas, VP Engineering Emusic.com) //--------------------------------------------------------------------------- // Portions (c) Copyright Kristian G. Kvilekval, and permission to use in LGPL // library granted on February 25th, 2003 #ifdef WIN32 # if _MSC_VER == 1200 # pragma warning(disable:4786) # endif #else # include #endif #include #include #include #include #include #include #include #include using namespace std; #include #include "metadata.h" #include "vorbis_meta.h" #include "vorbis/vorbisfile.h" #include "vcedit.h" #include "../../lib/utf8/utf8.h" #if WIN32 #include "../../config_win32.h" #endif typedef multimap tagmap_t; static bool add_comment(tagmap_t & tagmap, const string& tag, const string&val, bool singleton = true) { if (val.size() == 0) return false; // case 1: when a singleton, erase all existing values. if (singleton) tagmap.erase (tag); tagmap.insert (pair(tag,val)); return true; } /** Find a value from in the tag table returning true when it exists * */ static bool get_comment(tagmap_t& tagmap, const string& tag, string&val) { tagmap_t::iterator it; it = tagmap.find (tag); if (it != tagmap.end()) { val = (*it).second; return true; } return false; } /** Load all tags from the comment structure into the given map. */ static void load_tags (vorbis_comment *vc, tagmap_t& tagmap) { string entry; string key; string val; for (int i=0; icomments; i++) { entry = vc->user_comments[i]; string::size_type sep = entry.find ('='); if (sep != string::npos) { key = entry.substr(0, sep); val = entry.substr(sep+1, string::npos); transform(key.begin(),key.end(),key.begin(),(int(*)(int))&toupper); tagmap.insert (pair(key, val)); } } } /** Save the tags in the map to the given comment structure. * NOTE: This will not enforce vorbis tag recommenation singleton * or values, so use with care */ static void save_tags (vorbis_comment *vc, tagmap_t& tagmap) { string comment; string key, val; for (tagmap_t::iterator it = tagmap.begin(); it != tagmap.end(); it++) { key = (*it).first; val = (*it).second; transform(key.begin(),key.end(),key.begin(),(int(*)(int))&toupper); comment = key + '=' + val; vorbis_comment_add (vc, (char*)comment.c_str()); } } /** Write out vorbis metadata. */ bool Vorbis::write(const string &fileName, const Metadata & metadata, bool clear) { char dummy[20]; char *ptr; bool writetags; string temp; // We will support only id3-like tags. For a more complete list see // http://reactor-core.org/ogg-tag-standard.html ptr = strrchr((char *)fileName.c_str(), '.'); if (ptr == NULL) return false; if (strcmp(ptr, ".ogg")) return false; // Rewrite the file with the updated tags. vcedit_state *state; vorbis_comment *vc; state = vcedit_new_state(); TFILE *in = topen (fileName.c_str(), "rb", encoding.c_str()); if (in == NULL) { errString = string("Failed to open ") + fileName; vcedit_clear(state); return false; } if(vcedit_open(state, in) < 0) { errString = string("Failed to open ") + fileName + string("as vorbis ") + string(vcedit_error(state)); tclose (in); vcedit_clear(state); return false; } tagmap_t tagmap; vc = vcedit_comments(state); if (!clear) load_tags (vc, tagmap); add_comment(tagmap, "TITLE", metadata.track.c_str()); add_comment(tagmap, "ARTIST", metadata.artist.c_str()); add_comment(tagmap, "ALBUM", metadata.album.c_str()); add_comment(tagmap, "MUSICBRAINZ_SORTNAME", metadata.sortName.c_str()); add_comment(tagmap, "MUSICBRAINZ_TRACKID", metadata.trackId.c_str()); add_comment(tagmap, "MUSICBRAINZ_ALBUMID", metadata.albumId.c_str()); if (metadata.albumType != eAlbumType_Error) { convertFromAlbumType(metadata.albumType, temp); add_comment(tagmap, "MUSICBRAINZ_ALBUMTYPE", temp); } if (metadata.albumStatus != eAlbumStatus_Error) { convertFromAlbumStatus(metadata.albumStatus, temp); add_comment(tagmap, "MUSICBRAINZ_ALBUMSTATUS", temp); } add_comment(tagmap, "MUSICBRAINZ_ARTISTID", metadata.artistId.c_str()); add_comment(tagmap, "MUSICIP_PUID", metadata.filePUID.c_str()); if (!metadata.albumArtistId.empty()) { add_comment(tagmap, "MUSICBRAINZ_ALBUMARTISTID", metadata.albumArtistId.c_str()); add_comment(tagmap, "MUSICBRAINZ_ALBUMARTIST", metadata.albumArtist.c_str()); add_comment(tagmap, "MUSICBRAINZ_ALBUMARTISTSORTNAME", metadata.albumArtistSortName.c_str()); } else if (metadata.variousArtist) { add_comment(tagmap, "MUSICBRAINZ_ALBUMARTISTID", MBI_VARIOUS_ARTIST_ID); } if (metadata.trackNum > 0) { sprintf (dummy, "%d", metadata.trackNum); add_comment(tagmap, "TRACKNUMBER", dummy); } if (metadata.totalInSet > 0) { char dummy[20]; sprintf(dummy, "%d", metadata.totalInSet); add_comment(tagmap, "TRACKTOTAL", dummy); } if (metadata.releaseYear > 0) { char temp[16]; sprintf(temp, "%04d-%02d-%02d", metadata.releaseYear, metadata.releaseMonth, metadata.releaseDay); add_comment(tagmap, "DATE", temp); } sprintf(dummy, "%d", metadata.nonAlbum); add_comment(tagmap, "MUSICBRAINZ_NONALBUM", dummy); sprintf(dummy, "%d", metadata.variousArtist); add_comment(tagmap, "MUSICBRAINZ_VARIOUSARTISTS", dummy); if (metadata.releaseCountry.length()) add_comment(tagmap, "RELEASECOUNTRY", metadata.releaseCountry); vorbis_comment_clear (vc); save_tags (vc, tagmap); char newpath[1025]; TFILE *out = NULL; writetags = false; tmktempname(fileName.c_str(), newpath, 1025); out = topen (newpath, "wb", encoding.c_str()); if (out == NULL || vcedit_write(state, out) < 0) { errString = string("Failed to write comments to output file: ") + string(vcedit_error(state)); } else writetags = true; if (in) tclose (in); if (out) tclose (out); vcedit_clear(state); if (writetags) { tunlink(fileName.c_str(), encoding.c_str()); if (trename (newpath, fileName.c_str(), encoding.c_str()) < 0) { errString = string("Failed rename output file: ") + string(newpath); return false; } } return true; } bool Vorbis::read(const string &fileName, Metadata &metadata) { char *ptr; // We will support only id3-like tags. For a more complete list see // http://reactor-core.org/ogg-tag-standard.html ptr = strrchr((char *)fileName.c_str(), '.'); if (ptr == NULL) return false; if (strcmp(ptr, ".ogg")) return false; vcedit_state *state; vorbis_comment *vc; state = vcedit_new_state(); TFILE *in = topen (fileName.c_str(), "rb", encoding.c_str()); if (in == NULL) { vcedit_clear(state); return false; } if(vcedit_open(state, in) < 0) { tclose (in); vcedit_clear(state); return false; } tagmap_t tagmap; string val; vc = vcedit_comments(state); load_tags (vc, tagmap); if (get_comment(tagmap, "TITLE", val)) metadata.track = string(val.c_str()); if (get_comment(tagmap, "ARTIST", val)) metadata.artist = string(val.c_str()); if (get_comment(tagmap, "ALBUM", val)) { metadata.album = string(val.c_str()); metadata.nonAlbum = strcmp(metadata.album.c_str(), TP_NONALBUMTRACKS_NAME) == 0; } if (get_comment(tagmap, "TRACKNUMBER", val)) metadata.trackNum = atoi(val.c_str()); if (get_comment(tagmap, "TRACKTOTAL", val)) metadata.totalInSet = atoi(val.c_str()); if (get_comment(tagmap, "MUSICBRAINZ_TRACKID", val)) metadata.trackId = string(val.c_str()); if (get_comment(tagmap, "MUSICBRAINZ_ARTISTID", val)) metadata.artistId = string(val.c_str()); if (get_comment(tagmap, "MUSICBRAINZ_ALBUMID", val)) metadata.albumId = string(val.c_str()); if (get_comment(tagmap, "MUSICBRAINZ_ALBUMTYPE", val)) metadata.albumType = convertToAlbumType(val.c_str()); if (get_comment(tagmap, "MUSICBRAINZ_ALBUMSTATUS", val)) metadata.albumStatus = convertToAlbumStatus(val.c_str()); if (get_comment(tagmap, "MUSICBRAINZ_SORTNAME", val)) metadata.sortName = string(val.c_str()); if (get_comment(tagmap, "MUSICBRAINZ_ALBUMARTISTID", val)) { metadata.variousArtist = strcasecmp(val.c_str(), MBI_VARIOUS_ARTIST_ID) == 0; metadata.albumArtistId = string(val.c_str()); if (get_comment(tagmap, "MUSICBRAINZ_ALBUMARTIST", val)) metadata.albumArtist = string(val.c_str()); if (get_comment(tagmap, "MUSICBRAINZ_ALBUMARTISTSORTNAME", val)) metadata.albumArtistSortName = string(val.c_str()); } if (get_comment(tagmap, "MUSICIP_PUID", val)) metadata.filePUID = string(val.c_str()); if (get_comment(tagmap, "DATE", val)) { int num, month, day, year; num = sscanf(val.c_str(), "%d-%d-%d", &year, &month, &day); if (num == 3) { metadata.releaseYear = year; metadata.releaseMonth = month; metadata.releaseDay = day; } if (num == 2) { metadata.releaseYear = year; metadata.releaseMonth = month; metadata.releaseDay = 0; } if (num == 1) { metadata.releaseYear = year; metadata.releaseMonth = 0; metadata.releaseDay = 0; } } if (get_comment(tagmap, "RELEASECOUNTRY", val)) metadata.releaseCountry = string(val.c_str()); if (get_comment(tagmap, "MUSICBRAINZ_NONALBUM", val)) metadata.nonAlbum = atoi(val.c_str()); if (get_comment(tagmap, "MUSICBRAINZ_VARIOUSARTISTS", val)) metadata.variousArtist = atoi(val.c_str()); vcedit_clear(state); tclose(in); metadata.fileFormat = "ogg"; return true; }