/* * Entagged Audio Tag library * Copyright (c) 2003-2005 Raphael Slinckx <raphael@slinckx.net> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package org.jaudiotagger.tag.vorbiscomment; import org.jaudiotagger.audio.flac.metadatablock.MetadataBlockDataPicture; import org.jaudiotagger.audio.generic.AbstractTag; import org.jaudiotagger.audio.generic.Utils; import org.jaudiotagger.audio.ogg.util.VorbisHeader; import org.jaudiotagger.logging.ErrorMessage; import org.jaudiotagger.tag.*; import org.jaudiotagger.tag.datatype.Artwork; import org.jaudiotagger.tag.id3.valuepair.TextEncoding; import org.jaudiotagger.tag.reference.Tagger; import org.jaudiotagger.tag.vorbiscomment.util.Base64Coder; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.EnumMap; import java.util.EnumSet; import java.util.List; import static org.jaudiotagger.tag.vorbiscomment.VorbisCommentFieldKey.*; /** * This is the logical representation of Vorbis Comment Data */ public class VorbisCommentTag extends AbstractTag { private static EnumMap<FieldKey, VorbisCommentFieldKey> tagFieldToOggField = new EnumMap<FieldKey, VorbisCommentFieldKey>(FieldKey.class); private static EnumMap<FieldKey, VorbisCommentFieldKey> alternatives = new EnumMap<FieldKey, VorbisCommentFieldKey>(FieldKey.class); static { tagFieldToOggField.put(FieldKey.ALBUM, VorbisCommentFieldKey.ALBUM); tagFieldToOggField.put(FieldKey.ALBUM_ARTIST, VorbisCommentFieldKey.ALBUMARTIST); tagFieldToOggField.put(FieldKey.ALBUM_ARTIST_SORT, VorbisCommentFieldKey.ALBUMARTISTSORT); tagFieldToOggField.put(FieldKey.ALBUM_SORT, VorbisCommentFieldKey.ALBUMSORT); tagFieldToOggField.put(FieldKey.ARTIST, VorbisCommentFieldKey.ARTIST); tagFieldToOggField.put(FieldKey.AMAZON_ID, VorbisCommentFieldKey.ASIN); tagFieldToOggField.put(FieldKey.ARTIST_SORT, VorbisCommentFieldKey.ARTISTSORT); tagFieldToOggField.put(FieldKey.BARCODE, VorbisCommentFieldKey.BARCODE); tagFieldToOggField.put(FieldKey.BPM, VorbisCommentFieldKey.BPM); tagFieldToOggField.put(FieldKey.CATALOG_NO, VorbisCommentFieldKey.CATALOGNUMBER); tagFieldToOggField.put(FieldKey.COMMENT, VorbisCommentFieldKey.COMMENT); tagFieldToOggField.put(FieldKey.COMPOSER, VorbisCommentFieldKey.COMPOSER); tagFieldToOggField.put(FieldKey.COMPOSER_SORT, VorbisCommentFieldKey.COMPOSERSORT); tagFieldToOggField.put(FieldKey.CONDUCTOR, VorbisCommentFieldKey.CONDUCTOR); tagFieldToOggField.put(FieldKey.COVER_ART, VorbisCommentFieldKey.METADATA_BLOCK_PICTURE); tagFieldToOggField.put(FieldKey.CUSTOM1, VorbisCommentFieldKey.CUSTOM1); tagFieldToOggField.put(FieldKey.CUSTOM2, VorbisCommentFieldKey.CUSTOM2); tagFieldToOggField.put(FieldKey.CUSTOM3, VorbisCommentFieldKey.CUSTOM3); tagFieldToOggField.put(FieldKey.CUSTOM4, VorbisCommentFieldKey.CUSTOM4); tagFieldToOggField.put(FieldKey.CUSTOM5, VorbisCommentFieldKey.CUSTOM5); tagFieldToOggField.put(FieldKey.DISC_NO, VorbisCommentFieldKey.DISCNUMBER); tagFieldToOggField.put(FieldKey.DISC_TOTAL, VorbisCommentFieldKey.DISCTOTAL); tagFieldToOggField.put(FieldKey.ENCODER, VorbisCommentFieldKey.VENDOR); //Known as vendor in VorbisComment tagFieldToOggField.put(FieldKey.FBPM, VorbisCommentFieldKey.FBPM); tagFieldToOggField.put(FieldKey.GENRE, VorbisCommentFieldKey.GENRE); tagFieldToOggField.put(FieldKey.GROUPING, VorbisCommentFieldKey.GROUPING); tagFieldToOggField.put(FieldKey.ISRC, VorbisCommentFieldKey.ISRC); tagFieldToOggField.put(FieldKey.IS_COMPILATION, VorbisCommentFieldKey.COMPILATION); tagFieldToOggField.put(FieldKey.KEY, VorbisCommentFieldKey.KEY); tagFieldToOggField.put(FieldKey.LANGUAGE, VorbisCommentFieldKey.LANGUAGE); tagFieldToOggField.put(FieldKey.LYRICIST, VorbisCommentFieldKey.LYRICIST); tagFieldToOggField.put(FieldKey.LYRICS, VorbisCommentFieldKey.LYRICS); tagFieldToOggField.put(FieldKey.MEDIA, VorbisCommentFieldKey.MEDIA); tagFieldToOggField.put(FieldKey.MOOD, VorbisCommentFieldKey.MOOD); tagFieldToOggField.put(FieldKey.MUSICBRAINZ_ARTISTID, VorbisCommentFieldKey.MUSICBRAINZ_ARTISTID); tagFieldToOggField.put(FieldKey.MUSICBRAINZ_DISC_ID, VorbisCommentFieldKey.MUSICBRAINZ_DISCID); tagFieldToOggField.put(FieldKey.MUSICBRAINZ_RELEASEARTISTID, VorbisCommentFieldKey.MUSICBRAINZ_ALBUMARTISTID); tagFieldToOggField.put(FieldKey.MUSICBRAINZ_RELEASEID, VorbisCommentFieldKey.MUSICBRAINZ_ALBUMID); tagFieldToOggField.put(FieldKey.MUSICBRAINZ_RELEASE_GROUP_ID, VorbisCommentFieldKey.MUSICBRAINZ_RELEASEGROUPID); tagFieldToOggField.put(FieldKey.MUSICBRAINZ_RELEASE_COUNTRY, VorbisCommentFieldKey.RELEASECOUNTRY); tagFieldToOggField.put(FieldKey.MUSICBRAINZ_RELEASE_STATUS, VorbisCommentFieldKey.MUSICBRAINZ_ALBUMSTATUS); tagFieldToOggField.put(FieldKey.MUSICBRAINZ_RELEASE_TYPE, VorbisCommentFieldKey.MUSICBRAINZ_ALBUMTYPE); tagFieldToOggField.put(FieldKey.MUSICBRAINZ_TRACK_ID, VorbisCommentFieldKey.MUSICBRAINZ_TRACKID); tagFieldToOggField.put(FieldKey.MUSICBRAINZ_WORK_ID, VorbisCommentFieldKey.MUSICBRAINZ_WORKID); tagFieldToOggField.put(FieldKey.OCCASION, VorbisCommentFieldKey.OCCASION); tagFieldToOggField.put(FieldKey.ORIGINAL_ALBUM, VorbisCommentFieldKey.ORIGINAL_ALBUM); tagFieldToOggField.put(FieldKey.ORIGINAL_ARTIST, VorbisCommentFieldKey.ORIGINAL_ARTIST); tagFieldToOggField.put(FieldKey.ORIGINAL_LYRICIST, VorbisCommentFieldKey.ORIGINAL_LYRICIST); tagFieldToOggField.put(FieldKey.ORIGINAL_YEAR, VorbisCommentFieldKey.ORIGINAL_YEAR); tagFieldToOggField.put(FieldKey.MUSICIP_ID, VorbisCommentFieldKey.MUSICIP_PUID); tagFieldToOggField.put(FieldKey.QUALITY, VorbisCommentFieldKey.QUALITY); tagFieldToOggField.put(FieldKey.RATING, VorbisCommentFieldKey.RATING); tagFieldToOggField.put(FieldKey.RECORD_LABEL, VorbisCommentFieldKey.LABEL); tagFieldToOggField.put(FieldKey.REMIXER, VorbisCommentFieldKey.REMIXER); tagFieldToOggField.put(FieldKey.TAGS, VorbisCommentFieldKey.TAGS); tagFieldToOggField.put(FieldKey.SCRIPT, VorbisCommentFieldKey.SCRIPT); tagFieldToOggField.put(FieldKey.TEMPO, VorbisCommentFieldKey.TEMPO); tagFieldToOggField.put(FieldKey.TITLE, VorbisCommentFieldKey.TITLE); tagFieldToOggField.put(FieldKey.TITLE_SORT, VorbisCommentFieldKey.TITLESORT); tagFieldToOggField.put(FieldKey.TRACK, VorbisCommentFieldKey.TRACKNUMBER); tagFieldToOggField.put(FieldKey.TRACK_TOTAL, VorbisCommentFieldKey.TRACKTOTAL); tagFieldToOggField.put(FieldKey.URL_DISCOGS_ARTIST_SITE, VorbisCommentFieldKey.URL_DISCOGS_ARTIST_SITE); tagFieldToOggField.put(FieldKey.URL_DISCOGS_RELEASE_SITE, VorbisCommentFieldKey.URL_DISCOGS_RELEASE_SITE); tagFieldToOggField.put(FieldKey.URL_LYRICS_SITE, VorbisCommentFieldKey.URL_LYRICS_SITE); tagFieldToOggField.put(FieldKey.URL_OFFICIAL_ARTIST_SITE, VorbisCommentFieldKey.URL_OFFICIAL_ARTIST_SITE); tagFieldToOggField.put(FieldKey.URL_OFFICIAL_RELEASE_SITE, VorbisCommentFieldKey.URL_OFFICIAL_RELEASE_SITE); tagFieldToOggField.put(FieldKey.URL_WIKIPEDIA_ARTIST_SITE, VorbisCommentFieldKey.URL_WIKIPEDIA_ARTIST_SITE); tagFieldToOggField.put(FieldKey.URL_WIKIPEDIA_RELEASE_SITE, VorbisCommentFieldKey.URL_WIKIPEDIA_RELEASE_SITE); tagFieldToOggField.put(FieldKey.YEAR, VorbisCommentFieldKey.DATE); tagFieldToOggField.put(FieldKey.ENGINEER, VorbisCommentFieldKey.ENGINEER); tagFieldToOggField.put(FieldKey.PRODUCER, VorbisCommentFieldKey.PRODUCER); tagFieldToOggField.put(FieldKey.DJMIXER, VorbisCommentFieldKey.DJMIXER); tagFieldToOggField.put(FieldKey.MIXER, VorbisCommentFieldKey.MIXER); tagFieldToOggField.put(FieldKey.ARRANGER, VorbisCommentFieldKey.ARRANGER); alternatives.put(FieldKey.ALBUM_ARTIST, ALBUM_ARTIST); alternatives.put(FieldKey.TRACK_TOTAL, TOTALTRACKS); alternatives.put(FieldKey.DISC_TOTAL, TOTALDISCS); } //This is the vendor string that will be written if no other is supplied. Should be the name of the software //that actually encoded the file in the first place. public static final String DEFAULT_VENDOR = "jaudiotagger"; /** * Only used within Package, hidden because it doesnt set Vendor * which should be done when created by end user */ public VorbisCommentTag() { } /** * Use to construct a new tag properly initialized * * @return */ public static VorbisCommentTag createNewTag() { VorbisCommentTag tag = new VorbisCommentTag(); tag.setVendor(DEFAULT_VENDOR); return tag; } /** * @return the vendor, generically known as the encoder */ public String getVendor() { return getFirst(VENDOR.getFieldName()); } /** * Set the vendor, known as the encoder generally * <p/> * We dont want this to be blank, when written to file this field is written to a different location * to all other fields but user of library can just reat it as another field * * @param vendor */ public void setVendor(String vendor) { if (vendor == null) { vendor = DEFAULT_VENDOR; } super.setField(new VorbisCommentTagField(VENDOR.getFieldName(), vendor)); } protected boolean isAllowedEncoding(String enc) { return enc.equals(VorbisHeader.CHARSET_UTF_8); } public String toString() { return "OGG " + super.toString(); } /** * Create Tag Field using generic key */ @Override public TagField createField(FieldKey genericKey, String value) throws KeyNotFoundException, FieldDataInvalidException { if (genericKey == null) { throw new KeyNotFoundException(); } return createField(tagFieldToOggField.get(genericKey), value); } /** * Create Tag Field using ogg key * * @param vorbisCommentFieldKey * @param value * @return * @throws org.jaudiotagger.tag.KeyNotFoundException * * @throws org.jaudiotagger.tag.FieldDataInvalidException * */ public TagField createField(VorbisCommentFieldKey vorbisCommentFieldKey, String value) throws KeyNotFoundException, FieldDataInvalidException { if (value == null) { throw new IllegalArgumentException(ErrorMessage.GENERAL_INVALID_NULL_ARGUMENT.getMsg()); } if (vorbisCommentFieldKey == null) { throw new KeyNotFoundException(); } return new VorbisCommentTagField(vorbisCommentFieldKey.getFieldName(), value); } /** * Create Tag Field using ogg key * <p/> * This method is provided to allow you to create key of any value because VorbisComment allows * arbitary keys. * * @param vorbisCommentFieldKey * @param value * @return */ public TagField createField(String vorbisCommentFieldKey, String value) { if (value == null) { throw new IllegalArgumentException(ErrorMessage.GENERAL_INVALID_NULL_ARGUMENT.getMsg()); } return new VorbisCommentTagField(vorbisCommentFieldKey, value); } /** * Maps the generic key to the ogg key and return the list of values for this field * * @param genericKey */ @Override public List<TagField> getFields(FieldKey genericKey) throws KeyNotFoundException { VorbisCommentFieldKey vorbisCommentFieldKey = tagFieldToOggField.get(genericKey); if (vorbisCommentFieldKey == null) { throw new KeyNotFoundException(); } List<TagField> tagFields = super.getFields(vorbisCommentFieldKey.getFieldName()); if (tagFields == null || tagFields.isEmpty()) { vorbisCommentFieldKey = alternatives.get(genericKey); tagFields = super.getFields(vorbisCommentFieldKey.getFieldName()); } return tagFields; } /** * Retrieve the first value that exists for this vorbis comment key * * @param vorbisCommentKey * @return * @throws org.jaudiotagger.tag.KeyNotFoundException * */ public List<TagField> get(VorbisCommentFieldKey vorbisCommentKey) throws KeyNotFoundException { if (vorbisCommentKey == null) { throw new KeyNotFoundException(); } return super.getFields(vorbisCommentKey.getFieldName()); } public String getValue(FieldKey genericKey, int index) throws KeyNotFoundException { VorbisCommentFieldKey vorbisCommentFieldKey = tagFieldToOggField.get(genericKey); if (vorbisCommentFieldKey == null) { throw new KeyNotFoundException(); } String item = super.getItem(vorbisCommentFieldKey.getFieldName(), index); if (item == null || item.isEmpty()) { vorbisCommentFieldKey = alternatives.get(genericKey); item = super.getItem(vorbisCommentFieldKey.getFieldName(), index); } return item; } /** * Retrieve the first value that exists for this vorbis comment key * * @param vorbisCommentKey * @return * @throws org.jaudiotagger.tag.KeyNotFoundException * */ public String getFirst(VorbisCommentFieldKey vorbisCommentKey) throws KeyNotFoundException { if (vorbisCommentKey == null) { throw new KeyNotFoundException(); } return super.getFirst(vorbisCommentKey.getFieldName()); } /** * Delete fields with this generic key * * @param genericKey */ public void deleteField(FieldKey genericKey) throws KeyNotFoundException { if (genericKey == null) { throw new KeyNotFoundException(); } VorbisCommentFieldKey vorbisCommentFieldKey = tagFieldToOggField.get(genericKey); deleteField(vorbisCommentFieldKey); } /** * Delete fields with this vorbisCommentFieldKey * * @param vorbisCommentFieldKey * @throws org.jaudiotagger.tag.KeyNotFoundException * */ public void deleteField(VorbisCommentFieldKey vorbisCommentFieldKey) throws KeyNotFoundException { if (vorbisCommentFieldKey == null) { throw new KeyNotFoundException(); } super.deleteField(vorbisCommentFieldKey.getFieldName()); } /** * Retrieve artwork raw data when using the deprecated COVERART format * * @return */ public byte[] getArtworkBinaryData() { String base64data = this.getFirst(VorbisCommentFieldKey.COVERART); byte[] rawdata = Base64Coder.decode(base64data.toCharArray()); return rawdata; } /** * Retrieve artwork mimeType when using deprecated COVERART format * * @return mimetype */ public String getArtworkMimeType() { return this.getFirst(VorbisCommentFieldKey.COVERARTMIME); } /** * Is this tag empty * <p/> * <p>Overridden because check for size of one because there is always a vendor tag unless just * created an empty vorbis tag as part of flac tag in which case size could be zero * * @see org.jaudiotagger.tag.Tag#isEmpty() */ public boolean isEmpty() { return fields.size() <= 1; } /** * Add Field * <p/> * <p>Overidden because there can only be one vendor set * * @param field */ public void addField(TagField field) { if (field.getId().equals(VorbisCommentFieldKey.VENDOR.getFieldName())) { super.setField(field); } else { super.addField(field); } } public TagField getFirstField(FieldKey genericKey) throws KeyNotFoundException { if (genericKey == null) { throw new KeyNotFoundException(); } return getFirstField(tagFieldToOggField.get(genericKey).getFieldName()); } /** * @return list of artwork images */ public List<Artwork> getArtworkList() { List<Artwork> artworkList = new ArrayList<Artwork>(1); //Read Old Format if (getArtworkBinaryData() != null & getArtworkBinaryData().length > 0) { Artwork artwork = new Artwork(); artwork.setMimeType(getArtworkMimeType()); artwork.setBinaryData(getArtworkBinaryData()); artworkList.add(artwork); } //New Format (Supports Multiple Images) List<TagField> metadataBlockPics = this.get(VorbisCommentFieldKey.METADATA_BLOCK_PICTURE); for (TagField tagField : metadataBlockPics) { try { byte[] imageBinaryData = Base64Coder.decode(((TagTextField) tagField).getContent()); MetadataBlockDataPicture coverArt = new MetadataBlockDataPicture(ByteBuffer.wrap(imageBinaryData)); Artwork artwork = Artwork.createArtworkFromMetadataBlockDataPicture(coverArt); artworkList.add(artwork); } catch (IOException ioe) { throw new RuntimeException(ioe); } catch (InvalidFrameException ife) { throw new RuntimeException(ife); } } return artworkList; } /** * Create MetadataBlockPicture field, this is the preferred way of storing artwork in VorbisComment tag now but * has to be base encoded to be stored in VorbisComment * * @return MetadataBlockDataPicture */ private MetadataBlockDataPicture createMetadataBlockDataPicture(Artwork artwork) throws FieldDataInvalidException { if (artwork.isLinked()) { return new MetadataBlockDataPicture( Utils.getDefaultBytes(artwork.getImageUrl(), TextEncoding.CHARSET_ISO_8859_1), artwork.getPictureType(), MetadataBlockDataPicture.IMAGE_IS_URL, "", 0, 0, 0, 0); } else { BufferedImage image; try { image = artwork.getImage(); } catch (IOException ioe) { throw new FieldDataInvalidException("Unable to create MetadataBlockDataPicture from buffered:" + ioe.getMessage()); } return new MetadataBlockDataPicture(artwork.getBinaryData(), artwork.getPictureType(), artwork.getMimeType(), artwork.getDescription(), image.getWidth(), image.getHeight(), 0, 0); } } /** * Create Artwork field * * @param artwork * @return * @throws FieldDataInvalidException */ public TagField createField(Artwork artwork) throws FieldDataInvalidException { try { char[] testdata = Base64Coder.encode(createMetadataBlockDataPicture(artwork).getRawContent()); String base64image = new String(testdata); TagField imageTagField = createField(VorbisCommentFieldKey.METADATA_BLOCK_PICTURE, base64image); return imageTagField; } catch (UnsupportedEncodingException uee) { throw new RuntimeException(uee); } } /** * Create and set artwork field * * @return */ @Override public void setField(Artwork artwork) throws FieldDataInvalidException { //Set field this.setField(createField(artwork)); //If worked okay above then that should be first artwork and if we still had old coverart format //that should be removed if (this.getFirst(VorbisCommentFieldKey.COVERART).length() > 0) { this.deleteField(VorbisCommentFieldKey.COVERART); this.deleteField(VorbisCommentFieldKey.COVERARTMIME); } } /** * Add artwork field * * @param artwork * @throws FieldDataInvalidException */ public void addField(Artwork artwork) throws FieldDataInvalidException { this.addField(createField(artwork)); } /** * Create artwork field using the non-standard COVERART tag * <p/> * <p/> * Actually create two fields , the data field and the mimetype. Its is not recommended that you use this * method anymore. * * @param data raw image data * @param mimeType mimeType of data * <p/> * @return */ @Deprecated public void setArtworkField(byte[] data, String mimeType) { char[] testdata = Base64Coder.encode(data); String base64image = new String(testdata); VorbisCommentTagField dataField = new VorbisCommentTagField(VorbisCommentFieldKey.COVERART.getFieldName(), base64image); VorbisCommentTagField mimeField = new VorbisCommentTagField(VorbisCommentFieldKey.COVERARTMIME.getFieldName(), mimeType); setField(dataField); setField(mimeField); } /** * Create and set field with name of vorbisCommentkey * * @param vorbisCommentKey * @param value * @throws KeyNotFoundException * @throws FieldDataInvalidException */ public void setField(String vorbisCommentKey, String value) throws KeyNotFoundException, FieldDataInvalidException { TagField tagfield = createField(vorbisCommentKey, value); setField(tagfield); } /** * Create and add field with name of vorbisCommentkey * * @param vorbisCommentKey * @param value * @throws KeyNotFoundException * @throws FieldDataInvalidException */ public void addField(String vorbisCommentKey, String value) throws KeyNotFoundException, FieldDataInvalidException { TagField tagfield = createField(vorbisCommentKey, value); addField(tagfield); } /** * Delete all instance of artwork Field * * @throws KeyNotFoundException */ public void deleteArtworkField() throws KeyNotFoundException { //New Method this.deleteField(VorbisCommentFieldKey.METADATA_BLOCK_PICTURE); //Old Method this.deleteField(VorbisCommentFieldKey.COVERART); this.deleteField(VorbisCommentFieldKey.COVERARTMIME); } }