package com.mpatric.mp3agic.app; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import com.mpatric.mp3agic.BaseException; import com.mpatric.mp3agic.BufferTools; import com.mpatric.mp3agic.EncodedText; import com.mpatric.mp3agic.ID3Wrapper; import com.mpatric.mp3agic.ID3v1Tag; import com.mpatric.mp3agic.ID3v23Tag; import com.mpatric.mp3agic.Mp3File; import com.mpatric.mp3agic.NotSupportedException; import com.mpatric.mp3agic.Version; public class Mp3Retag extends BaseApp { private static final String RETAG_EXTENSION = ".retag"; private static final String BACKUP_EXTENSION = ".bak"; private static final int CUSTOM_TAG_WARNING_THRESHOLD = 1024; protected static Map<String, String> imageFileTypes; protected static boolean attachImage = false; protected static boolean keepCustomTag = false; protected static String comment = null; protected static String encoder = null; protected static String customTag = null; protected static String filename = null; protected Mp3File mp3file; { imageFileTypes = new LinkedHashMap<String, String>(); imageFileTypes.put("jpg", "image/jpeg"); imageFileTypes.put("jpeg", "image/jpeg"); imageFileTypes.put("png", "image/png"); } protected Mp3Retag() { } protected Mp3Retag(String filename) { try { mp3file = new Mp3File(filename); boolean hasId3v1Tag = mp3file.hasId3v1Tag(); boolean hasId3v2Tag = mp3file.hasId3v2Tag(); boolean hasImage = false; if (hasId3v2Tag && mp3file.getId3v2Tag().getAlbumImage() != null) hasImage = true; if (! hasId3v1Tag && ! hasId3v2Tag) { printError("ERROR processing \"" + extractFilename(filename) + "\" - no ID3 tags found"); } else { boolean hasCustomTag = mp3file.hasCustomTag(); if (hasCustomTag && mp3file.getCustomTag().length > CUSTOM_TAG_WARNING_THRESHOLD) { printError("WARNING processing \"" + extractFilename(filename) + "\" - custom tag is " + mp3file.getCustomTag().length + " bytes, potential corrupt file"); } retag(); StringBuffer message = new StringBuffer(); message.append("Retagged \""); message.append(extractFilename(mp3file.getFilename())); message.append("\""); if (! hasId3v1Tag) message.append(", added ID3v1 tag"); if (! hasId3v2Tag) message.append(", added ID3v2 tag"); if (! hasImage && mp3file.getId3v2Tag().getAlbumImage() != null) { message.append(", added album image"); } if (! hasCustomTag) { if (mp3file.hasCustomTag()) message.append(", added custom tag"); } else { if (! mp3file.hasCustomTag()) message.append(", removed custom tag"); else if (keepCustomTag && customTag != null && customTag.length() > 0) message.append(", appended to custom tag"); else message.append(", replaced custom tag"); } printOut(message.toString()); } } catch (BaseException e) { printError("ERROR processing \"" + extractFilename(filename) + "\" - " + e.getDetailedMessage()); if (mp3file != null) deleteFile(mp3file.getFilename() + RETAG_EXTENSION); } catch (Exception e) { printError("ERROR processing \"" + extractFilename(filename) + "\" - " + formatExceptionMessage(e)); if (mp3file != null) deleteFile(mp3file.getFilename() + RETAG_EXTENSION); } } private void retag() throws IOException, NotSupportedException { updateId3Tags(); updateCustomTag(); mp3file.save(mp3file.getFilename() + RETAG_EXTENSION); renameFiles(); } private void updateId3Tags() { ID3Wrapper oldId3Wrapper = new ID3Wrapper(mp3file.getId3v1Tag(), mp3file.getId3v2Tag()); ID3Wrapper newId3Wrapper = new ID3Wrapper(new ID3v1Tag(), new ID3v23Tag()); preProcess(oldId3Wrapper); newId3Wrapper.setTrack(cleanTrack(oldId3Wrapper.getTrack())); newId3Wrapper.setArtist(trimField(oldId3Wrapper.getArtist())); newId3Wrapper.setTitle(trimField(oldId3Wrapper.getTitle())); newId3Wrapper.setArtist(trimField(oldId3Wrapper.getArtist())); newId3Wrapper.setAlbum(trimField(oldId3Wrapper.getAlbum())); newId3Wrapper.setYear(trimField(oldId3Wrapper.getYear())); newId3Wrapper.setGenre(oldId3Wrapper.getGenre()); if (comment != null) { newId3Wrapper.setComment(trimField(comment)); } else { newId3Wrapper.setComment(trimField(oldId3Wrapper.getComment())); } newId3Wrapper.setComposer(trimField(oldId3Wrapper.getComposer())); newId3Wrapper.setOriginalArtist(trimField(oldId3Wrapper.getOriginalArtist())); newId3Wrapper.setCopyright(trimField(oldId3Wrapper.getCopyright())); newId3Wrapper.setUrl(trimField(oldId3Wrapper.getUrl())); if (encoder != null) { newId3Wrapper.setEncoder(trimField(encoder)); } else { newId3Wrapper.setEncoder(trimField(oldId3Wrapper.getEncoder())); } if (! attachImage || ! findAndSetAlbumImage(newId3Wrapper)) { newId3Wrapper.setAlbumImage(oldId3Wrapper.getAlbumImage(), cleanImageMimeType(oldId3Wrapper.getAlbumImageMimeType())); } newId3Wrapper.getId3v2Tag().setPadding(true); postProcess(newId3Wrapper); mp3file.setId3v1Tag(newId3Wrapper.getId3v1Tag()); mp3file.setId3v2Tag(newId3Wrapper.getId3v2Tag()); } private String cleanTrack(String track) { if (track == null) return track; int slashIndex = track.indexOf('/'); if (slashIndex < 0) return trimField(track); return trimField(track.substring(0, slashIndex)); } private String cleanImageMimeType(String mimeType) { if (mimeType == null) return mimeType; if (mimeType.indexOf('/') >= 0) return mimeType; return "image/" + mimeType.toLowerCase(); } protected void preProcess(ID3Wrapper id3Wrapper) { } protected void postProcess(ID3Wrapper id3Wrapper) { } private String trimField(String value) { if (value == null) return null; return value.trim(); } private boolean findAndSetAlbumImage(ID3Wrapper id3Wrapper) { String path = extractPath(mp3file.getFilename()); if (trySetAlbumImage(id3Wrapper, path + id3Wrapper.getArtist() + " - " + id3Wrapper.getAlbum())) { return true; } if (trySetAlbumImage(id3Wrapper, path + toCompressedString(id3Wrapper.getArtist()) + "-" + toCompressedString(id3Wrapper.getAlbum()))) { return true; } if (trySetAlbumImage(id3Wrapper, path + id3Wrapper.getAlbum())) { return true; } if (trySetAlbumImage(id3Wrapper, path + toCompressedString(id3Wrapper.getAlbum()))) { return true; } if (trySetAlbumImage(id3Wrapper, path + "folder")) { return true; } int andPosInArtist = id3Wrapper.getArtist().indexOf(" & "); if (andPosInArtist > 0) { if (trySetAlbumImage(id3Wrapper, path + id3Wrapper.getArtist().substring(0, andPosInArtist) + " - " + id3Wrapper.getAlbum())) { return true; } if (trySetAlbumImage(id3Wrapper, path + toCompressedString(id3Wrapper.getArtist().substring(0, andPosInArtist)) + "-" + toCompressedString(id3Wrapper.getAlbum()))) { return true; } } printError("WARNING processing \"" + extractFilename(mp3file.getFilename()) + "\" - no album image found"); return false; } private String toCompressedString(String s) { StringBuffer compressed = new StringBuffer(); for (int i = 0; i < s.length(); i++) { char ch = s.charAt(i); if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || (ch == '&') || (ch == '+') || (ch == '(') || (ch == ')')) { compressed.append(ch); } } return compressed.toString(); } private boolean trySetAlbumImage(ID3Wrapper newId3Wrapper, String filenameWithoutExtension) { Iterator<String> iterator = imageFileTypes.keySet().iterator(); while (iterator.hasNext()) { String fileExtension = iterator.next(); String mimeType = imageFileTypes.get(fileExtension.toLowerCase()); if (trySetAlbumImage(newId3Wrapper, filenameWithoutExtension + "." + fileExtension, mimeType)) { return true; } } return false; } private boolean trySetAlbumImage(ID3Wrapper id3Wrapper, String filename, String mimeType) { RandomAccessFile file = null; try { file = new RandomAccessFile(filename, "r"); byte[] bytes = new byte[(int) file.length()]; if (file.read(bytes) != file.length()) { return false; } id3Wrapper.setAlbumImage(bytes, mimeType); return true; } catch (IOException e) { // do nothing } finally { if (file != null) { try { file.close(); } catch (IOException e) { // do nothing } } } return false; } private void updateCustomTag() { byte[] existingCustomTag = mp3file.getCustomTag(); byte[] newCustomTag = null; if (keepCustomTag && existingCustomTag != null && existingCustomTag.length > 0) { if (customTag != null && customTag.length() > 0) { EncodedText customTagEncodedText = new EncodedText(customTag); byte bytes[] = customTagEncodedText.toBytes(true); int newLength = existingCustomTag.length + bytes.length; newCustomTag = new byte[newLength]; BufferTools.copyIntoByteBuffer(existingCustomTag, 0, existingCustomTag.length, newCustomTag, 0); BufferTools.copyIntoByteBuffer(bytes, 0, bytes.length, newCustomTag, existingCustomTag.length); } else { newCustomTag = mp3file.getCustomTag(); } } else if (customTag != null && customTag.length() > 0) { EncodedText customTagEncodedText = new EncodedText(customTag); newCustomTag = customTagEncodedText.toBytes(true); } mp3file.setCustomTag(newCustomTag); } protected void renameFiles() { File originalFile = new File(filename); File backupFile = new File(filename + BACKUP_EXTENSION); File retaggedFile = new File(filename + RETAG_EXTENSION); if (backupFile.exists()) { backupFile.delete(); } originalFile.renameTo(backupFile); retaggedFile.renameTo(originalFile); } private void deleteFile(String filename) { File file = new File(filename); file.delete(); } public static void main(String[] args) { if (! parseArgs(args)) { usage(); } else { new Mp3Retag(filename); } } protected static boolean parseArgs(String args[]) { if (args.length < 1) { return false; } for (int i = 0; i < args.length; i++) { if (args[i].charAt(0) == '-') { if ("-i".equals(args[i])) { attachImage = true; } else if ("-k".equals(args[i])) { keepCustomTag = true; } else if ("-c".equals(args[i])) { try { comment = args[i + 1]; i++; } catch (ArrayIndexOutOfBoundsException e) { return false; } } else if ("-e".equals(args[i])) { try { encoder = args[i + 1]; i++; } catch (ArrayIndexOutOfBoundsException e) { return false; } } else if ("-z".equals(args[i])) { try { customTag = args[i + 1]; i++; } catch (ArrayIndexOutOfBoundsException e) { return false; } } else { return false; } } else { if (filename != null) { return false; } filename = args[i]; } } if (filename == null) { return false; } return true; } private static void usage() { System.out.println("mp3retag [mp3agic " + Version.asString() + "]"); } }