/* * Written By Charles M. Chen * * Created on Jan 1, 2006 * */ package org.cmc.music.myid3.id3v1; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import org.cmc.music.common.ID3v1Genre; import org.cmc.music.metadata.IMusicMetadata; import org.cmc.music.metadata.MusicMetadata; import org.cmc.music.metadata.MusicMetadataConstants; import org.cmc.music.myid3.ID3Tag; import org.cmc.music.myid3.MyID3Listener; import org.cmc.music.util.Debug; import org.cmc.music.util.FileUtils; public class MyID3v1 implements MusicMetadataConstants, MyID3v1Constants { public byte[] toTag(MyID3Listener listener, IMusicMetadata values, boolean strict) throws UnsupportedEncodingException { byte result[] = new byte[ID3_V1_TAG_LENGTH]; int index = 0; result[index++] = 0x54; // T result[index++] = 0x41; // A result[index++] = 0x47; // G writeField(result, index, 30, values.getSongTitle()); index += 30; writeField(result, index, 30, values.getArtist()); index += 30; writeField(result, index, 30, values.getAlbum()); index += 30; { Number value = values.getYear(); writeField(result, index, 4, value == null ? null : "" + value); index += 4; } Number trackNumber = null; { Number value = values.getTrackNumberNumeric(); if (value != null && value.intValue() >= 0 && value.intValue() < 256) trackNumber = value; } String comment = null; if (values.getComments().size() > 0) comment = (String) values.getComments().get(0); // TODO: should we ignore 0x00 and 0xff track numbers? if (trackNumber == null) { writeField(result, index, 30, comment); index += 30; } else { writeField(result, index, 28, comment); index += 28; result[index++] = 0; result[index++] = (byte) trackNumber.intValue(); } { Object o = values.getGenreID(); if (o == null) o = values.getGenreName(); if (o != null && (o instanceof String)) { String genre_name = (String) o; Number genre_id = ID3v1Genre.getIDForName(genre_name); if (genre_id != null) { o = genre_id; // Debug.debug("fixed genre", genre_name); // Debug.debug("fixed genre", genre_id); // Debug.dumpStack(); } } if (o != null && !(o instanceof Number)) { if (null != listener) listener.log("Discarding invalid genre in ID3v1 tag", o + " (" + Debug.getType(o) + ")"); // Debug.dumpStack(); } else { Number value = (Number) o; if (value != null && value.intValue() >= 0 && value.intValue() < 80) result[index++] = (byte) value.intValue(); else result[index++] = 0; } } // Debug.debug("index", index); return result; } private void writeField(byte bytes[], int start, int max_length, String s) throws UnsupportedEncodingException { if (s == null) { for (int i = 0; i < max_length; i++) bytes[i + start] = 0; return; } byte value[] = s.getBytes(DEFAULT_CHAR_ENCODING); int count = Math.min(value.length, max_length); for (int i = 0; i < count; i++) bytes[i + start] = value[i]; for (int i = count; i < max_length; i++) bytes[i + start] = 0; } // private boolean isValidIso8859(byte bytes[], int start, int length) // { // for (int i = start; i < start + length; i++) // { // int value = 0xff & bytes[i]; // if (value >= 0x20 && value <= 0x7E) // ; // else if (value >= 0xA0 && value <= 0xFF) // ; // else // { // Debug.debug("bad byte[" + i + "/" + length + "]: " + value // + " (0x" + Integer.toHexString(value) + ""); // return false; // } // } // return true; // } private String getField(MyID3Listener listener, byte bytes[], int start, int length) { for (int i = start; i < start + length; i++) { if (bytes[i] == 0) { length = i - start; break; } } // if (null != listener) // listener // .log("isValidIso8859", isValidIso8859(bytes, start, length)); if (length > 0) { try { String result = new String(bytes, start, length, DEFAULT_CHAR_ENCODING); result = result.trim(); if (result.length() < 1) return null; return result; } catch (Throwable e) { Debug.debug(e); } } return null; } public IMusicMetadata parseTags(byte bytes[], boolean strict) { return parseTags(null, bytes, strict); } public IMusicMetadata parseTags(MyID3Listener listener, byte bytes[], boolean strict) { IMusicMetadata result = new MusicMetadata("ID3v1"); int counter = 3; String title = getField(listener, bytes, counter, 30); counter += 30; result.setSongTitle(title); if (null != listener) listener.logWithLength("id3v1 title", title); String artist = getField(listener, bytes, counter, 30); counter += 30; result.setArtist(artist); if (null != listener) listener.logWithLength("id3v1 artist", artist); String album = getField(listener, bytes, counter, 30); counter += 30; result.setAlbum(album); if (null != listener) listener.logWithLength("id3v1 album", album); String yearString = getField(listener, bytes, counter, 4); counter += 4; Number year = null; try { if (null != yearString) year = Integer.valueOf(yearString); } catch (NumberFormatException e) { // ignore } result.setYear(year); if (null != listener) { listener.logWithLength("id3v1 year", yearString); if (null != yearString) listener.log("id3v1 year", year); } String comment = getField(listener, bytes, counter, 30); counter += 30; if (null != comment) result.addComment(comment); if (null != listener) listener.logWithLength("id3v1 comment", comment); if (bytes[counter - 2] == 0 && bytes[counter - 1] != 0) { int trackNumber = 0xff & bytes[counter - 1]; // TODO: should we ignore 0x00 and 0xff track numbers? result.setTrackNumberNumeric(new Integer(trackNumber)); if (null != listener) listener.log("id3v1 trackNumber: " + trackNumber); } int genre = 0xff & bytes[counter]; if (genre < 80 && genre > 0) { result.setGenreID(new Integer(genre)); result.setGenreName(ID3v1Genre.getNameForID(new Integer(genre))); if (null != listener) listener.log("id3v1 genre: " + genre); } if (null != listener) listener.log(); return result; } public boolean hasID3v1(File file) throws IOException { if (file == null || !file.exists()) return false; long length = file.length(); if (length < ID3_V1_TAG_LENGTH) return false; byte bytes[]; InputStream is = null; try { is = new FileInputStream(file); is = new BufferedInputStream(is); is.skip(length - ID3_V1_TAG_LENGTH); bytes = FileUtils.readArray(is, ID3_V1_TAG_LENGTH); } finally { try { if (is != null) is.close(); } catch (IOException e) { Debug.debug(e); } } if (bytes[0] != 'T') return false; if (bytes[1] != 'A') return false; if (bytes[2] != 'G') return false; return true; } public ID3Tag readID3v1(File file, boolean strict) throws IOException { return readID3v1(null, file, strict); } public ID3Tag.V1 readID3v1(MyID3Listener listener, File file, boolean strict) throws IOException { if (file == null || !file.exists()) return null; long length = file.length(); if (length < ID3_V1_TAG_LENGTH) return null; byte bytes[]; InputStream is = null; try { is = new FileInputStream(file); is = new BufferedInputStream(is); is.skip(length - ID3_V1_TAG_LENGTH); bytes = FileUtils.readArray(is, ID3_V1_TAG_LENGTH); } finally { try { if (is != null) is.close(); } catch (IOException e) { Debug.debug(e); } } if (bytes[0] != 'T') return null; if (bytes[1] != 'A') return null; if (bytes[2] != 'G') return null; if (null != listener) listener.log("ID3v1 tag found."); IMusicMetadata tags = new MyID3v1().parseTags(listener, bytes, strict); return new ID3Tag.V1(bytes, tags); } }